diff --git a/clang-tools-extra/clangd/ASTMatchers.h b/clang-tools-extra/clangd/ASTMatchers.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/ASTMatchers.h @@ -0,0 +1,68 @@ +//===--- ASTMatchers.h - Query the current file with matchers ----*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// `#pragma clang query` lets users run AST matchers against the current file. +// It is named after the clang-query tool and serves a similar purpose. +// +// Examples: +// // Marks all function parameters +// #pragma clang query parmVarDecl() +// +// // Marks the types of function parameters +// #pragma clang query parmVarDecl(hasTypeLoc().bind("type")) +// +// The results are exposed in two ways: +// - matches are shown as warning diagnostics +// - find-references on the pragma will show the matches +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMATCHERS_H + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang::clangd { + +/// A parsed `#pragma clang query ` directive. +/// These define AST matchers which can be evaluated against the document. +struct MatcherPragma { + /// The compiled matcher. + /// Always valid: in case of errors we produce no MatcherPragma at all. + ast_matchers::dynamic::DynTypedMatcher Matcher; + /// Closed interval of (1-based) line numbers covered by the pragma. + std::pair LineRange; + + /// Find ranges of code in the matcher matches, along with their bound names. + std::vector> + match(ASTContext &) const; + + // Registers a handler to parse `#pragma clang query` directives. + // + // The AST matchers are written into *Out, if provided. + // Problems with the matcher specs are reported as diagnostics. + static void parse(std::vector *Out, CompilerInstance &); + + // Issues diagnostics marking the locations of all matchers in the list. + static void diagnose(llvm::ArrayRef, ASTContext &, + DiagnosticsEngine &); +}; + +} // namespace clang::clangd + +#endif diff --git a/clang-tools-extra/clangd/ASTMatchers.cpp b/clang-tools-extra/clangd/ASTMatchers.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/ASTMatchers.cpp @@ -0,0 +1,242 @@ +//===--- ASTMatchers.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTMatchers.h" +#include "SourceCode.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/ASTMatchers/Dynamic/Diagnostics.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Pragma.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include + +namespace clang::clangd { +namespace { + +// The matcher parser reads from a simple buffer (StringRef). +// But we want to report errors in terms of SourceLocation of the original file. +// This struct gathers the text, but retains enough information to translate +// offsets into it back into SourceLocations. +struct TextFromPragmaTokens { + std::string Text; + std::vector> Mapping; + + TextFromPragmaTokens(Preprocessor &PP, SourceLocation Initial) { + Mapping.emplace_back(0, Initial); + clang::Token Tok; + while (1) { + PP.LexUnexpandedToken(Tok); + if (Tok.is(tok::eod)) + break; + if (Tok.is(tok::code_completion)) { + PP.setCodeCompletionReached(); + break; + } + Text.push_back(' '); + Mapping.emplace_back(Text.size(), Tok.getLocation()); + Text.append(PP.getSourceManager().getCharacterData(Tok.getLocation()), + Tok.getLength()); + } + } + + // Returns the location corresponding to an offset in the buffer. + SourceLocation loc(unsigned Offset) const { + auto Result = prevTokenImpl(Offset); + return Result.first.getLocWithOffset(Result.second); + } + + // Returns the location of the last token <= Offset. + SourceLocation prevToken(unsigned Offset) const { + return prevTokenImpl(Offset).first; + } + + /// Returns the location of the last token. + SourceLocation end() const { return Mapping.back().second; } + +private: + // Returns the token that Offset is part of, and the position with it. + std::pair prevTokenImpl(unsigned Offset) const { + // Text: " tok1 tok2" + // Markers: ^^ ^ + if (Offset == 0) + return {Mapping.begin()->second, 0}; + auto Entry = std::prev( + std::partition_point(Mapping.begin(), Mapping.end(), [&](auto &Entry) { + return Entry.first <= Offset; + })); + return {Entry->second, Offset - Entry->first}; + } +}; + +class ASTMatcherParser : public PragmaHandler { + std::vector *Out; + CompilerInstance &Clang; + unsigned DiagMatcherProblem; + +public: + ASTMatcherParser(std::vector *Out, CompilerInstance &Clang) + : PragmaHandler("query"), Out(Out), Clang(Clang), + DiagMatcherProblem(Clang.getDiagnostics().getCustomDiagID( + DiagnosticsEngine::Error, "Bad matcher: %0")) {} + + void HandlePragma(Preprocessor &PP, PragmaIntroducer PI, + Token &FirstToken) override { + auto &SM = PP.getSourceManager(); + // clangd only respects main-file pragmas (completion+preamble+main parse) + if (!isInsideMainFile(PI.Loc, SM)) + return; + TextFromPragmaTokens Text(PP, FirstToken.getLocation()); + if (PP.isCodeCompletionReached()) + return handleCompletion(Text.Text); + if (!Out) + return; + + ast_matchers::dynamic::Diagnostics ParserDiags; + llvm::StringRef MatcherText = Text.Text; + bool HasBinds = MatcherText.contains(". bind ("); + if (auto Matcher = ast_matchers::dynamic::Parser::parseMatcherExpression( + MatcherText, &ParserDiags)) { + // Add "root" binding if there are no binds. + if (!HasBinds) { + std::string ID = + "query:" + std::to_string(SM.getSpellingLineNumber(PI.Loc)); + if (auto Bound = Matcher->tryBind(ID)) + Matcher = std::move(Bound); + } + Out->push_back( + MatcherPragma{std::move(*Matcher), + std::make_pair(SM.getSpellingLineNumber(PI.Loc), + SM.getSpellingLineNumber(Text.end()))}); + } + for (const auto &ParserError : ParserDiags.errors()) { + for (const auto &Message : ParserError.Messages) { + std::string Msg; + { + llvm::raw_string_ostream OS(Msg); + // FIXME: Only show the message of diag in question! + ParserDiags.printToStream(OS); + } + SourceRange Range(Text.loc(Message.Range.Start.Column - 1), + Text.prevToken(Message.Range.End.Column - 1)); + Clang.getDiagnostics().Report(Range.getBegin(), DiagMatcherProblem) + << Range << Msg; + } + } + } + + void handleCompletion(llvm::StringRef Text) { + std::vector Results; + auto Copy = [&](llvm::StringRef Text) { + return Clang.getCodeCompletionConsumer().getAllocator().CopyString(Text); + }; + + for (auto &Completion : + ast_matchers::dynamic::Parser::completeExpression(Text, Text.size())) { + clang::CodeCompletionBuilder Builder( + Clang.getCodeCompletionConsumer().getAllocator(), + Clang.getCodeCompletionConsumer().getCodeCompletionTUInfo()); + auto [Return, Signature] = + llvm::StringRef(Completion.MatcherDecl).split(' '); + // The plain Completion.TypedText is a prefix string like "varDecl(". + // This is not ideal for LSP, parse the "MatcherDecl" string instead. + + // Matcher varDecl(Matcher...) + if (Return.starts_with("Matcher<") && Return.ends_with(">") && + !Signature.empty()) { + Builder.AddResultTypeChunk(Copy(Return)); + if (Signature.ends_with(")")) { + auto [Name, Args] = Signature.drop_back().split("("); + Builder.AddTypedTextChunk(Copy(Name)); + Builder.AddChunk(CodeCompletionString::CK_LeftParen, "("); + // FIXME: we could add individual arg placeholders. + Builder.AddPlaceholderChunk(Copy(Args)); + Builder.AddChunk(CodeCompletionString::CK_RightParen, ")"); + } else { + Builder.AddTypedTextChunk(Copy(Completion.TypedText)); + } + } else if (Completion.MatcherDecl == "bind") { + Builder.AddTypedTextChunk("bind"); + Builder.AddChunk(CodeCompletionString::CK_LeftParen, "("); + Builder.AddChunk(CodeCompletionString::CK_Text, "\""); + Builder.AddPlaceholderChunk("name"); + Builder.AddChunk(CodeCompletionString::CK_Text, "\""); + Builder.AddChunk(CodeCompletionString::CK_RightParen, ")"); + } else { + // Generic fallback, we don't expect to get here much. + Builder.AddTypedTextChunk(Copy(Completion.TypedText)); + } + Results.push_back(Builder.TakeString()); + } + + Clang.getCodeCompletionConsumer().ProcessCodeCompleteResults( + Clang.getSema(), CodeCompletionContext::CCC_Other, Results.data(), + Results.size()); + } +}; + +} // namespace + +void MatcherPragma::parse(std::vector *Out, + CompilerInstance &Clang) { + Clang.getPreprocessor().AddPragmaHandler("clang", + new ASTMatcherParser(Out, Clang)); +} + +std::vector> +MatcherPragma::match(ASTContext &Ctx) const { + std::vector> Result; + for (const auto &Match : ast_matchers::matchDynamic(Matcher, Ctx)) + for (const auto &KV : Match.getMap()) + Result.emplace_back(KV.first, KV.second.getSourceRange()); + return Result; +} + +void MatcherPragma::diagnose(llvm::ArrayRef Matchers, + ASTContext &AST, DiagnosticsEngine &Diags) { + ast_matchers::MatchFinder Finder; + class ReportMatch : public ast_matchers::MatchFinder::MatchCallback { + DiagnosticsEngine &Diags; + unsigned DiagBindsHere; + + public: + ReportMatch(DiagnosticsEngine &Diags) + : Diags(Diags), + // FIXME: somehow propagate a sensible LSP severity/source/code. + // For now, this is a warning, as we expect notes to have parents. + DiagBindsHere(Diags.getCustomDiagID(DiagnosticsEngine::Warning, + "%0 matches")) {} + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + for (const auto &E : Result.Nodes.getMap()) { + E.second.dump(llvm::errs() << E.first << ": ", *Result.Context); + Diags.Report(E.second.getSourceRange().getBegin(), DiagBindsHere) + << E.second.getSourceRange() << E.first; + } + } + } Reporter(Diags); + + for (const auto &M : Matchers) + Finder.addDynamicMatcher(M.Matcher, &Reporter); + Finder.matchAST(AST); +} + +} // namespace clang::clangd diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -63,6 +63,7 @@ add_clang_library(clangDaemon AST.cpp + ASTMatchers.cpp ASTSignals.cpp ClangdLSPServer.cpp ClangdServer.cpp @@ -160,6 +161,7 @@ PRIVATE clangAST clangASTMatchers + clangDynamicASTMatchers clangBasic clangDriver clangFormat diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -42,6 +42,7 @@ #include "support/Trace.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" @@ -1362,6 +1363,7 @@ Input.FileName); return false; } + MatcherPragma::parse(/*Matchers=*/nullptr, *Clang); // Macros can be defined within the preamble region of the main file. // They don't fall nicely into our index/Sema dichotomy: // - they're not indexed for completion (they're not available across files) diff --git a/clang-tools-extra/clangd/Compiler.cpp b/clang-tools-extra/clangd/Compiler.cpp --- a/clang-tools-extra/clangd/Compiler.cpp +++ b/clang-tools-extra/clangd/Compiler.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Compiler.h" +#include "ASTMatchers.h" #include "support/Logger.h" #include "clang/Basic/TargetInfo.h" #include "clang/Frontend/CompilerInvocation.h" diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp --- a/clang-tools-extra/clangd/Diagnostics.cpp +++ b/clang-tools-extra/clangd/Diagnostics.cpp @@ -584,6 +584,8 @@ for (auto &Fix : Diag.Fixes) CleanMessage(Fix.Message); } + } else { + Diag.Source = Diag::Clangd; } setTags(Diag); } diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -20,6 +20,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PARSEDAST_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PARSEDAST_H +#include "ASTMatchers.h" #include "CollectMacros.h" #include "Compiler.h" #include "Diagnostics.h" @@ -104,6 +105,8 @@ const MainFileMacros &getMacros() const; /// Gets all pragma marks in the main file. const std::vector &getMarks() const; + /// Gets all AST matcher pragmas in the main file. + llvm::ArrayRef getMatchers() const { return Matchers; } /// Tokens recorded while parsing the main file. /// (!) does not have tokens from the preamble. const syntax::TokenBuffer &getTokens() const { return Tokens; } @@ -131,6 +134,7 @@ std::unique_ptr Clang, std::unique_ptr Action, syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector Marks, + std::vector Matchers, std::vector LocalTopLevelDecls, std::optional> Diags, IncludeStructure Includes, CanonicalIncludes CanonIncludes); @@ -157,6 +161,8 @@ MainFileMacros Macros; // Pragma marks in the main file. std::vector Marks; + // #pragma clang query directives in the main file. + std::vector Matchers; // Data, stored after parsing. std::nullopt if AST was built with a stale // preamble. std::optional> Diags; diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -12,6 +12,7 @@ #include "../clang-tidy/ClangTidyModule.h" #include "../clang-tidy/ClangTidyModuleRegistry.h" #include "AST.h" +#include "ASTMatchers.h" #include "Compiler.h" #include "Config.h" #include "Diagnostics.h" @@ -50,8 +51,10 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include #include #include +#include #include // Force the linker to link in Clang-tidy modules. @@ -383,12 +386,14 @@ // dropped later on to not pay for extra latency by processing them. DiagnosticConsumer *DiagConsumer = &ASTDiags; IgnoreDiagnostics DropDiags; + std::vector Matchers; if (Preamble) { Patch = PreamblePatch::createFullPatch(Filename, Inputs, *Preamble); Patch->apply(*CI); PreserveDiags = Patch->preserveDiagnostics(); if (!PreserveDiags) DiagConsumer = &DropDiags; + Matchers = Preamble->Matchers; } auto Clang = prepareCompilerInstance( std::move(CI), PreamblePCH, @@ -457,6 +462,7 @@ const FileEntry *MainFE = SM.getFileEntryForID(SM.getMainFileID()); Clang->getPreprocessor().getHeaderSearchInfo().MarkFileIncludeOnce(MainFE); } + MatcherPragma::parse(&Matchers, *Clang); // Set up ClangTidy. Must happen after BeginSourceFile() so ASTContext exists. // Clang-tidy has some limitations to ensure reasonable performance: @@ -657,6 +663,8 @@ trace::Span Tracer("ClangTidyMatch"); CTFinder.matchAST(Clang->getASTContext()); } + MatcherPragma::diagnose(Matchers, Clang->getASTContext(), + Clang->getDiagnostics()); // XXX: This is messy: clang-tidy checks flush some diagnostics at EOF. // However Action->EndSourceFile() would destroy the ASTContext! @@ -684,9 +692,9 @@ } ParsedAST Result(Filename, Inputs.Version, std::move(Preamble), std::move(Clang), std::move(Action), std::move(Tokens), - std::move(Macros), std::move(Marks), std::move(ParsedDecls), - std::move(Diags), std::move(Includes), - std::move(CanonIncludes)); + std::move(Macros), std::move(Marks), std::move(Matchers), + std::move(ParsedDecls), std::move(Diags), + std::move(Includes), std::move(CanonIncludes)); if (Result.Diags) llvm::move(issueIncludeCleanerDiagnostics(Result, Inputs.Contents), std::back_inserter(*Result.Diags)); @@ -783,13 +791,15 @@ std::unique_ptr Action, syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector Marks, + std::vector Matchers, std::vector LocalTopLevelDecls, std::optional> Diags, IncludeStructure Includes, CanonicalIncludes CanonIncludes) : TUPath(TUPath), Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Tokens(std::move(Tokens)), Macros(std::move(Macros)), - Marks(std::move(Marks)), Diags(std::move(Diags)), + Marks(std::move(Marks)), Matchers(std::move(Matchers)), + Diags(std::move(Diags)), LocalTopLevelDecls(std::move(LocalTopLevelDecls)), Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) { Resolver = std::make_unique(getASTContext()); diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h --- a/clang-tools-extra/clangd/Preamble.h +++ b/clang-tools-extra/clangd/Preamble.h @@ -22,6 +22,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PREAMBLE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PREAMBLE_H +#include "ASTMatchers.h" #include "CollectMacros.h" #include "Compiler.h" #include "Diagnostics.h" @@ -71,6 +72,8 @@ MainFileMacros Macros; // Pragma marks defined in the preamble section of the main file. std::vector Marks; + // Matcher pragmas defined in the preamble section of the main file. + std::vector Matchers; // Cache of FS operations performed when building the preamble. // When reusing a preamble, this cache can be consumed to save IO. std::unique_ptr StatCache; diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Preamble.h" +#include "ASTMatchers.h" #include "CollectMacros.h" #include "Compiler.h" #include "Config.h" @@ -89,6 +90,7 @@ MainFileMacros takeMacros() { return std::move(Macros); } std::vector takeMarks() { return std::move(Marks); } + std::vector takeMatchers() { return std::move(Matchers); } include_cleaner::PragmaIncludes takePragmaIncludes() { return std::move(Pragmas); @@ -136,6 +138,7 @@ PP = &CI.getPreprocessor(); Includes.collect(CI); Pragmas.record(CI); + MatcherPragma::parse(&Matchers, CI); if (BeforeExecuteCallback) BeforeExecuteCallback(CI); } @@ -208,6 +211,7 @@ include_cleaner::PragmaIncludes Pragmas; MainFileMacros Macros; std::vector Marks; + std::vector Matchers; bool IsMainFileIncludeGuarded = false; std::unique_ptr IWYUHandler = nullptr; const clang::LangOptions *LangOpts = nullptr; @@ -679,6 +683,7 @@ Result->Pragmas = CapturedInfo.takePragmaIncludes(); Result->Macros = CapturedInfo.takeMacros(); Result->Marks = CapturedInfo.takeMarks(); + Result->Matchers = CapturedInfo.takeMatchers(); Result->CanonIncludes = CapturedInfo.takeCanonicalIncludes(); Result->StatCache = std::move(StatCache); Result->MainIsIncludeGuarded = CapturedInfo.isMainFileIncludeGuarded(); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -67,6 +67,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include #include namespace clang { @@ -1373,6 +1374,35 @@ return std::nullopt; return Results; } + +std::optional +maybeFindMatcherReferences(ParsedAST &AST, Position Pos, + URIForFile URIMainFile) { + const auto &Matchers = AST.getMatchers(); + auto MatcherOnLine = llvm::find_if(Matchers, [&Pos](const MatcherPragma &M) { + return M.LineRange.first <= unsigned(Pos.line + 1) && + unsigned(Pos.line + 1) <= M.LineRange.second; + }); + if (MatcherOnLine == Matchers.end()) + return std::nullopt; + + ReferencesResult Results; + for (const auto &Match : MatcherOnLine->match(AST.getASTContext())) { + auto Range = AST.getTokens().spelledForExpanded( + AST.getTokens().expandedTokens(Match.second)); + if (!Range || Range->empty()) + continue; + ReferencesResult::Reference Ref; + Ref.Loc.uri = URIMainFile; + Ref.Loc.range.start = + sourceLocToPosition(AST.getSourceManager(), Range->front().location()); + Ref.Loc.range.end = sourceLocToPosition(AST.getSourceManager(), + Range->back().endLocation()); + Results.References.push_back(std::move(Ref)); + } + return Results; +} + } // namespace ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, @@ -1387,10 +1417,12 @@ return {}; } - const auto IncludeReferences = - maybeFindIncludeReferences(AST, Pos, URIMainFile); - if (IncludeReferences) + if (auto IncludeReferences = + maybeFindIncludeReferences(AST, Pos, URIMainFile)) return *IncludeReferences; + if (auto MatcherReferences = + maybeFindMatcherReferences(AST, Pos, URIMainFile)) + return *MatcherReferences; llvm::DenseSet IDsToQuery, OverriddenMethods; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3968,6 +3968,16 @@ } } +TEST(CompletionTest, MatcherPragma) { + Annotations Code(R"cpp( + #pragma clang query parmVarDecl(hasType^) + )cpp"); + auto TU = TestTU::withCode(Code.code()); + + ASSERT_THAT(completions(TU, Code.point()).Completions, + ElementsAre(named("hasType"), named("hasTypeLoc"))); +} + TEST(SignatureHelp, DocFormat) { Annotations Code(R"cpp( // Comment `with` markup. diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -361,6 +361,40 @@ diagSource(Diag::ClangTidy), diagName("llvm-include-order"))))); } +TEST(DiagnosticsTest, Matchers) { + Annotations Test(R"cpp( + ; + #pragma clang query parmVarDecl() + void foo([[int x]], [[int y]]); + )cpp"); + auto TU = TestTU::withCode(Test.code()); + EXPECT_THAT(*TU.build().getDiagnostics(), + ElementsAre(Diag(Test.ranges()[0], "query:3 matches"), + Diag(Test.ranges()[1], "query:3 matches"))); +} + +TEST(DiagnosticsTest, MatchersPreamble) { + Annotations Test(R"cpp( + #pragma clang query parmVarDecl().bind("parm") + void foo([[int x]]); + )cpp"); + auto TU = TestTU::withCode(Test.code()); + EXPECT_THAT(*TU.build().getDiagnostics(), + ElementsAre(Diag(Test.range(), "parm matches"))); +} + +TEST(DiagnosticsTest, MatchersError) { + Annotations Test(R"cpp( + #pragma clang query [[pmVrDcl]]() // error-ok + void foo(int x); + )cpp"); + auto TU = TestTU::withCode(Test.code()); + EXPECT_THAT( + *TU.build().getDiagnostics(), + ElementsAre( + Diag(Test.range(), "Bad matcher: 1:2: Matcher not found: pmVrDcl"))); +} + TEST(DiagnosticTest, TemplatesInHeaders) { // Diagnostics from templates defined in headers are placed at the expansion. Annotations Main(R"cpp( diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -2341,6 +2341,14 @@ } } +TEST(FindReferences, Matchers) { + checkFindRefs(R"cpp( + #pragma clang query \ + ^parmVarDecl() + void foo([[int x]], [[int y]]); + )cpp"); +} + TEST(FindReferences, NeedsIndexForSymbols) { const char *Header = "int foo();"; Annotations Main("int main() { [[f^oo]](); }");