diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 089f8158c9aa5..45cd90e6aa0b5 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -859,17 +859,47 @@ std::vector getDocumentLinks(ParsedAST &AST) { for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) { if (Inc.Resolved.empty()) continue; + + // Get the location of the # symbole of the "#include ..." statement auto HashLoc = SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); + + // get the # Token itself, std::next to get the "include" token and the + // first token after (aka "File Token") const auto *HashTok = AST.getTokens().spelledTokenContaining(HashLoc); assert(HashTok && "got inclusion at wrong offset"); const auto *IncludeTok = std::next(HashTok); const auto *FileTok = std::next(IncludeTok); - // FileTok->range is not sufficient here, as raw lexing wouldn't yield - // correct tokens for angled filenames. Hence we explicitly use - // Inc.Written's length. - auto FileRange = - syntax::FileRange(SM, FileTok->location(), Inc.Written.length()) - .toCharRange(SM); + + // The File Token can either be of kind : + // "less" if using the "#include new-line" syntax + // "string_literal" if using the "#include "q-char-sequence" new-line" + // syntax something else (most likely "identifier") if using the "#include + // pp-tokens new-line" syntax (#include with macro argument) + + CharSourceRange FileRange; + + if (FileTok->kind() == tok::TokenKind::less) { + // FileTok->range would only include the '<' char. Hence we explicitly use + // Inc.Written's length. + FileRange = + syntax::FileRange(SM, FileTok->location(), Inc.Written.length()) + .toCharRange(SM); + } else if (FileTok->kind() == tok::TokenKind::string_literal) { + // FileTok->range includes the quotes for string literals so just return + // it. + FileRange = FileTok->range(SM).toCharRange(SM); + } else { + // FileTok is the first Token of a macro spelling + + // Report the range of the first token (as it should be the macro + // identifier) + // We could use the AST to find the last spelled token of the macro and + // report a range spanning the full macro expression, but it would require + // using token-buffers that are deemed too unstable and crash-prone + // due to optimizations in cland + + FileRange = FileTok->range(SM).toCharRange(SM); + } Result.push_back( DocumentLink({halfOpenToRange(SM, FileRange), diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index b04d6431f89f9..17204a47ba3bc 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -2784,14 +2784,24 @@ TEST(GetNonLocalDeclRefs, All) { TEST(DocumentLinks, All) { Annotations MainCpp(R"cpp( + #define HEADER_AA "faa.h" + #define HEADER_BB "fbb.h" + #define GET_HEADER(X) HEADER_ ## X + #/*comments*/include /*comments*/ $foo[["foo.h"]] //more comments int end_of_preamble = 0; #include $bar[[]] + #include $AA[[GET_HEADER]](AA) // Some comment ! + # /* What about */ \ + include /* multiple line */ \ + $BB[[GET_HEADER]]( /* statements ? */ \ + BB /* :) */ ) )cpp"); TestTU TU; TU.Code = std::string(MainCpp.code()); - TU.AdditionalFiles = {{"foo.h", ""}, {"bar.h", ""}}; + TU.AdditionalFiles = { + {"faa.h", ""}, {"fbb.h", ""}, {"foo.h", ""}, {"bar.h", ""}}; TU.ExtraArgs = {"-isystem."}; auto AST = TU.build(); @@ -2801,7 +2811,11 @@ TEST(DocumentLinks, All) { DocumentLink({MainCpp.range("foo"), URIForFile::canonicalize(testPath("foo.h"), "")}), DocumentLink({MainCpp.range("bar"), - URIForFile::canonicalize(testPath("bar.h"), "")}))); + URIForFile::canonicalize(testPath("bar.h"), "")}), + DocumentLink({MainCpp.range("AA"), + URIForFile::canonicalize(testPath("faa.h"), "")}), + DocumentLink({MainCpp.range("BB"), + URIForFile::canonicalize(testPath("fbb.h"), "")}))); } } // namespace