diff --git a/clang-tools-extra/clang-query/Query.cpp b/clang-tools-extra/clang-query/Query.cpp --- a/clang-tools-extra/clang-query/Query.cpp +++ b/clang-tools-extra/clang-query/Query.cpp @@ -50,6 +50,13 @@ "default.\n" " IgnoreUnlessSpelledInSource " "Omit AST nodes unless spelled in the source.\n" + " set match-scope " + "Set the scope of matchers. Available kinds are:\n" + " include-headers " + "Match nodes included from header files. This mode is the " + "default.\n" + " main-file-only " + "Only match nodes from the main source file.\n" " set output " "Set whether to output only content.\n" " enable output " @@ -88,20 +95,43 @@ } // namespace +static void printMatch(llvm::raw_ostream &OS, QuerySession &QS, ASTUnit &AST, + const DynTypedNode &Node, StringRef BindName) { + if (QS.DiagOutput) { + clang::SourceRange R = Node.getSourceRange(); + if (R.isValid()) { + TextDiagnostic TD(OS, AST.getASTContext().getLangOpts(), + &AST.getDiagnostics().getDiagnosticOptions()); + TD.emitDiagnostic(FullSourceLoc(R.getBegin(), AST.getSourceManager()), + DiagnosticsEngine::Note, + ("\"" + BindName + "\" binds here").str(), + CharSourceRange::getTokenRange(R), None); + } + } + if (QS.PrintOutput) { + OS << "Binding for \"" << BindName << "\":\n"; + Node.print(OS, AST.getASTContext().getPrintingPolicy()); + OS << "\n"; + } + if (QS.DetailedASTOutput) { + OS << "Binding for \"" << BindName << "\":\n"; + const ASTContext &Ctx = AST.getASTContext(); + ASTDumper Dumper(OS, Ctx, AST.getDiagnostics().getShowColors()); + Dumper.SetTraversalKind(QS.TK); + Dumper.Visit(Node); + OS << "\n"; + } +} + bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { unsigned MatchCount = 0; + unsigned MatchesFromHeaders = 0; for (auto &AST : QS.ASTs) { MatchFinder Finder; std::vector Matches; - DynTypedMatcher MaybeBoundMatcher = Matcher; - if (QS.BindRoot) { - llvm::Optional M = Matcher.tryBind("root"); - if (M) - MaybeBoundMatcher = *M; - } CollectBoundNodes Collect(Matches); - if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) { + if (!Finder.addDynamicMatcher(Matcher, &Collect)) { OS << "Not a valid top-level matcher.\n"; return false; } @@ -130,43 +160,50 @@ << " " << std::string(PrefixText.size() + MaxLength, '=') << "\n\n"; } - for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) { - OS << "\nMatch #" << ++MatchCount << ":\n\n"; - - for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE; - ++BI) { - if (QS.DiagOutput) { - clang::SourceRange R = BI->second.getSourceRange(); - if (R.isValid()) { - TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(), - &AST->getDiagnostics().getDiagnosticOptions()); - TD.emitDiagnostic( - FullSourceLoc(R.getBegin(), AST->getSourceManager()), - DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here", - CharSourceRange::getTokenRange(R), None); + for (const auto &Match : Matches) { + const DynTypedNode &RootNode = Match.getRoot(); + if (QS.OnlyMainFileMatches || LLVM_UNLIKELY(QS.HeaderModeWarnOnce)) { + SourceRange SR = RootNode.getSourceRange(); + if (SR.isValid()) { + const SourceManager &SM = AST->getSourceManager(); + if (!SM.isInMainFile(SM.getExpansionLoc(SR.getBegin())) || + !SM.isInMainFile(SM.getExpansionLoc(SR.getEnd()))) { + ++MatchesFromHeaders; + if (LLVM_LIKELY(QS.OnlyMainFileMatches)) + continue; } } - if (QS.PrintOutput) { - OS << "Binding for \"" << BI->first << "\":\n"; - BI->second.print(OS, AST->getASTContext().getPrintingPolicy()); - OS << "\n"; - } - if (QS.DetailedASTOutput) { - OS << "Binding for \"" << BI->first << "\":\n"; - const ASTContext &Ctx = AST->getASTContext(); - ASTDumper Dumper(OS, Ctx, AST->getDiagnostics().getShowColors()); - Dumper.SetTraversalKind(QS.TK); - Dumper.Visit(BI->second); - OS << "\n"; - } + } + OS << "\nMatch #" << ++MatchCount << ":\n\n"; + + for (const auto &NameAndNode : Match.getMap()) { + printMatch(OS, QS, *AST, NameAndNode.second, NameAndNode.first); } - if (MI->getMap().empty()) + if (QS.BindRoot) + printMatch(OS, QS, *AST, RootNode, "root"); + else if (Match.getMap().empty()) OS << "No bindings.\n"; } } OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n"); + if (MatchesFromHeaders > 0) { + if (QS.OnlyMainFileMatches) { + OS << "Skipped " << MatchesFromHeaders + << (MatchesFromHeaders == 1 ? " match" : " matches") + << " from header files.\n"; + if (QS.HeaderModeWarnOnce) + OS << "Use 'set match-mode include-headers' to display them.\n"; + } else { + assert(QS.HeaderModeWarnOnce); + OS << "Included " << MatchesFromHeaders + << (MatchesFromHeaders == 1 ? " match" : " matches") + << " from header files.\nUse 'set match-mode main-file-only' to hide " + "them.\n"; + } + QS.HeaderModeWarnOnce = false; + } return true; } diff --git a/clang-tools-extra/clang-query/QueryParser.h b/clang-tools-extra/clang-query/QueryParser.h --- a/clang-tools-extra/clang-query/QueryParser.h +++ b/clang-tools-extra/clang-query/QueryParser.h @@ -44,6 +44,7 @@ QueryRef parseSetBool(bool QuerySession::*Var); QueryRef parseSetTraversalKind(TraversalKind QuerySession::*Var); + QueryRef parseMatchScope(bool QuerySession::*Var); template QueryRef parseSetOutputKind(); QueryRef completeMatcherExpression(); diff --git a/clang-tools-extra/clang-query/QueryParser.cpp b/clang-tools-extra/clang-query/QueryParser.cpp --- a/clang-tools-extra/clang-query/QueryParser.cpp +++ b/clang-tools-extra/clang-query/QueryParser.cpp @@ -141,6 +141,18 @@ return new SetQuery(Var, static_cast(Value)); } +QueryRef QueryParser::parseMatchScope(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = LexOrCompleteWord(this, ValStr) + .Case("main-file-only", 1) + .Case("include-headers", 0) + .Default(~0U); + if (Value == ~0U) { + return new InvalidQuery("expected match scope, got '" + ValStr + "'"); + } + return new SetQuery(Var, Value); +} + QueryRef QueryParser::endQuery(QueryRef Q) { StringRef Extra = Line; StringRef ExtraTrimmed = Extra.drop_while( @@ -185,7 +197,8 @@ PQV_Output, PQV_BindRoot, PQV_PrintMatcher, - PQV_Traversal + PQV_Traversal, + PQV_OnlyMainFileMatches }; QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { @@ -287,6 +300,7 @@ .Case("bind-root", PQV_BindRoot) .Case("print-matcher", PQV_PrintMatcher) .Case("traversal", PQV_Traversal) + .Case("match-scope", PQV_OnlyMainFileMatches) .Default(PQV_Invalid); if (VarStr.empty()) return new InvalidQuery("expected variable name"); @@ -307,6 +321,9 @@ case PQV_Traversal: Q = parseSetTraversalKind(&QuerySession::TK); break; + case PQV_OnlyMainFileMatches: + Q = parseMatchScope(&QuerySession::OnlyMainFileMatches); + break; case PQV_Invalid: llvm_unreachable("Invalid query kind"); } diff --git a/clang-tools-extra/clang-query/QuerySession.h b/clang-tools-extra/clang-query/QuerySession.h --- a/clang-tools-extra/clang-query/QuerySession.h +++ b/clang-tools-extra/clang-query/QuerySession.h @@ -26,7 +26,8 @@ QuerySession(llvm::ArrayRef> ASTs) : ASTs(ASTs), PrintOutput(false), DiagOutput(true), DetailedASTOutput(false), BindRoot(true), PrintMatcher(false), - Terminate(false), TK(TK_AsIs) {} + OnlyMainFileMatches(false), Terminate(false), HeaderModeWarnOnce(true), + TK(TK_AsIs) {} llvm::ArrayRef> ASTs; @@ -36,8 +37,12 @@ bool BindRoot; bool PrintMatcher; + bool OnlyMainFileMatches; bool Terminate; + // Use this to print the set match-scope help once. + bool HeaderModeWarnOnce; + TraversalKind TK; llvm::StringMap NamedValues; }; diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -57,7 +57,8 @@ Improvements to clang-query --------------------------- -The improvements are... + - Added a option `set match-scope` to control displaying matches that occur + in header files. Improvements to clang-rename ---------------------------- diff --git a/clang-tools-extra/test/clang-query/Inputs/foo.h b/clang-tools-extra/test/clang-query/Inputs/foo.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-query/Inputs/foo.h @@ -0,0 +1,7 @@ +#ifndef INPUTS_FOO_HH +#define INPUTS_FOO_HH + +void foo(); +void bar(); + +#endif // INPUTS_FOO_HH diff --git a/clang-tools-extra/test/clang-query/headers.cpp b/clang-tools-extra/test/clang-query/headers.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-query/headers.cpp @@ -0,0 +1,55 @@ +// RUN: clang-query -c "set match-scope include-headers" -c "match functionDecl(hasName('foo'))" %s -- | FileCheck %s --check-prefix=CHECK-ALL-FOO +// RUN: clang-query -c "set match-scope main-file-only" -c "match functionDecl(hasName('foo'))" %s -- | FileCheck %s --check-prefix=CHECK-MAIN-FOO +// RUN: clang-query -c "set match-scope include-headers" -c "match functionDecl(hasName('bar'))" %s -- | FileCheck %s --check-prefix=CHECK-ALL-BAR +// RUN: clang-query -c "set match-scope main-file-only" -c "match functionDecl(hasName('bar'))" %s -- | FileCheck %s --check-prefix=CHECK-MAIN-BAR +// RUN: clang-query -c "set match-scope include-headers" -c "match functionDecl()" %s -- | FileCheck %s --check-prefix=CHECK-ALL-ANY +// RUN: clang-query -c "set match-scope main-file-only" -c "match functionDecl()" %s -- | FileCheck %s --check-prefix=CHECK-MAIN-ANY + +// Test to ensure default behavious only includes main file matches. +// RUN: clang-query -c "match functionDecl()" %s -- | FileCheck %s --check-prefix=CHECK-ALL-ANY + +// Test to ensure the warnings about selecting match-scope is only shown once +// RUN: clang-query -c "set match-scope include-headers" -c "match functionDecl()" -c "match functionDecl()" %s | FileCheck %s --check-prefix=CHECK-SINGLE-WARN-ALL +// RUN: clang-query -c "set match-scope main-file-only" -c "match functionDecl()" -c "match functionDecl()" %s | FileCheck %s --check-prefix=CHECK-SINGLE-WARN-MAIN + +#include "Inputs/foo.h" + +void foo() {} + +// CHECK-ALL-FOO: /Inputs/foo.h:4:1: note: "root" binds here +// CHECK-ALL-FOO: /headers.cpp:17:1: note: "root" binds here +// CHECK-ALL-FOO: 2 matches. +// CHECK-ALL-FOO-NEXT: Included 1 match from header files +// CHECK-ALL-FOO-NEXT: Use 'set match-mode main-file-only' to hide them. + +// CHECK-MAIN-FOO: /headers.cpp:17:1: note: "root" binds here +// CHECK-MAIN-FOO: 1 match. +// CHECK-MAIN-FOO-Next: Skipped 1 match from header files. +// CHECK-MAIN-FOO-Next: Use 'set match-mode include-headers' to display them. + +// CHECK-ALL-BAR: /Inputs/foo.h:5:1: note: "root" binds here +// CHECK-ALL-BAR: 1 match. +// CHECK-ALL-BAR-NEXT: Included 1 match from header files +// CHECK-ALL-BAR-NEXT: Use 'set match-mode main-file-only' to hide them. + +// CHECK-MAIN-BAR: 0 matches. +// CHECK-MAIN-BAR-Next: Skipped 1 match from header files. +// CHECK-MAIN-BAR-Next: Use 'set match-mode include-headers' to display them. + +// CHECK-ALL-ANY: /Inputs/foo.h:4:1: note: "root" binds here +// CHECK-ALL-ANY: /Inputs/foo.h:5:1: note: "root" binds here +// CHECK-ALL-ANY: /headers.cpp:17:1: note: "root" binds here +// CHECK-ALL-ANY: 3 matches. +// CHECK-ALL-ANY-NEXT: Included 2 matches from header files +// CHECK-ALL-ANY-NEXT: Use 'set match-mode main-file-only' to hide them. + +// CHECK-MAIN-ANY: /headers.cpp:17:1: note: "root" binds here +// CHECK-MAIN-ANY: 1 match. +// CHECK-MAIN-ANY-Next: Skipped 2 matches from header files. +// CHECK-MAIN-ANY-Next: Use 'set match-mode include-headers' to display them. + +// CHECK-SINGLE-WARN-ALL: Use 'set match-mode main-file-only' to hide them. +// CHECK-SINGLE-WARN-ALL-NOT: Use 'set match-mode main-file-only' to hide them. + +// CHECK-SINGLE-WARN-MAIN: Use 'set match-mode include-headers' to display them. +// CHECK-SINGLE-WARN-MAIN-NOT: Use 'set match-mode include-headers' to display them.