255 changes: 203 additions & 52 deletions clang-tools-extra/clangd/Preamble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
Expand Down Expand Up @@ -49,6 +50,7 @@
namespace clang {
namespace clangd {
namespace {
constexpr llvm::StringLiteral PreamblePatchHeaderName = "__preamble_patch__.h";

bool compileCommandsAreEqual(const tooling::CompileCommand &LHS,
const tooling::CompileCommand &RHS) {
Expand Down Expand Up @@ -106,14 +108,110 @@ class CppFilePreambleCallbacks : public PreambleCallbacks {
const SourceManager *SourceMgr = nullptr;
};

/// Gets the includes in the preamble section of the file by running
/// preprocessor over \p Contents. Returned includes do not contain resolved
/// paths. \p VFS and \p Cmd is used to build the compiler invocation, which
/// might stat/read files.
llvm::Expected<std::vector<Inclusion>>
scanPreambleIncludes(llvm::StringRef Contents,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
const tooling::CompileCommand &Cmd) {
// Represents directives other than includes, where basic textual information is
// enough.
struct TextualPPDirective {
unsigned DirectiveLine;
// Full text that's representing the directive, including the `#`.
std::string Text;

bool operator==(const TextualPPDirective &RHS) const {
return std::tie(DirectiveLine, Text) ==
std::tie(RHS.DirectiveLine, RHS.Text);
}
};

// Formats a PP directive consisting of Prefix (e.g. "#define ") and Body ("X
// 10"). The formatting is copied so that the tokens in Body have PresumedLocs
// with correct columns and lines.
std::string spellDirective(llvm::StringRef Prefix,
CharSourceRange DirectiveRange,
const LangOptions &LangOpts, const SourceManager &SM,
unsigned &DirectiveLine) {
std::string SpelledDirective;
llvm::raw_string_ostream OS(SpelledDirective);
OS << Prefix;

// Make sure DirectiveRange is a char range and doesn't contain macro ids.
DirectiveRange = SM.getExpansionRange(DirectiveRange);
if (DirectiveRange.isTokenRange()) {
DirectiveRange.setEnd(
Lexer::getLocForEndOfToken(DirectiveRange.getEnd(), 0, SM, LangOpts));
}

auto DecompLoc = SM.getDecomposedLoc(DirectiveRange.getBegin());
DirectiveLine = SM.getLineNumber(DecompLoc.first, DecompLoc.second);
auto TargetColumn = SM.getColumnNumber(DecompLoc.first, DecompLoc.second) - 1;

// Pad with spaces before DirectiveRange to make sure it will be on right
// column when patched.
if (Prefix.size() <= TargetColumn) {
// There is enough space for Prefix and space before directive, use it.
// We try to squeeze the Prefix into the same line whenever we can, as
// putting onto a separate line won't work at the beginning of the file.
OS << std::string(TargetColumn - Prefix.size(), ' ');
} else {
// Prefix was longer than the space we had. We produce e.g.:
// #line N-1
// #define \
// X 10
OS << "\\\n" << std::string(TargetColumn, ' ');
// Decrement because we put an additional line break before
// DirectiveRange.begin().
--DirectiveLine;
}
OS << toSourceCode(SM, DirectiveRange.getAsRange());
return OS.str();
}

// Collects #define directives inside the main file.
struct DirectiveCollector : public PPCallbacks {
DirectiveCollector(const Preprocessor &PP,
std::vector<TextualPPDirective> &TextualDirectives)
: LangOpts(PP.getLangOpts()), SM(PP.getSourceManager()),
TextualDirectives(TextualDirectives) {}

void FileChanged(SourceLocation Loc, FileChangeReason Reason,
SrcMgr::CharacteristicKind FileType,
FileID PrevFID) override {
InMainFile = SM.isWrittenInMainFile(Loc);
}

void MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) override {
if (!InMainFile)
return;
TextualDirectives.emplace_back();
TextualPPDirective &TD = TextualDirectives.back();

const auto *MI = MD->getMacroInfo();
TD.Text =
spellDirective("#define ",
CharSourceRange::getTokenRange(
MI->getDefinitionLoc(), MI->getDefinitionEndLoc()),
LangOpts, SM, TD.DirectiveLine);
}

private:
bool InMainFile = true;
const LangOptions &LangOpts;
const SourceManager &SM;
std::vector<TextualPPDirective> &TextualDirectives;
};

struct ScannedPreamble {
std::vector<Inclusion> Includes;
std::vector<TextualPPDirective> TextualDirectives;
};

/// Scans the preprocessor directives in the preamble section of the file by
/// running preprocessor over \p Contents. Returned includes do not contain
/// resolved paths. \p VFS and \p Cmd is used to build the compiler invocation,
/// which might stat/read files.
llvm::Expected<ScannedPreamble>
scanPreamble(llvm::StringRef Contents,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
const tooling::CompileCommand &Cmd) {
// Build and run Preprocessor over the preamble.
ParseInputs PI;
PI.Contents = Contents.str();
Expand Down Expand Up @@ -147,14 +245,18 @@ scanPreambleIncludes(llvm::StringRef Contents,
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"failed BeginSourceFile");
const auto &SM = Clang->getSourceManager();
Preprocessor &PP = Clang->getPreprocessor();
IncludeStructure Includes;
PP.addPPCallbacks(collectIncludeStructureCallback(SM, &Includes));
ScannedPreamble SP;
PP.addPPCallbacks(
collectIncludeStructureCallback(Clang->getSourceManager(), &Includes));
std::make_unique<DirectiveCollector>(PP, SP.TextualDirectives));
if (llvm::Error Err = Action.Execute())
return std::move(Err);
Action.EndSourceFile();
return Includes.MainFileIncludes;
SP.Includes = std::move(Includes.MainFileIncludes);
return SP;
}

const char *spellingForIncDirective(tok::PPKeywordKind IncludeDirective) {
Expand All @@ -170,6 +272,13 @@ const char *spellingForIncDirective(tok::PPKeywordKind IncludeDirective) {
}
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());
}

} // namespace

PreambleData::PreambleData(const ParseInputs &Inputs,
Expand Down Expand Up @@ -277,80 +386,103 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
const ParseInputs &Modified,
const PreambleData &Baseline) {
assert(llvm::sys::path::is_absolute(FileName) && "relative FileName!");
// First scan the include directives in Baseline and Modified. These will be
// First scan preprocessor directives in Baseline and Modified. These will be
// used to figure out newly added directives in Modified. Scanning can fail,
// the code just bails out and creates an empty patch in such cases, as:
// - If scanning for Baseline fails, no knowledge of existing includes hence
// patch will contain all the includes in Modified. Leading to rebuild of
// whole preamble, which is terribly slow.
// - If scanning for Modified fails, cannot figure out newly added ones so
// there's nothing to do but generate an empty patch.
auto BaselineIncludes = scanPreambleIncludes(
auto BaselineScan = scanPreamble(
// Contents needs to be null-terminated.
Baseline.Preamble.getContents().str(),
Baseline.StatCache->getConsumingFS(Modified.FS), Modified.CompileCommand);
if (!BaselineIncludes) {
elog("Failed to scan includes for baseline of {0}: {1}", FileName,
BaselineIncludes.takeError());
return {};
if (!BaselineScan) {
elog("Failed to scan baseline of {0}: {1}", FileName,
BaselineScan.takeError());
return PreamblePatch::unmodified(Baseline);
}
auto ModifiedIncludes = scanPreambleIncludes(
auto ModifiedScan = scanPreamble(
Modified.Contents, Baseline.StatCache->getConsumingFS(Modified.FS),
Modified.CompileCommand);
if (!ModifiedIncludes) {
elog("Failed to scan includes for modified contents of {0}: {1}", FileName,
ModifiedIncludes.takeError());
return {};
if (!ModifiedScan) {
elog("Failed to scan modified contents of {0}: {1}", FileName,
ModifiedScan.takeError());
return PreamblePatch::unmodified(Baseline);
}
// No patch needed if includes are equal.
if (*BaselineIncludes == *ModifiedIncludes)

bool IncludesChanged = BaselineScan->Includes != ModifiedScan->Includes;
bool DirectivesChanged =
BaselineScan->TextualDirectives != ModifiedScan->TextualDirectives;
if (!IncludesChanged && !DirectivesChanged)
return PreamblePatch::unmodified(Baseline);

PreamblePatch PP;
// This shouldn't coincide with any real file name.
llvm::SmallString<128> PatchName;
llvm::sys::path::append(PatchName, llvm::sys::path::parent_path(FileName),
"__preamble_patch__.h");
PreamblePatchHeaderName);
PP.PatchFileName = PatchName.str().str();

// We are only interested in newly added includes, record the ones in Baseline
// for exclusion.
llvm::DenseMap<std::pair<tok::PPKeywordKind, llvm::StringRef>,
/*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 : *BaselineIncludes)
ExistingIncludes.try_emplace({Inc.Directive, Inc.Written});
// Calculate extra includes that needs to be inserted.
llvm::raw_string_ostream Patch(PP.PatchContents);
// Set default filename for subsequent #line directives
Patch << "#line 0 \"";
// FileName part of a line directive is subject to backslash escaping, which
// might lead to problems on windows especially.
escapeBackslashAndQuotes(FileName, Patch);
Patch << "\"\n";
for (auto &Inc : *ModifiedIncludes) {
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();
PP.PreambleIncludes.push_back(Inc);
continue;

if (IncludesChanged) {
// We are only interested in newly added includes, record the ones in
// Baseline for exclusion.
llvm::DenseMap<std::pair<tok::PPKeywordKind, llvm::StringRef>,
/*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});
// 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();
PP.PreambleIncludes.push_back(Inc);
continue;
}
// Include is new in the modified preamble. Inject it into the patch and
// use #line to set the presumed location to where it is spelled.
auto LineCol = offsetToClangLineColumn(Modified.Contents, Inc.HashOffset);
Patch << llvm::formatv("#line {0}\n", LineCol.first);
Patch << llvm::formatv(
"#{0} {1}\n", spellingForIncDirective(Inc.Directive), Inc.Written);
}
// Include is new in the modified preamble. Inject it into the patch and use
// #line to set the presumed location to where it is spelled.
auto LineCol = offsetToClangLineColumn(Modified.Contents, Inc.HashOffset);
Patch << llvm::formatv("#line {0}\n", LineCol.first);
Patch << llvm::formatv("#{0} {1}\n", spellingForIncDirective(Inc.Directive),
Inc.Written);
}
Patch.flush();

// FIXME: Handle more directives, e.g. define/undef.
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
// #define BAR(X) OLD(X) // Exists in the Baseline
//
// If we've patched only the first directive, the macro definition would've
// been wrong for the rest of the file, since patch is applied after the
// baseline preamble.
//
// Note that we deliberately ignore conditional directives and undefs to
// reduce complexity. The former might cause problems because scanning is
// imprecise and might pick directives from disabled regions.
for (const auto &TD : ModifiedScan->TextualDirectives) {
Patch << "#line " << TD.DirectiveLine << '\n';
Patch << TD.Text << '\n';
}
}
dlog("Created preamble patch: {0}", Patch.str());
Patch.flush();
return PP;
}

Expand Down Expand Up @@ -380,5 +512,24 @@ PreamblePatch PreamblePatch::unmodified(const PreambleData &Preamble) {
return PP;
}

SourceLocation translatePreamblePatchLocation(SourceLocation Loc,
const SourceManager &SM) {
auto DefFile = SM.getFileID(Loc);
if (auto *FE = SM.getFileEntryForID(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;
}
} // namespace clangd
} // namespace clang
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/Preamble.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ class PreamblePatch {
/// using the presumed-location mechanism.
std::vector<Inclusion> preambleIncludes() const;

/// Returns textual patch contents.
llvm::StringRef text() const { return PatchContents; }

private:
PreamblePatch() = default;
std::string PatchContents;
Expand All @@ -128,6 +131,11 @@ class PreamblePatch {
std::vector<Inclusion> PreambleIncludes;
};

/// 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

Expand Down
5 changes: 4 additions & 1 deletion clang-tools-extra/clangd/SourceCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "SourceCode.h"

#include "FuzzyMatch.h"
#include "Preamble.h"
#include "Protocol.h"
#include "refactor/Tweak.h"
#include "support/Context.h"
Expand Down Expand Up @@ -961,7 +962,9 @@ llvm::Optional<DefinedMacro> locateMacroAt(const syntax::Token &SpelledTok,
Loc = Loc.getLocWithOffset(-1);
MacroDefinition MacroDef = PP.getMacroDefinitionAtLoc(IdentifierInfo, Loc);
if (auto *MI = MacroDef.getMacroInfo())
return DefinedMacro{IdentifierInfo->getName(), MI};
return DefinedMacro{
IdentifierInfo->getName(), MI,
translatePreamblePatchLocation(MI->getDefinitionLoc(), SM)};
return None;
}

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/SourceCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ EligibleRegion getEligiblePoints(llvm::StringRef Code,
struct DefinedMacro {
llvm::StringRef Name;
const MacroInfo *Info;
/// Location of the identifier that names the macro.
/// Unlike Info->Location, this translates preamble-patch locations to
/// main-file locations.
SourceLocation NameLoc;
};
/// Gets the macro referenced by \p SpelledTok. It must be a spelled token
/// aligned to the beginning of an identifier.
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ llvm::Optional<LocatedSymbol>
locateMacroReferent(const syntax::Token &TouchedIdentifier, ParsedAST &AST,
llvm::StringRef MainFilePath) {
if (auto M = locateMacroAt(TouchedIdentifier, AST.getPreprocessor())) {
if (auto Loc = makeLocation(AST.getASTContext(),
M->Info->getDefinitionLoc(), MainFilePath)) {
if (auto Loc =
makeLocation(AST.getASTContext(), M->NameLoc, MainFilePath)) {
LocatedSymbol Macro;
Macro.Name = std::string(M->Name);
Macro.PreferredDeclaration = *Loc;
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/unittests/HeadersTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ TEST(Headers, NoHeaderSearchInfo) {
}

TEST_F(HeadersTest, PresumedLocations) {
std::string HeaderFile = "implicit_include.h";
std::string HeaderFile = "__preamble_patch__.h";

// Line map inclusion back to main file.
std::string HeaderContents =
Expand All @@ -317,7 +317,7 @@ TEST_F(HeadersTest, PresumedLocations) {
FS.Files[HeaderFile] = HeaderContents;

// Including through non-builtin file has no effects.
FS.Files[MainFile] = "#include \"implicit_include.h\"\n\n";
FS.Files[MainFile] = "#include \"__preamble_patch__.h\"\n\n";
EXPECT_THAT(collectIncludes().MainFileIncludes,
Not(Contains(Written("<a.h>"))));

Expand Down
303 changes: 303 additions & 0 deletions clang-tools-extra/clangd/unittests/PreambleTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
#include "Annotations.h"
#include "Compiler.h"
#include "Headers.h"
#include "Hover.h"
#include "Preamble.h"
#include "SourceCode.h"
#include "TestFS.h"
#include "TestTU.h"
#include "XRefs.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/PrecompiledPreamble.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "gmock/gmock.h"
Expand All @@ -26,7 +31,10 @@
#include <string>
#include <vector>

using testing::Contains;
using testing::Field;
using testing::Matcher;
using testing::MatchesRegex;

namespace clang {
namespace clangd {
Expand Down Expand Up @@ -181,6 +189,301 @@ TEST(PreamblePatchTest, PatchesPreambleIncludes) {
ElementsAre(AllOf(Field(&Inclusion::Written, "\"a.h\""),
Field(&Inclusion::Resolved, testPath("a.h")))));
}

llvm::Optional<ParsedAST> createPatchedAST(llvm::StringRef Baseline,
llvm::StringRef Modified) {
auto BaselinePreamble = TestTU::withCode(Baseline).preamble();
if (!BaselinePreamble) {
ADD_FAILURE() << "Failed to build baseline preamble";
return llvm::None;
}

IgnoreDiagnostics Diags;
auto TU = TestTU::withCode(Modified);
auto CI = buildCompilerInvocation(TU.inputs(), Diags);
if (!CI) {
ADD_FAILURE() << "Failed to build compiler invocation";
return llvm::None;
}
return ParsedAST::build(testPath(TU.Filename), TU.inputs(), 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 "";
}
auto TU = TestTU::withCode(Modified);
return PreamblePatch::create(testPath("main.cpp"), TU.inputs(),
*BaselinePreamble)
.text()
.str();
}

TEST(PreamblePatchTest, Define) {
// BAR should be defined while parsing the AST.
struct {
llvm::StringLiteral Contents;
llvm::StringLiteral ExpectedPatch;
} Cases[] = {
{
R"cpp(
#define BAR
[[BAR]])cpp",
R"cpp(#line 0 ".*main.cpp"
#line 2
#define BAR
)cpp",
},
// multiline macro
{
R"cpp(
#define BAR \
[[BAR]])cpp",
R"cpp(#line 0 ".*main.cpp"
#line 2
#define BAR
)cpp",
},
// multiline macro
{
R"cpp(
#define \
BAR
[[BAR]])cpp",
R"cpp(#line 0 ".*main.cpp"
#line 3
#define BAR
)cpp",
},
};

for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Contents);
Annotations Modified(Case.Contents);
EXPECT_THAT(getPreamblePatch("", Modified.code()),
MatchesRegex(Case.ExpectedPatch.str()));

auto AST = createPatchedAST("", Modified.code());
ASSERT_TRUE(AST);
EXPECT_THAT(AST->getDiagnostics(),
Not(Contains(Field(&Diag::Range, 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"
#line 2
#define BAR\(X, Y\) X Y
#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);
EXPECT_THAT(AST->getDiagnostics(),
Not(Contains(Field(&Diag::Range, Modified.range()))));
}

TEST(PreamblePatchTest, LocateMacroAtWorks) {
struct {
llvm::StringLiteral Baseline;
llvm::StringLiteral 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");
}
}

TEST(PreamblePatchTest, RefsToMacros) {
struct {
llvm::StringLiteral Baseline;
llvm::StringLiteral 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<Location>> ExpectedLocations;
for (const auto &R : Modified.ranges())
ExpectedLocations.push_back(Field(&Location::range, 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);
}
} // namespace
} // namespace clangd
} // namespace clang