blob: 16a2f9448b1ecf4d48548d87ea415394955f8cc5 [file] [log] [blame] [edit]
//===--- 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 <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
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 <a.h>
#ifdef skipped
^#import "a.h"
#endif)cpp",
// Broken directives
R"cpp(
#include "a
^#include "a.h"
#include <b
^#include <b.h>)cpp",
// Directive is not part of preamble if it is not the token immediately
// followed by the hash (#).
R"cpp(
^#include "a.h"
#/**/include <b.h>)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 <a.h>
#include <b.h> // This will be removed
#include <c.h>
)cpp";
constexpr llvm::StringLiteral ModifiedContents = R"cpp(
#include <a.h>
#include <c.h> // This has changed a line.
#include <c.h> // This is a duplicate.
#include <d.h> // This is newly introduced.
)cpp";
auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents)
.MainFileIncludes;
EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>"),
Field(&Inclusion::HashLine, 4))));
}
TEST(PreamblePatchTest, MainFileIsEscaped) {
auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")
.MainFileIncludes;
EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),
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<ParsedAST>
createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified,
llvm::StringMap<std::string> 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 <foo>
#include <bar>
)";
llvm::StringLiteral Modified = R"(//error-ok
#include <foo>
#include <bar>
#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<llvm::Annotations::Range> 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().spelledTokenContaining(
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().spelledTokenContaining(
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<Matcher<ReferencesResult::Reference>> ExpectedLocations;
for (const auto &R : Modified.ranges())
ExpectedLocations.push_back(referenceRangeIs(R));
for (const auto &P : Modified.points()) {
auto *MacroTok =
AST->getTokens().spelledTokenContaining(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 <foo.h>\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<const Diag &>
withNote(::testing::Matcher<Note> 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<std::string> 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 [[<foo>]]");
Annotations NewCode(R"(
#define BAR
#include [[<foo>]])");
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 [[<foo>]])");
Annotations NewCode("#include [[<foo>]]");
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 [[<foo>]]");
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 [[<foo>]]");
Annotations NewCode(R"(
#define BAR
#include [[<foo>]]
#define BAR
#include <foo>)");
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 [[<foo>]]");
Annotations NewCode(" # include <foo>");
auto AST = createPatchedAST(Code.code(), NewCode.code());
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
}
{
// Multiple lines.
Annotations Code(R"(
#define BAR
#include [[<fo\
o>]])");
Annotations NewCode(R"(#include [[<fo\
o>]])");
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 <fox>
#include [[<fo\
o>]])");
Annotations NewCode(R"(#include <fo\
x>)");
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