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 @@ -98,17 +98,23 @@ // LSP needs a single range. Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { auto &M = D.getSourceManager(); + auto PatchedRange = [&M](CharSourceRange &R) { + // FIXME: Should we handle all presumed locations? + R.setBegin(translatePreamblePatchLocation(R.getBegin(), M)); + R.setEnd(translatePreamblePatchLocation(R.getEnd(), M)); + return R; + }; auto Loc = M.getFileLoc(D.getLocation()); for (const auto &CR : D.getRanges()) { auto R = Lexer::makeFileCharRange(CR, M, L); if (locationInRange(Loc, R, M)) - return halfOpenToRange(M, R); + return halfOpenToRange(M, PatchedRange(R)); } // The range may be given as a fixit hint instead. for (const auto &F : D.getFixItHints()) { auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); if (locationInRange(Loc, R, M)) - return halfOpenToRange(M, R); + return halfOpenToRange(M, PatchedRange(R)); } // If the token at the location is not a comment, we use the token. // If we can't get the token at the location, fall back to using the location @@ -117,7 +123,7 @@ if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) { R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc()); } - return halfOpenToRange(M, R); + return halfOpenToRange(M, PatchedRange(R)); } // Try to find a location in the main-file to report the diagnostic D. @@ -213,13 +219,6 @@ return true; } -bool isInsideMainFile(const clang::Diagnostic &D) { - if (!D.hasSourceManager()) - return false; - - return clangd::isInsideMainFile(D.getLocation(), D.getSourceManager()); -} - bool isNote(DiagnosticsEngine::Level L) { return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; } @@ -713,11 +712,14 @@ auto FillDiagBase = [&](DiagBase &D) { fillNonLocationData(DiagLevel, Info, D); - D.InsideMainFile = isInsideMainFile(Info); + auto PLoc = translatePreamblePatchLocation(Info.getLocation(), SM); + D.InsideMainFile = isInsideMainFile(PLoc, SM); D.Range = diagnosticRange(Info, *LangOpts); - D.File = std::string(SM.getFilename(Info.getLocation())); - D.AbsFile = getCanonicalPath( - SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM); + auto FID = SM.getFileID(Info.getLocation()); + if (auto *FE = SM.getFileEntryForID(FID)) { + D.File = FE->getName().str(); + D.AbsFile = getCanonicalPath(FE, SM); + } D.ID = Info.getID(); return D; }; 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 @@ -674,9 +674,10 @@ if (PreserveDiags) { Diags = CompilerInvocationDiags; // Add diagnostics from the preamble, if any. - if (Preamble) - Diags->insert(Diags->end(), Preamble->Diags.begin(), - Preamble->Diags.end()); + if (Preamble) { + auto PDiags = Patch->patchedDiags(); + Diags->insert(Diags->end(), PDiags.begin(), PDiags.end()); + } // Finally, add diagnostics coming from the AST. { std::vector D = ASTDiags.take(&*CTContext); 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 @@ -155,7 +155,12 @@ llvm::StringRef text() const { return PatchContents; } /// Whether diagnostics generated using this patch are trustable. - bool preserveDiagnostics() const { return PatchContents.empty(); } + bool preserveDiagnostics() const; + + /// Returns diag locations for Modified contents, only contains diags attached + /// to an #include or #define directive. + std::vector patchedDiags() const; + static constexpr llvm::StringLiteral HeaderName = "__preamble_patch__.h"; private: static PreamblePatch create(llvm::StringRef FileName, @@ -166,17 +171,14 @@ PreamblePatch() = default; std::string PatchContents; std::string PatchFileName; - /// Includes that are present in both \p Baseline and \p Modified. Used for - /// patching includes of baseline preamble. + // Includes that are present in both \p Baseline and \p Modified. Used for + // patching includes of baseline preamble. std::vector PreambleIncludes; + // Diags that were attached to an #include or a #define directive. + std::vector PatchedDiags; PreambleBounds ModifiedBounds = {0, false}; }; -/// Translates locations inside preamble patch to their main-file equivalent -/// using presumed locations. Returns \p Loc if it isn't inside preamble patch. -SourceLocation translatePreamblePatchLocation(SourceLocation Loc, - const SourceManager &SM); - } // namespace clangd } // namespace clang 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 @@ -9,6 +9,7 @@ #include "Preamble.h" #include "Compiler.h" #include "Config.h" +#include "Diagnostics.h" #include "Headers.h" #include "SourceCode.h" #include "clang-include-cleaner/Record.h" @@ -35,6 +36,8 @@ #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" @@ -43,7 +46,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" -#include +#include #include #include #include @@ -53,7 +56,6 @@ namespace clang { namespace clangd { namespace { -constexpr llvm::StringLiteral PreamblePatchHeaderName = "__preamble_patch__.h"; bool compileCommandsAreEqual(const tooling::CompileCommand &LHS, const tooling::CompileCommand &RHS) { @@ -213,6 +215,9 @@ // Full text that's representing the directive, including the `#`. std::string Text; unsigned Offset; + tok::PPKeywordKind Directive; + // Name of the macro being defined in the case of a #define directive. + std::string MacroName; bool operator==(const TextualPPDirective &RHS) const { return std::tie(DirectiveLine, Offset, Text) == @@ -283,6 +288,8 @@ return; TextualDirectives.emplace_back(); TextualPPDirective &TD = TextualDirectives.back(); + TD.Directive = tok::pp_define; + TD.MacroName = MacroNameTok.getIdentifierInfo()->getName().str(); const auto *MI = MD->getMacroInfo(); TD.Text = @@ -376,12 +383,6 @@ llvm_unreachable("not an include directive"); } -// Checks whether \p FileName is a valid spelling of main file. -bool isMainFile(llvm::StringRef FileName, const SourceManager &SM) { - auto FE = SM.getFileManager().getFile(FileName); - return FE && *FE == SM.getFileEntryForID(SM.getMainFileID()); -} - // Accumulating wall time timer. Similar to llvm::Timer, but much cheaper, // it only tracks wall time. // Since this is a generic timer, We may want to move this to support/ if we @@ -467,6 +468,19 @@ WallTimer Timer; }; +// Represents an include as spelled in the source code, without taking resolved +// path or location into account. +struct IncludeKey { + tok::PPKeywordKind Directive; + llvm::StringRef Written; + + bool operator<(const IncludeKey &RHS) const { + return std::tie(Directive, Written) < std::tie(RHS.Directive, RHS.Written); + } + bool operator==(const IncludeKey &RHS) const { + return std::tie(Directive, Written) == std::tie(RHS.Directive, RHS.Written); + } +}; } // namespace std::shared_ptr @@ -614,6 +628,70 @@ } } +// Checks if all pointers in \p D are for the same line of the main file. +static bool diagReferencesMultipleLines(const Diag &D) { + int Line = D.Range.start.line; + if (D.Range.end.line != Line) + return true; + bool NotePointsToOutside = llvm::any_of(D.Notes, [&](const Note &N) { + return N.File == D.File && + (N.Range.start.line != Line || N.Range.end.line != Line); + }); + if (NotePointsToOutside) + return true; + bool FixPointsToOutside = llvm::any_of(D.Fixes, [&](const Fix &F) { + for (auto &E : F.Edits) + if (E.range.start.line != Line || E.range.end.line != Line) + return true; + return false; + }); + if (FixPointsToOutside) + return true; + return false; +} + +// Move all references in \p D to \p NewLine. This assumes none of the +// references to the main file are multi-line. +static Diag translateDiag(const Diag &D, int NewLine) { + assert(!diagReferencesMultipleLines(D)); + Diag Translated = D; + Translated.Range.start.line = Translated.Range.end.line = NewLine; + for (auto &N : Translated.Notes) { + if (N.File != D.File) + continue; + N.Range.start.line = N.Range.end.line = NewLine; + } + for (auto &F : Translated.Fixes) { + for (auto &E : F.Edits) + E.range.start.line = E.range.end.line = NewLine; + } + return Translated; +} + +static llvm::DenseMap> +mapDiagsToLines(llvm::ArrayRef Diags) { + llvm::DenseMap> LineToDiags; + for (auto &D : Diags) { + if (diagReferencesMultipleLines(D)) + continue; + LineToDiags[D.Range.start.line].emplace_back(&D); + } + return LineToDiags; +} + +static std::map +getExistingIncludes(const ScannedPreamble &BaselineScan, + llvm::ArrayRef MFI) { + std::map ExistingIncludes; + // There might be includes coming from disabled regions, record these for + // exclusion. note that we don't have resolved paths for those. + for (const auto &Inc : BaselineScan.Includes) + ExistingIncludes[{Inc.Directive, Inc.Written}] = nullptr; + for (const auto &Inc : MFI) + ExistingIncludes[{Inc.Directive, Inc.Written}] = &Inc; + return ExistingIncludes; +} + PreamblePatch PreamblePatch::create(llvm::StringRef FileName, const ParseInputs &Modified, const PreambleData &Baseline, @@ -655,7 +733,7 @@ // This shouldn't coincide with any real file name. llvm::SmallString<128> PatchName; llvm::sys::path::append(PatchName, llvm::sys::path::parent_path(FileName), - PreamblePatchHeaderName); + PreamblePatch::HeaderName); PP.PatchFileName = PatchName.str().str(); PP.ModifiedBounds = ModifiedScan->Bounds; @@ -667,26 +745,37 @@ escapeBackslashAndQuotes(FileName, Patch); Patch << "\"\n"; + // List of diagnostics associated to a particular line in the baseline. + auto LineToDiags = mapDiagsToLines(Baseline.Diags); + auto MoveDiagsBetweenLines = [&LineToDiags, &PP](int OldLine, int NewLine) { + auto DiagsForInclude = LineToDiags.find(OldLine); + if (DiagsForInclude == LineToDiags.end()) + return; + for (auto *D : DiagsForInclude->second) + PP.PatchedDiags.emplace_back(translateDiag(*D, NewLine)); + }; + // Map from an include to its line in the Modified contents. + std::map IncludeToPatchedLine; if (IncludesChanged && PatchType == PatchType::All) { // We are only interested in newly added includes, record the ones in // Baseline for exclusion. - llvm::DenseMap, - /*Resolved=*/llvm::StringRef> - ExistingIncludes; - for (const auto &Inc : Baseline.Includes.MainFileIncludes) - ExistingIncludes[{Inc.Directive, Inc.Written}] = Inc.Resolved; - // There might be includes coming from disabled regions, record these for - // exclusion too. note that we don't have resolved paths for those. - for (const auto &Inc : BaselineScan->Includes) - ExistingIncludes.try_emplace({Inc.Directive, Inc.Written}); + auto ExistingIncludes = + getExistingIncludes(*BaselineScan, Baseline.Includes.MainFileIncludes); // Calculate extra includes that needs to be inserted. for (auto &Inc : ModifiedScan->Includes) { auto It = ExistingIncludes.find({Inc.Directive, Inc.Written}); // Include already present in the baseline preamble. Set resolved path and // put into preamble includes. if (It != ExistingIncludes.end()) { - Inc.Resolved = It->second.str(); + if (It->second) { + // Copy everything from existing include, apart from the location. + Inc.Resolved = It->second->Resolved; + Inc.HeaderID = It->second->HeaderID; + Inc.BehindPragmaKeep = It->second->BehindPragmaKeep; + Inc.FileKind = It->second->FileKind; + } PP.PreambleIncludes.push_back(Inc); + IncludeToPatchedLine[It->first] = Inc.HashLine; continue; } // Include is new in the modified preamble. Inject it into the patch and @@ -696,8 +785,29 @@ Patch << llvm::formatv( "#{0} {1}\n", spellingForIncDirective(Inc.Directive), Inc.Written); } + } else { + // Copy include information from baseline preamble if nothing has changed. + PP.PreambleIncludes = Baseline.Includes.MainFileIncludes; } + // Patch locations for diagnostics on baseline includes. + for (const auto &Inc : Baseline.Includes.MainFileIncludes) { + // Use existing locations if includes haven't changed. + size_t NewLine = Inc.HashLine; + if (IncludesChanged) { + auto PatchedLine = + IncludeToPatchedLine.find({Inc.Directive, Inc.Written}); + // Ignore diags for deleted includes. + if (PatchedLine == IncludeToPatchedLine.end()) + continue; + NewLine = PatchedLine->second; + } + MoveDiagsBetweenLines(Inc.HashLine, NewLine); + } + + // Maps a directive from its spelling to its location in the Modified + // contents. + llvm::StringMap DirectiveToPatchedLine; if (DirectivesChanged) { // We need to patch all the directives, since they are order dependent. e.g: // #define BAR(X) NEW(X) // Newly introduced in Modified @@ -711,10 +821,29 @@ // reduce complexity. The former might cause problems because scanning is // imprecise and might pick directives from disabled regions. for (const auto &TD : ModifiedScan->TextualDirectives) { + // Introduce an #undef directive before #defines to suppress any + // re-definition warnings. + if (TD.Directive == tok::pp_define) + Patch << "#undef " << TD.MacroName << '\n'; Patch << "#line " << TD.DirectiveLine << '\n'; Patch << TD.Text << '\n'; + DirectiveToPatchedLine[TD.Text] = TD.DirectiveLine; } + } else { + // Take existing directives as-is, if they're the same. + for (const auto &TD : BaselineScan->TextualDirectives) + DirectiveToPatchedLine[TD.Text] = TD.DirectiveLine; + } + + // Patch locations for diagnositcs on baseline macros. + for (const auto &TD : BaselineScan->TextualDirectives) { + // Textual directive lines are 1-based. + auto NewLine = DirectiveToPatchedLine.find(TD.Text); + if (NewLine == DirectiveToPatchedLine.end()) + continue; + MoveDiagsBetweenLines(TD.DirectiveLine - 1, NewLine->second - 1); } + dlog("Created preamble patch: {0}", Patch.str()); Patch.flush(); return PP; @@ -756,28 +885,14 @@ PreamblePatch PP; PP.PreambleIncludes = Preamble.Includes.MainFileIncludes; PP.ModifiedBounds = Preamble.Preamble.getBounds(); + PP.PatchedDiags = Preamble.Diags; return PP; } -SourceLocation translatePreamblePatchLocation(SourceLocation Loc, - const SourceManager &SM) { - auto DefFile = SM.getFileID(Loc); - if (auto FE = SM.getFileEntryRefForID(DefFile)) { - auto IncludeLoc = SM.getIncludeLoc(DefFile); - // Preamble patch is included inside the builtin file. - if (IncludeLoc.isValid() && SM.isWrittenInBuiltinFile(IncludeLoc) && - FE->getName().endswith(PreamblePatchHeaderName)) { - auto Presumed = SM.getPresumedLoc(Loc); - // Check that line directive is pointing at main file. - if (Presumed.isValid() && Presumed.getFileID().isInvalid() && - isMainFile(Presumed.getFilename(), SM)) { - Loc = SM.translateLineCol(SM.getMainFileID(), Presumed.getLine(), - Presumed.getColumn()); - } - } - } - return Loc; +bool PreamblePatch::preserveDiagnostics() const { + return PatchContents.empty() || + Config::current().Diagnostics.Mode != Config::DiagnosticsMode::Strict; } - +std::vector PreamblePatch::patchedDiags() const { return PatchedDiags; } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -333,6 +333,10 @@ (isUppercase(Name[1]) || Name[1] == '_'); } +/// Translates locations inside preamble patch to their main-file equivalent +/// using presumed locations. Returns \p Loc if it isn't inside preamble patch. +SourceLocation translatePreamblePatchLocation(SourceLocation Loc, + const SourceManager &SM); } // namespace clangd } // namespace clang #endif diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -1219,5 +1219,30 @@ return SM.getBufferData(FID).startswith(ProtoHeaderComment); } +SourceLocation translatePreamblePatchLocation(SourceLocation Loc, + const SourceManager &SM) { + // Checks whether \p FileName is a valid spelling of main file. + auto IsMainFile = [](llvm::StringRef FileName, const SourceManager &SM) { + auto FE = SM.getFileManager().getFile(FileName); + return FE && *FE == SM.getFileEntryForID(SM.getMainFileID()); + }; + + auto DefFile = SM.getFileID(Loc); + if (auto FE = SM.getFileEntryRefForID(DefFile)) { + auto IncludeLoc = SM.getIncludeLoc(DefFile); + // Preamble patch is included inside the builtin file. + if (IncludeLoc.isValid() && SM.isWrittenInBuiltinFile(IncludeLoc) && + FE->getName().endswith(PreamblePatch::HeaderName)) { + auto Presumed = SM.getPresumedLoc(Loc); + // Check that line directive is pointing at main file. + if (Presumed.isValid() && Presumed.getFileID().isInvalid() && + IsMainFile(Presumed.getFilename(), SM)) { + Loc = SM.translateLineCol(SM.getMainFileID(), Presumed.getLine(), + Presumed.getColumn()); + } + } + } + return Loc; +} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp --- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp +++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp @@ -8,9 +8,13 @@ #include "Annotations.h" #include "Compiler.h" +#include "Config.h" +#include "Diagnostics.h" #include "Headers.h" #include "Hover.h" +#include "ParsedAST.h" #include "Preamble.h" +#include "Protocol.h" #include "SourceCode.h" #include "TestFS.h" #include "TestTU.h" @@ -18,10 +22,12 @@ #include "clang/Format/Format.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/PrecompiledPreamble.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Testing/Support/SupportHelpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include @@ -30,9 +36,14 @@ #include using testing::Contains; +using testing::ElementsAre; using testing::Field; +using testing::IsEmpty; using testing::Matcher; using testing::MatchesRegex; +using testing::Not; +using testing::UnorderedElementsAre; +using testing::UnorderedElementsAreArray; namespace clang { namespace clangd { @@ -190,9 +201,12 @@ Field(&Inclusion::Resolved, testPath("a.h"))))); } -std::optional createPatchedAST(llvm::StringRef Baseline, - llvm::StringRef Modified) { - auto BaselinePreamble = TestTU::withCode(Baseline).preamble(); +std::optional +createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified, + llvm::StringMap AdditionalFiles = {}) { + auto PreambleTU = TestTU::withCode(Baseline); + PreambleTU.AdditionalFiles = AdditionalFiles; + auto BaselinePreamble = PreambleTU.preamble(); if (!BaselinePreamble) { ADD_FAILURE() << "Failed to build baseline preamble"; return std::nullopt; @@ -201,6 +215,7 @@ IgnoreDiagnostics Diags; MockFS FS; auto TU = TestTU::withCode(Modified); + TU.AdditionalFiles = std::move(AdditionalFiles); auto CI = buildCompilerInvocation(TU.inputs(FS), Diags); if (!CI) { ADD_FAILURE() << "Failed to build compiler invocation"; @@ -236,6 +251,7 @@ #define BAR [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" +#undef BAR #line 2 #define BAR )cpp", @@ -247,6 +263,7 @@ [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" +#undef BAR #line 2 #define BAR )cpp", @@ -258,6 +275,7 @@ BAR [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" +#undef BAR #line 3 #define BAR )cpp", @@ -290,8 +308,10 @@ )cpp"); llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp" +#undef BAR #line 2 #define BAR\(X, Y\) X Y +#undef BAR #line 3 #define BAR\(X\) X )cpp"); @@ -565,6 +585,141 @@ TU.inputs(FS), *BaselinePreamble); EXPECT_TRUE(PP.text().empty()); } + +std::vector mainFileDiagRanges(const ParsedAST &AST) { + std::vector Result; + auto AddRangeIfInMainfile = [&Result](const DiagBase &DB) { + if (DB.InsideMainFile) + Result.emplace_back(DB.Range); + }; + for (auto &D : *AST.getDiagnostics()) { + AddRangeIfInMainfile(D); + for (auto &N : D.Notes) + AddRangeIfInMainfile(N); + } + return Result; +} + +TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) { + Config Cfg; + Cfg.Diagnostics.Mode = Config::DiagnosticsMode::Fast; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + llvm::StringLiteral BaselinePreamble = "#define FOO\n"; + { + // Check with removals from preamble. + Annotations Code("[[x]];/* error-ok */"); + auto AST = createPatchedAST(BaselinePreamble, Code.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + } + { + // Check with additions to preamble. + Annotations Code( + (BaselinePreamble + "#define BAR\n[[x]];/* error-ok */").str()); + auto AST = createPatchedAST(BaselinePreamble, Code.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + } +} + +TEST(PreamblePatch, DiagnosticsToPreamble) { + Config Cfg; + Cfg.Diagnostics.Mode = Config::DiagnosticsMode::Fast; + Cfg.Diagnostics.UnusedIncludes = Config::UnusedIncludesPolicy::Strict; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + llvm::StringMap AdditionalFiles; + AdditionalFiles["foo.h"] = "#pragma once"; + AdditionalFiles["bar.h"] = "#pragma once"; + llvm::StringLiteral BaselinePreamble("#define FOO 1\n[[#include \"foo.h\"]]"); + { + // Check with removals from preamble. + Annotations NewCode("[[# include \"foo.h\"]]"); + auto AST = createPatchedAST(Annotations(BaselinePreamble).code(), + NewCode.code(), AdditionalFiles); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } + { + // Check with additions to preamble. + Annotations NewCode(("[[#include \"bar.h\"]]\n" + BaselinePreamble).str()); + auto AST = createPatchedAST(Annotations(BaselinePreamble).code(), + NewCode.code(), AdditionalFiles); + EXPECT_THAT(mainFileDiagRanges(*AST), + UnorderedElementsAreArray(NewCode.ranges())); + } + { + llvm::StringLiteral BaselinePreamble = "#define [[FOO]] 1\n"; + // Check ranges for notes. + Annotations NewCode(("#define BARXYZ 1\n" + BaselinePreamble + + "void foo();\n#define [[FOO]] 2") + .str()); + auto AST = createPatchedAST(Annotations(BaselinePreamble).code(), + NewCode.code(), AdditionalFiles); + EXPECT_THAT(mainFileDiagRanges(*AST), + UnorderedElementsAreArray(NewCode.ranges())); + } +} + +TEST(PreamblePatch, TranslatesIncludeDiagnosticsInPreamble) { + Config Cfg; + Cfg.Diagnostics.Mode = Config::DiagnosticsMode::Fast; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + llvm::StringLiteral BaselinePreamble("#include [[]]\n"); + { + // Check with additions to preamble. + Annotations Code(BaselinePreamble); + Annotations NewCode(("#define BAR\n" + BaselinePreamble).str()); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } + { + // Check with removals from preamble. + Annotations Code(("#define BAR\n" + BaselinePreamble).str()); + Annotations NewCode(BaselinePreamble); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } + { + // Drop line with diags. + Annotations Code(BaselinePreamble); + Annotations NewCode("#define BAR\n#include [[]]\n"); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } +} + +TEST(PreamblePatch, TranslatesNonIncludeDiagnosticsInPreamble) { + Config Cfg; + Cfg.Diagnostics.Mode = Config::DiagnosticsMode::Fast; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + llvm::StringLiteral BaselinePreamble("#define [[__LINE__]]\n"); + ASSERT_THAT(TestTU::withCode(Annotations(BaselinePreamble).code()) + .build() + .getDiagnostics(), + Not(llvm::ValueIs(IsEmpty()))); + { + // Check with additions to preamble. + Annotations Code(BaselinePreamble); + Annotations NewCode(("#define BAR\n" + BaselinePreamble).str()); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } + { + // Check with removals from preamble. + Annotations Code(("#define BAR\n" + BaselinePreamble).str()); + Annotations NewCode(BaselinePreamble); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } + { + // Drop line with diags. + Annotations Code(BaselinePreamble); + Annotations NewCode("#define BAR\n#include [[]]\n#include \n"); + auto AST = createPatchedAST(Code.code(), NewCode.code()); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); + } +} } // namespace } // namespace clangd } // namespace clang