//===--- PreambleTests.cpp --------------------------------------*- 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 // //===----------------------------------------------------------------------===// #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" #include "XRefs.h" #include "support/Context.h" #include "clang/Basic/SourceManager.h" #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/ScopedPrinter.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest-matchers.h" #include "gtest/gtest.h" #include #include #include #include #include using testing::AllOf; using testing::Contains; using testing::ElementsAre; using testing::Field; using testing::IsEmpty; using testing::Matcher; using testing::MatchesRegex; using testing::UnorderedElementsAre; using testing::UnorderedElementsAreArray; namespace clang { namespace clangd { namespace { MATCHER_P2(Distance, File, D, "") { return arg.first() == File && arg.second == D; } // Builds a preamble for BaselineContents, patches it for ModifiedContents and // returns the includes in the patch. IncludeStructure collectPatchedIncludes(llvm::StringRef ModifiedContents, llvm::StringRef BaselineContents, llvm::StringRef MainFileName = "main.cpp") { MockFS FS; auto TU = TestTU::withCode(BaselineContents); TU.Filename = MainFileName.str(); // ms-compatibility changes meaning of #import, make sure it is turned off. TU.ExtraArgs = {"-fno-ms-compatibility"}; auto BaselinePreamble = TU.preamble(); // Create the patch. TU.Code = ModifiedContents.str(); auto PI = TU.inputs(FS); auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), PI, *BaselinePreamble); // Collect patch contents. IgnoreDiagnostics Diags; auto CI = buildCompilerInvocation(PI, Diags); PP.apply(*CI); // Run preprocessor over the modified contents with patched Invocation. We // provide a preamble and trim contents to ensure only the implicit header // introduced by the patch is parsed and nothing else. // We don't run PP directly over the patch cotents to test production // behaviour. auto Bounds = Lexer::ComputePreamble(ModifiedContents, CI->getLangOpts()); auto Clang = prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble, llvm::MemoryBuffer::getMemBufferCopy( ModifiedContents.slice(0, Bounds.Size).str()), PI.TFS->view(PI.CompileCommand.Directory), Diags); PreprocessOnlyAction Action; if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { ADD_FAILURE() << "failed begin source file"; return {}; } IncludeStructure Includes; Includes.collect(*Clang); if (llvm::Error Err = Action.Execute()) { ADD_FAILURE() << "failed to execute action: " << std::move(Err); return {}; } Action.EndSourceFile(); return Includes; } // Check preamble lexing logic by building an empty preamble and patching it // with all the contents. TEST(PreamblePatchTest, IncludeParsing) { // We expect any line with a point to show up in the patch. llvm::StringRef Cases[] = { // Only preamble R"cpp(^#include "a.h")cpp", // Both preamble and mainfile R"cpp( ^#include "a.h" garbage, finishes preamble #include "a.h")cpp", // Mixed directives R"cpp( ^#include "a.h" #pragma directive // some comments ^#include_next #ifdef skipped ^#import "a.h" #endif)cpp", // Broken directives R"cpp( #include "a ^#include "a.h" #include )cpp", // Directive is not part of preamble if it is not the token immediately // followed by the hash (#). R"cpp( ^#include "a.h" #/**/include )cpp", }; for (const auto &Case : Cases) { Annotations Test(Case); const auto Code = Test.code(); SCOPED_TRACE(Code); auto Includes = collectPatchedIncludes(Code, /*BaselineContents=*/"").MainFileIncludes; auto Points = Test.points(); ASSERT_EQ(Includes.size(), Points.size()); for (size_t I = 0, E = Includes.size(); I != E; ++I) EXPECT_EQ(Includes[I].HashLine, Points[I].line); } } TEST(PreamblePatchTest, ContainsNewIncludes) { constexpr llvm::StringLiteral BaselineContents = R"cpp( #include #include // This will be removed #include )cpp"; constexpr llvm::StringLiteral ModifiedContents = R"cpp( #include #include // This has changed a line. #include // This is a duplicate. #include // This is newly introduced. )cpp"; auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents) .MainFileIncludes; EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, ""), Field(&Inclusion::HashLine, 4)))); } TEST(PreamblePatchTest, MainFileIsEscaped) { auto Includes = collectPatchedIncludes("#include ", "", "file\"name.cpp") .MainFileIncludes; EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, ""), Field(&Inclusion::HashLine, 0)))); } TEST(PreamblePatchTest, PatchesPreambleIncludes) { MockFS FS; IgnoreDiagnostics Diags; auto TU = TestTU::withCode(R"cpp( #include "a.h" // IWYU pragma: keep #include "c.h" #ifdef FOO #include "d.h" #endif )cpp"); TU.AdditionalFiles["a.h"] = "#include \"b.h\""; TU.AdditionalFiles["b.h"] = ""; TU.AdditionalFiles["c.h"] = ""; auto PI = TU.inputs(FS); auto BaselinePreamble = buildPreamble( TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr); // We drop c.h from modified and add a new header. Since the latter is patched // we should only get a.h in preamble includes. d.h shouldn't be part of the // preamble, as it's coming from a disabled region. TU.Code = R"cpp( #include "a.h" #include "b.h" #ifdef FOO #include "d.h" #endif )cpp"; auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS), *BaselinePreamble); // Only a.h should exists in the preamble, as c.h has been dropped and b.h was // newly introduced. EXPECT_THAT( PP.preambleIncludes(), ElementsAre(AllOf( Field(&Inclusion::Written, "\"a.h\""), Field(&Inclusion::Resolved, testPath("a.h")), Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))), Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User)))); } std::optional createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified, llvm::StringMap AdditionalFiles = {}) { auto TU = TestTU::withCode(Baseline); TU.AdditionalFiles = std::move(AdditionalFiles); auto BaselinePreamble = TU.preamble(); if (!BaselinePreamble) { ADD_FAILURE() << "Failed to build baseline preamble"; return std::nullopt; } IgnoreDiagnostics Diags; MockFS FS; TU.Code = Modified.str(); auto CI = buildCompilerInvocation(TU.inputs(FS), Diags); if (!CI) { ADD_FAILURE() << "Failed to build compiler invocation"; return std::nullopt; } return ParsedAST::build(testPath(TU.Filename), TU.inputs(FS), std::move(CI), {}, BaselinePreamble); } std::string getPreamblePatch(llvm::StringRef Baseline, llvm::StringRef Modified) { auto BaselinePreamble = TestTU::withCode(Baseline).preamble(); if (!BaselinePreamble) { ADD_FAILURE() << "Failed to build baseline preamble"; return ""; } MockFS FS; auto TU = TestTU::withCode(Modified); return PreamblePatch::createFullPatch(testPath("main.cpp"), TU.inputs(FS), *BaselinePreamble) .text() .str(); } TEST(PreamblePatchTest, IncludesArePreserved) { llvm::StringLiteral Baseline = R"(//error-ok #include #include )"; llvm::StringLiteral Modified = R"(//error-ok #include #include #define FOO)"; auto Includes = createPatchedAST(Baseline, Modified.str()) ->getIncludeStructure() .MainFileIncludes; EXPECT_TRUE(!Includes.empty()); EXPECT_EQ(Includes, TestTU::withCode(Baseline) .build() .getIncludeStructure() .MainFileIncludes); } TEST(PreamblePatchTest, Define) { // BAR should be defined while parsing the AST. struct { const char *const Contents; const char *const ExpectedPatch; } Cases[] = { { R"cpp( #define BAR [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" #undef BAR #line 2 #define BAR )cpp", }, // multiline macro { R"cpp( #define BAR \ [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" #undef BAR #line 2 #define BAR )cpp", }, // multiline macro { R"cpp( #define \ BAR [[BAR]])cpp", R"cpp(#line 0 ".*main.cpp" #undef BAR #line 3 #define BAR )cpp", }, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Contents); llvm::Annotations Modified(Case.Contents); EXPECT_THAT(getPreamblePatch("", Modified.code()), MatchesRegex(Case.ExpectedPatch)); auto AST = createPatchedAST("", Modified.code()); ASSERT_TRUE(AST); std::vector MacroRefRanges; for (auto &M : AST->getMacros().MacroRefs) { for (auto &O : M.getSecond()) MacroRefRanges.push_back({O.StartOffset, O.EndOffset}); } EXPECT_THAT(MacroRefRanges, Contains(Modified.range())); } } TEST(PreamblePatchTest, OrderingPreserved) { llvm::StringLiteral Baseline = "#define BAR(X) X"; Annotations Modified(R"cpp( #define BAR(X, Y) X Y #define BAR(X) X [[BAR]](int y); )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"); EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()), MatchesRegex(ExpectedPatch.str())); auto AST = createPatchedAST(Baseline, Modified.code()); ASSERT_TRUE(AST); } TEST(PreamblePatchTest, LocateMacroAtWorks) { struct { const char *const Baseline; const char *const Modified; } Cases[] = { // Addition of new directive { "", R"cpp( #define $def^FOO $use^FOO)cpp", }, // Available inside preamble section { "", R"cpp( #define $def^FOO #undef $use^FOO)cpp", }, // Available after undef, as we don't patch those { "", R"cpp( #define $def^FOO #undef FOO $use^FOO)cpp", }, // Identifier on a different line { "", R"cpp( #define \ $def^FOO $use^FOO)cpp", }, // In presence of comment tokens { "", R"cpp( #\ define /* FOO */\ /* FOO */ $def^FOO $use^FOO)cpp", }, // Moved around { "#define FOO", R"cpp( #define BAR #define $def^FOO $use^FOO)cpp", }, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Modified); llvm::Annotations Modified(Case.Modified); auto AST = createPatchedAST(Case.Baseline, Modified.code()); ASSERT_TRUE(AST); const auto &SM = AST->getSourceManager(); auto *MacroTok = AST->getTokens().spelledTokenAt( SM.getComposedLoc(SM.getMainFileID(), Modified.point("use"))); ASSERT_TRUE(MacroTok); auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor()); ASSERT_TRUE(FoundMacro); EXPECT_THAT(FoundMacro->Name, "FOO"); auto MacroLoc = FoundMacro->NameLoc; EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID()); EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def")); } } TEST(PreamblePatchTest, LocateMacroAtDeletion) { { // We don't patch deleted define directives, make sure we don't crash. llvm::StringLiteral Baseline = "#define FOO"; llvm::Annotations Modified("^FOO"); auto AST = createPatchedAST(Baseline, Modified.code()); ASSERT_TRUE(AST); const auto &SM = AST->getSourceManager(); auto *MacroTok = AST->getTokens().spelledTokenAt( SM.getComposedLoc(SM.getMainFileID(), Modified.point())); ASSERT_TRUE(MacroTok); auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor()); ASSERT_TRUE(FoundMacro); EXPECT_THAT(FoundMacro->Name, "FOO"); auto HI = getHover(*AST, offsetToPosition(Modified.code(), Modified.point()), format::getLLVMStyle(), nullptr); ASSERT_TRUE(HI); EXPECT_THAT(HI->Definition, testing::IsEmpty()); } { // Offset is valid, but underlying text is different. llvm::StringLiteral Baseline = "#define FOO"; Annotations Modified(R"cpp(#define BAR ^FOO")cpp"); auto AST = createPatchedAST(Baseline, Modified.code()); ASSERT_TRUE(AST); auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr); ASSERT_TRUE(HI); EXPECT_THAT(HI->Definition, "#define BAR"); } } MATCHER_P(referenceRangeIs, R, "") { return arg.Loc.range == R; } TEST(PreamblePatchTest, RefsToMacros) { struct { const char *const Baseline; const char *const Modified; } Cases[] = { // Newly added { "", R"cpp( #define ^FOO ^[[FOO]])cpp", }, // Moved around { "#define FOO", R"cpp( #define BAR #define ^FOO ^[[FOO]])cpp", }, // Ref in preamble section { "", R"cpp( #define ^FOO #undef ^FOO)cpp", }, }; for (const auto &Case : Cases) { Annotations Modified(Case.Modified); auto AST = createPatchedAST("", Modified.code()); ASSERT_TRUE(AST); const auto &SM = AST->getSourceManager(); std::vector> ExpectedLocations; for (const auto &R : Modified.ranges()) ExpectedLocations.push_back(referenceRangeIs(R)); for (const auto &P : Modified.points()) { auto *MacroTok = AST->getTokens().spelledTokenAt(SM.getComposedLoc( SM.getMainFileID(), llvm::cantFail(positionToOffset(Modified.code(), P)))); ASSERT_TRUE(MacroTok); EXPECT_THAT(findReferences(*AST, P, 0).References, testing::ElementsAreArray(ExpectedLocations)); } } } TEST(TranslatePreamblePatchLocation, Simple) { auto TU = TestTU::withHeaderCode(R"cpp( #line 3 "main.cpp" int foo();)cpp"); // Presumed line/col needs to be valid in the main file. TU.Code = R"cpp(// line 1 // line 2 // line 3 // line 4)cpp"; TU.Filename = "main.cpp"; TU.HeaderFilename = "__preamble_patch__.h"; TU.ImplicitHeaderGuard = false; auto AST = TU.build(); auto &SM = AST.getSourceManager(); auto &ND = findDecl(AST, "foo"); EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID()); auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM); auto DecompLoc = SM.getDecomposedLoc(TranslatedLoc); EXPECT_EQ(DecompLoc.first, SM.getMainFileID()); EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U); } TEST(PreamblePatch, ModifiedBounds) { struct { const char *const Baseline; const char *const Modified; } Cases[] = { // Size increased { "", R"cpp( #define FOO FOO)cpp", }, // Stayed same {"#define FOO", "#define BAR"}, // Got smaller { R"cpp( #define FOO #undef FOO)cpp", "#define FOO"}, }; for (const auto &Case : Cases) { auto TU = TestTU::withCode(Case.Baseline); auto BaselinePreamble = TU.preamble(); ASSERT_TRUE(BaselinePreamble); Annotations Modified(Case.Modified); TU.Code = Modified.code().str(); MockFS FS; auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS), *BaselinePreamble); IgnoreDiagnostics Diags; auto CI = buildCompilerInvocation(TU.inputs(FS), Diags); ASSERT_TRUE(CI); const auto ExpectedBounds = Lexer::ComputePreamble(Case.Modified, CI->getLangOpts()); EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size); EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine, ExpectedBounds.PreambleEndsAtStartOfLine); } } TEST(PreamblePatch, MacroLoc) { llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;"; llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;"; auto AST = createPatchedAST(Baseline, Modified); ASSERT_TRUE(AST); } TEST(PreamblePatch, NoopWhenNotRequested) { llvm::StringLiteral Baseline = "#define M\nint num = M;"; llvm::StringLiteral Modified = "#define M\n#include \nint num = M;"; auto TU = TestTU::withCode(Baseline); auto BaselinePreamble = TU.preamble(); ASSERT_TRUE(BaselinePreamble); TU.Code = Modified.str(); MockFS FS; auto PP = PreamblePatch::createMacroPatch(testPath(TU.Filename), TU.inputs(FS), *BaselinePreamble); EXPECT_TRUE(PP.text().empty()); } ::testing::Matcher withNote(::testing::Matcher NoteMatcher) { return Field(&Diag::Notes, ElementsAre(NoteMatcher)); } MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) { return arg.Range == Range; } MATCHER_P2(Diag, Range, Name, "Diag at " + llvm::to_string(Range) + " = [" + Name + "]") { return arg.Range == Range && arg.Name == Name; } TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) { { Annotations Code("#define FOO"); // Check with removals from preamble. Annotations NewCode("[[x]];/* error-ok */"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "missing_type_specifier"))); } { // Check with additions to preamble. Annotations Code("#define FOO"); Annotations NewCode(R"( #define FOO #define BAR [[x]];/* error-ok */)"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "missing_type_specifier"))); } } TEST(PreamblePatch, DiagnosticsToPreamble) { Config Cfg; Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict; Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict; WithContextValue WithCfg(Config::Key, std::move(Cfg)); llvm::StringMap AdditionalFiles; AdditionalFiles["foo.h"] = "#pragma once"; AdditionalFiles["bar.h"] = "#pragma once"; { Annotations Code(R"( // Test comment [[#include "foo.h"]])"); // Check with removals from preamble. Annotations NewCode(R"([[# include "foo.h"]])"); auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "unused-includes"))); } { // Check with additions to preamble. Annotations Code(R"( // Test comment [[#include "foo.h"]])"); Annotations NewCode(R"( $bar[[#include "bar.h"]] // Test comment $foo[[#include "foo.h"]])"); auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles); EXPECT_THAT( AST->getDiagnostics(), UnorderedElementsAre(Diag(NewCode.range("bar"), "unused-includes"), Diag(NewCode.range("foo"), "unused-includes"))); } { Annotations Code("#define [[FOO]] 1\n"); // Check ranges for notes. // This also makes sure we don't generate missing-include diagnostics // because macros are redefined in preamble-patch. Annotations NewCode(R"(#define BARXYZ 1 #define $foo1[[FOO]] 1 void foo(); #define $foo2[[FOO]] 2)"); auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles); EXPECT_THAT( AST->getDiagnostics(), ElementsAre(AllOf(Diag(NewCode.range("foo2"), "-Wmacro-redefined"), withNote(Diag(NewCode.range("foo1")))))); } } TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) { { // Check with additions to preamble. Annotations Code("#include [[]]"); Annotations NewCode(R"( #define BAR #include [[]])"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "pp_file_not_found"))); } { // Check with removals from preamble. Annotations Code(R"( #define BAR #include [[]])"); Annotations NewCode("#include [[]]"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "pp_file_not_found"))); } { // Drop line with diags. Annotations Code("#include [[]]"); Annotations NewCode("#define BAR\n#define BAZ\n"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); } { // Picks closest line in case of multiple alternatives. Annotations Code("#include [[]]"); Annotations NewCode(R"( #define BAR #include [[]] #define BAR #include )"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "pp_file_not_found"))); } { // Drop diag if line spelling has changed. Annotations Code("#include [[]]"); Annotations NewCode(" # include "); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); } { // Multiple lines. Annotations Code(R"( #define BAR #include [[]])"); Annotations NewCode(R"(#include [[]])"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "pp_file_not_found"))); } { // Multiple lines with change. Annotations Code(R"( #define BAR #include #include [[]])"); Annotations NewCode(R"(#include )"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); } { // Preserves notes. Annotations Code(R"( #define $note[[BAR]] 1 #define $main[[BAR]] 2)"); Annotations NewCode(R"( #define BAZ 0 #define $note[[BAR]] 1 #define BAZ 0 #define $main[[BAR]] 2)"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT( AST->getDiagnostics(), ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"), withNote(Diag(NewCode.range("note")))))); } { // Preserves diag without note. Annotations Code(R"( #define $note[[BAR]] 1 #define $main[[BAR]] 2)"); Annotations NewCode(R"( #define $main[[BAR]] 2)"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT( AST->getDiagnostics(), ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"), Field(&Diag::Notes, IsEmpty())))); } { // Make sure orphaned notes are not promoted to diags. Annotations Code(R"( #define $note[[BAR]] 1 #define $main[[BAR]] 2)"); Annotations NewCode(R"( #define BAZ 0 #define BAR 1)"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); } { Annotations Code(R"( #ifndef FOO #define FOO void foo(); #endif)"); // This code will emit a diagnostic for unterminated #ifndef (as stale // preamble has the conditional but main file doesn't terminate it). // We shouldn't emit any diagnotiscs (and shouldn't crash). Annotations NewCode(""); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); } { Annotations Code(R"( #ifndef FOO #define FOO void foo(); #endif)"); // This code will emit a diagnostic for unterminated #ifndef (as stale // preamble has the conditional but main file doesn't terminate it). // We shouldn't emit any diagnotiscs (and shouldn't crash). // FIXME: Patch/ignore diagnostics in such cases. Annotations NewCode(R"( i[[nt]] xyz; )"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT( AST->getDiagnostics(), ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional"))); } } MATCHER_P2(Mark, Range, Text, "") { return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text); } TEST(PreamblePatch, MacroAndMarkHandling) { { Annotations Code(R"cpp( #ifndef FOO #define FOO // Some comments #pragma mark XX #define BAR #endif)cpp"); Annotations NewCode(R"cpp( #ifndef FOO #define FOO #define BAR #pragma $x[[mark XX ]] #pragma $y[[mark YY ]] #define BAZ #endif)cpp"); auto AST = createPatchedAST(Code.code(), NewCode.code()); EXPECT_THAT(AST->getMacros().Names.keys(), UnorderedElementsAreArray({"FOO", "BAR", "BAZ"})); EXPECT_THAT(AST->getMarks(), UnorderedElementsAre(Mark(NewCode.range("x"), " XX"), Mark(NewCode.range("y"), " YY"))); } } TEST(PreamblePatch, PatchFileEntry) { Annotations Code(R"cpp(#define FOO)cpp"); Annotations NewCode(R"cpp( #define BAR #define FOO)cpp"); { auto AST = createPatchedAST(Code.code(), Code.code()); EXPECT_EQ( PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()), nullptr); } { auto AST = createPatchedAST(Code.code(), NewCode.code()); auto FE = PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()); ASSERT_NE(FE, std::nullopt); EXPECT_THAT(FE->getName().str(), testing::EndsWith(PreamblePatch::HeaderName.str())); } } } // namespace } // namespace clangd } // namespace clang