903 lines · cpp
1//===--- PreambleTests.cpp --------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "Annotations.h"10#include "Compiler.h"11#include "Config.h"12#include "Diagnostics.h"13#include "Headers.h"14#include "Hover.h"15#include "ParsedAST.h"16#include "Preamble.h"17#include "Protocol.h"18#include "SourceCode.h"19#include "TestFS.h"20#include "TestTU.h"21#include "XRefs.h"22#include "support/Context.h"23#include "clang/Basic/SourceManager.h"24#include "clang/Format/Format.h"25#include "clang/Frontend/FrontendActions.h"26#include "clang/Frontend/PrecompiledPreamble.h"27#include "llvm/ADT/StringMap.h"28#include "llvm/ADT/StringRef.h"29#include "llvm/Support/Error.h"30#include "llvm/Support/MemoryBuffer.h"31#include "llvm/Support/ScopedPrinter.h"32#include "llvm/Support/VirtualFileSystem.h"33#include "llvm/Testing/Annotations/Annotations.h"34#include "gmock/gmock.h"35#include "gtest/gtest-matchers.h"36#include "gtest/gtest.h"37#include <memory>38#include <optional>39#include <string>40#include <utility>41#include <vector>42 43using testing::AllOf;44using testing::Contains;45using testing::ElementsAre;46using testing::Field;47using testing::IsEmpty;48using testing::Matcher;49using testing::MatchesRegex;50using testing::UnorderedElementsAre;51using testing::UnorderedElementsAreArray;52 53namespace clang {54namespace clangd {55namespace {56 57MATCHER_P2(Distance, File, D, "") {58 return arg.first() == File && arg.second == D;59}60 61// Builds a preamble for BaselineContents, patches it for ModifiedContents and62// returns the includes in the patch.63IncludeStructure64collectPatchedIncludes(llvm::StringRef ModifiedContents,65 llvm::StringRef BaselineContents,66 llvm::StringRef MainFileName = "main.cpp") {67 MockFS FS;68 auto TU = TestTU::withCode(BaselineContents);69 TU.Filename = MainFileName.str();70 // ms-compatibility changes meaning of #import, make sure it is turned off.71 TU.ExtraArgs = {"-fno-ms-compatibility"};72 auto BaselinePreamble = TU.preamble();73 // Create the patch.74 TU.Code = ModifiedContents.str();75 auto PI = TU.inputs(FS);76 auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), PI,77 *BaselinePreamble);78 // Collect patch contents.79 IgnoreDiagnostics Diags;80 auto CI = buildCompilerInvocation(PI, Diags);81 PP.apply(*CI);82 // Run preprocessor over the modified contents with patched Invocation. We83 // provide a preamble and trim contents to ensure only the implicit header84 // introduced by the patch is parsed and nothing else.85 // We don't run PP directly over the patch cotents to test production86 // behaviour.87 auto Bounds = Lexer::ComputePreamble(ModifiedContents, CI->getLangOpts());88 auto Clang =89 prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble,90 llvm::MemoryBuffer::getMemBufferCopy(91 ModifiedContents.slice(0, Bounds.Size).str()),92 PI.TFS->view(PI.CompileCommand.Directory), Diags);93 PreprocessOnlyAction Action;94 if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {95 ADD_FAILURE() << "failed begin source file";96 return {};97 }98 IncludeStructure Includes;99 Includes.collect(*Clang);100 if (llvm::Error Err = Action.Execute()) {101 ADD_FAILURE() << "failed to execute action: " << std::move(Err);102 return {};103 }104 Action.EndSourceFile();105 return Includes;106}107 108// Check preamble lexing logic by building an empty preamble and patching it109// with all the contents.110TEST(PreamblePatchTest, IncludeParsing) {111 // We expect any line with a point to show up in the patch.112 llvm::StringRef Cases[] = {113 // Only preamble114 R"cpp(^#include "a.h")cpp",115 // Both preamble and mainfile116 R"cpp(117 ^#include "a.h"118 garbage, finishes preamble119 #include "a.h")cpp",120 // Mixed directives121 R"cpp(122 ^#include "a.h"123 #pragma directive124 // some comments125 ^#include_next <a.h>126 #ifdef skipped127 ^#import "a.h"128 #endif)cpp",129 // Broken directives130 R"cpp(131 #include "a132 ^#include "a.h"133 #include <b134 ^#include <b.h>)cpp",135 // Directive is not part of preamble if it is not the token immediately136 // followed by the hash (#).137 R"cpp(138 ^#include "a.h"139 #/**/include <b.h>)cpp",140 };141 142 for (const auto &Case : Cases) {143 Annotations Test(Case);144 const auto Code = Test.code();145 SCOPED_TRACE(Code);146 147 auto Includes =148 collectPatchedIncludes(Code, /*BaselineContents=*/"").MainFileIncludes;149 auto Points = Test.points();150 ASSERT_EQ(Includes.size(), Points.size());151 for (size_t I = 0, E = Includes.size(); I != E; ++I)152 EXPECT_EQ(Includes[I].HashLine, Points[I].line);153 }154}155 156TEST(PreamblePatchTest, ContainsNewIncludes) {157 constexpr llvm::StringLiteral BaselineContents = R"cpp(158 #include <a.h>159 #include <b.h> // This will be removed160 #include <c.h>161 )cpp";162 constexpr llvm::StringLiteral ModifiedContents = R"cpp(163 #include <a.h>164 #include <c.h> // This has changed a line.165 #include <c.h> // This is a duplicate.166 #include <d.h> // This is newly introduced.167 )cpp";168 auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents)169 .MainFileIncludes;170 EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>"),171 Field(&Inclusion::HashLine, 4))));172}173 174TEST(PreamblePatchTest, MainFileIsEscaped) {175 auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")176 .MainFileIncludes;177 EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),178 Field(&Inclusion::HashLine, 0))));179}180 181TEST(PreamblePatchTest, PatchesPreambleIncludes) {182 MockFS FS;183 IgnoreDiagnostics Diags;184 auto TU = TestTU::withCode(R"cpp(185 #include "a.h" // IWYU pragma: keep186 #include "c.h"187 #ifdef FOO188 #include "d.h"189 #endif190 )cpp");191 TU.AdditionalFiles["a.h"] = "#include \"b.h\"";192 TU.AdditionalFiles["b.h"] = "";193 TU.AdditionalFiles["c.h"] = "";194 auto PI = TU.inputs(FS);195 auto BaselinePreamble = buildPreamble(196 TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);197 // We drop c.h from modified and add a new header. Since the latter is patched198 // we should only get a.h in preamble includes. d.h shouldn't be part of the199 // preamble, as it's coming from a disabled region.200 TU.Code = R"cpp(201 #include "a.h"202 #include "b.h"203 #ifdef FOO204 #include "d.h"205 #endif206 )cpp";207 auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS),208 *BaselinePreamble);209 // Only a.h should exists in the preamble, as c.h has been dropped and b.h was210 // newly introduced.211 EXPECT_THAT(212 PP.preambleIncludes(),213 ElementsAre(AllOf(214 Field(&Inclusion::Written, "\"a.h\""),215 Field(&Inclusion::Resolved, testPath("a.h")),216 Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))),217 Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User))));218}219 220std::optional<ParsedAST>221createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified,222 llvm::StringMap<std::string> AdditionalFiles = {}) {223 auto TU = TestTU::withCode(Baseline);224 TU.AdditionalFiles = std::move(AdditionalFiles);225 auto BaselinePreamble = TU.preamble();226 if (!BaselinePreamble) {227 ADD_FAILURE() << "Failed to build baseline preamble";228 return std::nullopt;229 }230 231 IgnoreDiagnostics Diags;232 MockFS FS;233 TU.Code = Modified.str();234 auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);235 if (!CI) {236 ADD_FAILURE() << "Failed to build compiler invocation";237 return std::nullopt;238 }239 return ParsedAST::build(testPath(TU.Filename), TU.inputs(FS), std::move(CI),240 {}, BaselinePreamble);241}242 243std::string getPreamblePatch(llvm::StringRef Baseline,244 llvm::StringRef Modified) {245 auto BaselinePreamble = TestTU::withCode(Baseline).preamble();246 if (!BaselinePreamble) {247 ADD_FAILURE() << "Failed to build baseline preamble";248 return "";249 }250 MockFS FS;251 auto TU = TestTU::withCode(Modified);252 return PreamblePatch::createFullPatch(testPath("main.cpp"), TU.inputs(FS),253 *BaselinePreamble)254 .text()255 .str();256}257 258TEST(PreamblePatchTest, IncludesArePreserved) {259 llvm::StringLiteral Baseline = R"(//error-ok260#include <foo>261#include <bar>262)";263 llvm::StringLiteral Modified = R"(//error-ok264#include <foo>265#include <bar>266#define FOO)";267 268 auto Includes = createPatchedAST(Baseline, Modified.str())269 ->getIncludeStructure()270 .MainFileIncludes;271 EXPECT_TRUE(!Includes.empty());272 EXPECT_EQ(Includes, TestTU::withCode(Baseline)273 .build()274 .getIncludeStructure()275 .MainFileIncludes);276}277 278TEST(PreamblePatchTest, Define) {279 // BAR should be defined while parsing the AST.280 struct {281 const char *const Contents;282 const char *const ExpectedPatch;283 } Cases[] = {284 {285 R"cpp(286 #define BAR287 [[BAR]])cpp",288 R"cpp(#line 0 ".*main.cpp"289#undef BAR290#line 2291#define BAR292)cpp",293 },294 // multiline macro295 {296 R"cpp(297 #define BAR \298 299 [[BAR]])cpp",300 R"cpp(#line 0 ".*main.cpp"301#undef BAR302#line 2303#define BAR304)cpp",305 },306 // multiline macro307 {308 R"cpp(309 #define \310 BAR311 [[BAR]])cpp",312 R"cpp(#line 0 ".*main.cpp"313#undef BAR314#line 3315#define BAR316)cpp",317 },318 };319 320 for (const auto &Case : Cases) {321 SCOPED_TRACE(Case.Contents);322 llvm::Annotations Modified(Case.Contents);323 EXPECT_THAT(getPreamblePatch("", Modified.code()),324 MatchesRegex(Case.ExpectedPatch));325 326 auto AST = createPatchedAST("", Modified.code());327 ASSERT_TRUE(AST);328 std::vector<llvm::Annotations::Range> MacroRefRanges;329 for (auto &M : AST->getMacros().MacroRefs) {330 for (auto &O : M.getSecond())331 MacroRefRanges.push_back({O.StartOffset, O.EndOffset});332 }333 EXPECT_THAT(MacroRefRanges, Contains(Modified.range()));334 }335}336 337TEST(PreamblePatchTest, OrderingPreserved) {338 llvm::StringLiteral Baseline = "#define BAR(X) X";339 Annotations Modified(R"cpp(340 #define BAR(X, Y) X Y341 #define BAR(X) X342 [[BAR]](int y);343 )cpp");344 345 llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp"346#undef BAR347#line 2348#define BAR\(X, Y\) X Y349#undef BAR350#line 3351#define BAR\(X\) X352)cpp");353 EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()),354 MatchesRegex(ExpectedPatch.str()));355 356 auto AST = createPatchedAST(Baseline, Modified.code());357 ASSERT_TRUE(AST);358}359 360TEST(PreamblePatchTest, LocateMacroAtWorks) {361 struct {362 const char *const Baseline;363 const char *const Modified;364 } Cases[] = {365 // Addition of new directive366 {367 "",368 R"cpp(369 #define $def^FOO370 $use^FOO)cpp",371 },372 // Available inside preamble section373 {374 "",375 R"cpp(376 #define $def^FOO377 #undef $use^FOO)cpp",378 },379 // Available after undef, as we don't patch those380 {381 "",382 R"cpp(383 #define $def^FOO384 #undef FOO385 $use^FOO)cpp",386 },387 // Identifier on a different line388 {389 "",390 R"cpp(391 #define \392 $def^FOO393 $use^FOO)cpp",394 },395 // In presence of comment tokens396 {397 "",398 R"cpp(399 #\400 define /* FOO */\401 /* FOO */ $def^FOO402 $use^FOO)cpp",403 },404 // Moved around405 {406 "#define FOO",407 R"cpp(408 #define BAR409 #define $def^FOO410 $use^FOO)cpp",411 },412 };413 for (const auto &Case : Cases) {414 SCOPED_TRACE(Case.Modified);415 llvm::Annotations Modified(Case.Modified);416 auto AST = createPatchedAST(Case.Baseline, Modified.code());417 ASSERT_TRUE(AST);418 419 const auto &SM = AST->getSourceManager();420 auto *MacroTok = AST->getTokens().spelledTokenContaining(421 SM.getComposedLoc(SM.getMainFileID(), Modified.point("use")));422 ASSERT_TRUE(MacroTok);423 424 auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());425 ASSERT_TRUE(FoundMacro);426 EXPECT_THAT(FoundMacro->Name, "FOO");427 428 auto MacroLoc = FoundMacro->NameLoc;429 EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID());430 EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def"));431 }432}433 434TEST(PreamblePatchTest, LocateMacroAtDeletion) {435 {436 // We don't patch deleted define directives, make sure we don't crash.437 llvm::StringLiteral Baseline = "#define FOO";438 llvm::Annotations Modified("^FOO");439 440 auto AST = createPatchedAST(Baseline, Modified.code());441 ASSERT_TRUE(AST);442 443 const auto &SM = AST->getSourceManager();444 auto *MacroTok = AST->getTokens().spelledTokenContaining(445 SM.getComposedLoc(SM.getMainFileID(), Modified.point()));446 ASSERT_TRUE(MacroTok);447 448 auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());449 ASSERT_TRUE(FoundMacro);450 EXPECT_THAT(FoundMacro->Name, "FOO");451 auto HI =452 getHover(*AST, offsetToPosition(Modified.code(), Modified.point()),453 format::getLLVMStyle(), nullptr);454 ASSERT_TRUE(HI);455 EXPECT_THAT(HI->Definition, testing::IsEmpty());456 }457 458 {459 // Offset is valid, but underlying text is different.460 llvm::StringLiteral Baseline = "#define FOO";461 Annotations Modified(R"cpp(#define BAR462 ^FOO")cpp");463 464 auto AST = createPatchedAST(Baseline, Modified.code());465 ASSERT_TRUE(AST);466 467 auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr);468 ASSERT_TRUE(HI);469 EXPECT_THAT(HI->Definition, "#define BAR");470 }471}472 473MATCHER_P(referenceRangeIs, R, "") { return arg.Loc.range == R; }474 475TEST(PreamblePatchTest, RefsToMacros) {476 struct {477 const char *const Baseline;478 const char *const Modified;479 } Cases[] = {480 // Newly added481 {482 "",483 R"cpp(484 #define ^FOO485 ^[[FOO]])cpp",486 },487 // Moved around488 {489 "#define FOO",490 R"cpp(491 #define BAR492 #define ^FOO493 ^[[FOO]])cpp",494 },495 // Ref in preamble section496 {497 "",498 R"cpp(499 #define ^FOO500 #undef ^FOO)cpp",501 },502 };503 504 for (const auto &Case : Cases) {505 Annotations Modified(Case.Modified);506 auto AST = createPatchedAST("", Modified.code());507 ASSERT_TRUE(AST);508 509 const auto &SM = AST->getSourceManager();510 std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;511 for (const auto &R : Modified.ranges())512 ExpectedLocations.push_back(referenceRangeIs(R));513 514 for (const auto &P : Modified.points()) {515 auto *MacroTok =516 AST->getTokens().spelledTokenContaining(SM.getComposedLoc(517 SM.getMainFileID(),518 llvm::cantFail(positionToOffset(Modified.code(), P))));519 ASSERT_TRUE(MacroTok);520 EXPECT_THAT(findReferences(*AST, P, 0).References,521 testing::ElementsAreArray(ExpectedLocations));522 }523 }524}525 526TEST(TranslatePreamblePatchLocation, Simple) {527 auto TU = TestTU::withHeaderCode(R"cpp(528 #line 3 "main.cpp"529 int foo();)cpp");530 // Presumed line/col needs to be valid in the main file.531 TU.Code = R"cpp(// line 1532 // line 2533 // line 3534 // line 4)cpp";535 TU.Filename = "main.cpp";536 TU.HeaderFilename = "__preamble_patch__.h";537 TU.ImplicitHeaderGuard = false;538 539 auto AST = TU.build();540 auto &SM = AST.getSourceManager();541 auto &ND = findDecl(AST, "foo");542 EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID());543 544 auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM);545 auto DecompLoc = SM.getDecomposedLoc(TranslatedLoc);546 EXPECT_EQ(DecompLoc.first, SM.getMainFileID());547 EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U);548}549 550TEST(PreamblePatch, ModifiedBounds) {551 struct {552 const char *const Baseline;553 const char *const Modified;554 } Cases[] = {555 // Size increased556 {557 "",558 R"cpp(559 #define FOO560 FOO)cpp",561 },562 // Stayed same563 {"#define FOO", "#define BAR"},564 // Got smaller565 {566 R"cpp(567 #define FOO568 #undef FOO)cpp",569 "#define FOO"},570 };571 572 for (const auto &Case : Cases) {573 auto TU = TestTU::withCode(Case.Baseline);574 auto BaselinePreamble = TU.preamble();575 ASSERT_TRUE(BaselinePreamble);576 577 Annotations Modified(Case.Modified);578 TU.Code = Modified.code().str();579 MockFS FS;580 auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename),581 TU.inputs(FS), *BaselinePreamble);582 583 IgnoreDiagnostics Diags;584 auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);585 ASSERT_TRUE(CI);586 587 const auto ExpectedBounds =588 Lexer::ComputePreamble(Case.Modified, CI->getLangOpts());589 EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size);590 EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine,591 ExpectedBounds.PreambleEndsAtStartOfLine);592 }593}594 595TEST(PreamblePatch, MacroLoc) {596 llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;";597 llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;";598 auto AST = createPatchedAST(Baseline, Modified);599 ASSERT_TRUE(AST);600}601 602TEST(PreamblePatch, NoopWhenNotRequested) {603 llvm::StringLiteral Baseline = "#define M\nint num = M;";604 llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;";605 auto TU = TestTU::withCode(Baseline);606 auto BaselinePreamble = TU.preamble();607 ASSERT_TRUE(BaselinePreamble);608 609 TU.Code = Modified.str();610 MockFS FS;611 auto PP = PreamblePatch::createMacroPatch(testPath(TU.Filename),612 TU.inputs(FS), *BaselinePreamble);613 EXPECT_TRUE(PP.text().empty());614}615 616::testing::Matcher<const Diag &>617withNote(::testing::Matcher<Note> NoteMatcher) {618 return Field(&Diag::Notes, ElementsAre(NoteMatcher));619}620MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) {621 return arg.Range == Range;622}623MATCHER_P2(Diag, Range, Name,624 "Diag at " + llvm::to_string(Range) + " = [" + Name + "]") {625 return arg.Range == Range && arg.Name == Name;626}627 628TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) {629 {630 Annotations Code("#define FOO");631 // Check with removals from preamble.632 Annotations NewCode("[[x]];/* error-ok */");633 auto AST = createPatchedAST(Code.code(), NewCode.code());634 EXPECT_THAT(AST->getDiagnostics(),635 ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));636 }637 {638 // Check with additions to preamble.639 Annotations Code("#define FOO");640 Annotations NewCode(R"(641#define FOO642#define BAR643[[x]];/* error-ok */)");644 auto AST = createPatchedAST(Code.code(), NewCode.code());645 EXPECT_THAT(AST->getDiagnostics(),646 ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));647 }648}649 650TEST(PreamblePatch, DiagnosticsToPreamble) {651 Config Cfg;652 Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;653 Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict;654 WithContextValue WithCfg(Config::Key, std::move(Cfg));655 656 llvm::StringMap<std::string> AdditionalFiles;657 AdditionalFiles["foo.h"] = "#pragma once";658 AdditionalFiles["bar.h"] = "#pragma once";659 {660 Annotations Code(R"(661// Test comment662[[#include "foo.h"]])");663 // Check with removals from preamble.664 Annotations NewCode(R"([[# include "foo.h"]])");665 auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);666 EXPECT_THAT(AST->getDiagnostics(),667 ElementsAre(Diag(NewCode.range(), "unused-includes")));668 }669 {670 // Check with additions to preamble.671 Annotations Code(R"(672// Test comment673[[#include "foo.h"]])");674 Annotations NewCode(R"(675$bar[[#include "bar.h"]]676// Test comment677$foo[[#include "foo.h"]])");678 auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);679 EXPECT_THAT(680 AST->getDiagnostics(),681 UnorderedElementsAre(Diag(NewCode.range("bar"), "unused-includes"),682 Diag(NewCode.range("foo"), "unused-includes")));683 }684 {685 Annotations Code("#define [[FOO]] 1\n");686 // Check ranges for notes.687 // This also makes sure we don't generate missing-include diagnostics688 // because macros are redefined in preamble-patch.689 Annotations NewCode(R"(#define BARXYZ 1690#define $foo1[[FOO]] 1691void foo();692#define $foo2[[FOO]] 2)");693 auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);694 EXPECT_THAT(695 AST->getDiagnostics(),696 ElementsAre(AllOf(Diag(NewCode.range("foo2"), "-Wmacro-redefined"),697 withNote(Diag(NewCode.range("foo1"))))));698 }699}700 701TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) {702 {703 // Check with additions to preamble.704 Annotations Code("#include [[<foo>]]");705 Annotations NewCode(R"(706#define BAR707#include [[<foo>]])");708 auto AST = createPatchedAST(Code.code(), NewCode.code());709 EXPECT_THAT(AST->getDiagnostics(),710 ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));711 }712 {713 // Check with removals from preamble.714 Annotations Code(R"(715#define BAR716#include [[<foo>]])");717 Annotations NewCode("#include [[<foo>]]");718 auto AST = createPatchedAST(Code.code(), NewCode.code());719 EXPECT_THAT(AST->getDiagnostics(),720 ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));721 }722 {723 // Drop line with diags.724 Annotations Code("#include [[<foo>]]");725 Annotations NewCode("#define BAR\n#define BAZ\n");726 auto AST = createPatchedAST(Code.code(), NewCode.code());727 EXPECT_THAT(AST->getDiagnostics(), IsEmpty());728 }729 {730 // Picks closest line in case of multiple alternatives.731 Annotations Code("#include [[<foo>]]");732 Annotations NewCode(R"(733#define BAR734#include [[<foo>]]735#define BAR736#include <foo>)");737 auto AST = createPatchedAST(Code.code(), NewCode.code());738 EXPECT_THAT(AST->getDiagnostics(),739 ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));740 }741 {742 // Drop diag if line spelling has changed.743 Annotations Code("#include [[<foo>]]");744 Annotations NewCode(" # include <foo>");745 auto AST = createPatchedAST(Code.code(), NewCode.code());746 EXPECT_THAT(AST->getDiagnostics(), IsEmpty());747 }748 {749 // Multiple lines.750 Annotations Code(R"(751#define BAR752#include [[<fo\753o>]])");754 Annotations NewCode(R"(#include [[<fo\755o>]])");756 auto AST = createPatchedAST(Code.code(), NewCode.code());757 EXPECT_THAT(AST->getDiagnostics(),758 ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));759 }760 {761 // Multiple lines with change.762 Annotations Code(R"(763#define BAR764#include <fox>765#include [[<fo\766o>]])");767 Annotations NewCode(R"(#include <fo\768x>)");769 auto AST = createPatchedAST(Code.code(), NewCode.code());770 EXPECT_THAT(AST->getDiagnostics(), IsEmpty());771 }772 {773 // Preserves notes.774 Annotations Code(R"(775#define $note[[BAR]] 1776#define $main[[BAR]] 2)");777 Annotations NewCode(R"(778#define BAZ 0779#define $note[[BAR]] 1780#define BAZ 0781#define $main[[BAR]] 2)");782 auto AST = createPatchedAST(Code.code(), NewCode.code());783 EXPECT_THAT(784 AST->getDiagnostics(),785 ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),786 withNote(Diag(NewCode.range("note"))))));787 }788 {789 // Preserves diag without note.790 Annotations Code(R"(791#define $note[[BAR]] 1792#define $main[[BAR]] 2)");793 Annotations NewCode(R"(794#define $main[[BAR]] 2)");795 auto AST = createPatchedAST(Code.code(), NewCode.code());796 EXPECT_THAT(797 AST->getDiagnostics(),798 ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),799 Field(&Diag::Notes, IsEmpty()))));800 }801 {802 // Make sure orphaned notes are not promoted to diags.803 Annotations Code(R"(804#define $note[[BAR]] 1805#define $main[[BAR]] 2)");806 Annotations NewCode(R"(807#define BAZ 0808#define BAR 1)");809 auto AST = createPatchedAST(Code.code(), NewCode.code());810 EXPECT_THAT(AST->getDiagnostics(), IsEmpty());811 }812 {813 Annotations Code(R"(814#ifndef FOO815#define FOO816void foo();817#endif)");818 // This code will emit a diagnostic for unterminated #ifndef (as stale819 // preamble has the conditional but main file doesn't terminate it).820 // We shouldn't emit any diagnotiscs (and shouldn't crash).821 Annotations NewCode("");822 auto AST = createPatchedAST(Code.code(), NewCode.code());823 EXPECT_THAT(AST->getDiagnostics(), IsEmpty());824 }825 {826 Annotations Code(R"(827#ifndef FOO828#define FOO829void foo();830#endif)");831 // This code will emit a diagnostic for unterminated #ifndef (as stale832 // preamble has the conditional but main file doesn't terminate it).833 // We shouldn't emit any diagnotiscs (and shouldn't crash).834 // FIXME: Patch/ignore diagnostics in such cases.835 Annotations NewCode(R"(836i[[nt]] xyz;837 )");838 auto AST = createPatchedAST(Code.code(), NewCode.code());839 EXPECT_THAT(840 AST->getDiagnostics(),841 ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional")));842 }843}844 845MATCHER_P2(Mark, Range, Text, "") {846 return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text);847}848 849TEST(PreamblePatch, MacroAndMarkHandling) {850 {851 Annotations Code(R"cpp(852#ifndef FOO853#define FOO854// Some comments855#pragma mark XX856#define BAR857 858#endif)cpp");859 Annotations NewCode(R"cpp(860#ifndef FOO861#define FOO862#define BAR863#pragma $x[[mark XX864]]865#pragma $y[[mark YY866]]867#define BAZ868 869#endif)cpp");870 auto AST = createPatchedAST(Code.code(), NewCode.code());871 EXPECT_THAT(AST->getMacros().Names.keys(),872 UnorderedElementsAreArray({"FOO", "BAR", "BAZ"}));873 EXPECT_THAT(AST->getMarks(),874 UnorderedElementsAre(Mark(NewCode.range("x"), " XX"),875 Mark(NewCode.range("y"), " YY")));876 }877}878 879TEST(PreamblePatch, PatchFileEntry) {880 Annotations Code(R"cpp(#define FOO)cpp");881 Annotations NewCode(R"cpp(882#define BAR883#define FOO)cpp");884 {885 auto AST = createPatchedAST(Code.code(), Code.code());886 EXPECT_EQ(887 PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()),888 nullptr);889 }890 {891 auto AST = createPatchedAST(Code.code(), NewCode.code());892 auto FE =893 PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager());894 ASSERT_NE(FE, std::nullopt);895 EXPECT_THAT(FE->getName().str(),896 testing::EndsWith(PreamblePatch::HeaderName.str()));897 }898}899 900} // namespace901} // namespace clangd902} // namespace clang903