diff --git a/include/Compiler/Directive.h b/include/Compiler/Directive.h index b221dd3e..ea550af8 100644 --- a/include/Compiler/Directive.h +++ b/include/Compiler/Directive.h @@ -8,33 +8,25 @@ namespace clice { /// Information about `#include` directive. struct Include { - /// The file id of the included file. If the file is skipped because of - /// include guard, or `#pragma once`, this will be invalid. + /// whether this header is skipped because of #pragma once + /// or a header guard macro. + bool skipped; + + /// The file id of included file. clang::FileID fid; /// Location of the `include`. clang::SourceLocation location; - /// The name of the included file. - std::string fileName; - - /// If the file was found via an absolute include path, `searchPath` will be empty. - /// For framework includes, the `searchPath` and `relativePath` will be split up. - /// - /// For example, if an include of "Some/Some.h" is found via the framework path - /// "path/to/Frameworks/Some.framework/Headers/Some.h" - /// `searchPath` will be "path/to/Frameworks/Some.framework/Headers" - /// `relativePath` will be "Some.h". - std::string searchPath; - - /// See `searchPath`. - std::string relativePath; + /// The range of filename(includes `""` or `<>`). + clang::SourceRange fileNameRange; }; /// Information about `__has_include` directive. struct HasInclude { - /// The path of the included file. - llvm::StringRef path; + /// The file id of included file, may be empty if there is + /// not such file. + clang::FileID fid; /// Location of the filename token start. clang::SourceLocation location; diff --git a/include/Feature/DocumentLink.h b/include/Feature/DocumentLink.h index 6f70f09b..c14eebd0 100644 --- a/include/Feature/DocumentLink.h +++ b/include/Feature/DocumentLink.h @@ -1 +1,33 @@ #pragma once + +#include + +#include "AST/SourceCode.h" +#include "Index/Shared.h" + +namespace clice { + +class ASTInfo; + +namespace feature { + +struct DocumentLink { + /// The range of the whole link. + LocalSourceRange range; + + /// The target string path. + std::string file; +}; + +using DocumentLinkResult = std::vector; + +/// Generate document link for main file. +DocumentLinkResult documentLink(ASTInfo& AST); + +/// Generate document link for all source file. +index::Shared indexDocumentLink(ASTInfo& AST); + +} // namespace feature + +} // namespace clice + diff --git a/include/Index/FeatureIndex.h b/include/Index/FeatureIndex.h index 62f1795b..424f8177 100644 --- a/include/Index/FeatureIndex.h +++ b/include/Index/FeatureIndex.h @@ -5,6 +5,7 @@ #include "Shared.h" #include "Feature/SemanticTokens.h" #include "Feature/FoldingRange.h" +#include "Feature/DocumentLink.h" #include "llvm/ADT/DenseMap.h" #include "clang/Basic/SourceLocation.h" @@ -35,6 +36,8 @@ public: std::vector foldingRanges() const; + std::vector documentLinks() const; + public: char* base; std::size_t size; diff --git a/include/Server/Indexer.h b/include/Server/Indexer.h index c2709cc1..05b148f7 100644 --- a/include/Server/Indexer.h +++ b/include/Server/Indexer.h @@ -98,6 +98,8 @@ public: async::Task> foldingRanges(llvm::StringRef file) const; + async::Task> documentLinks(llvm::StringRef file) const; + private: async::Task<> index(std::string file); diff --git a/include/Server/LSPConverter.h b/include/Server/LSPConverter.h index 7596ac6b..b384ca26 100644 --- a/include/Server/LSPConverter.h +++ b/include/Server/LSPConverter.h @@ -7,6 +7,7 @@ #include "Feature/FoldingRange.h" #include "Feature/DocumentSymbol.h" #include "Feature/SemanticTokens.h" +#include "Feature/DocumentLink.h" #include "Server/Protocol.h" namespace clice { @@ -36,10 +37,15 @@ public: std::vector transform(llvm::StringRef content, llvm::ArrayRef foldings); + std::vector transform(llvm::StringRef content, + llvm::ArrayRef links); + Result convert(llvm::StringRef path, llvm::ArrayRef tokens); Result convert(llvm::StringRef path, llvm::ArrayRef foldings); + Result convert(llvm::StringRef path, llvm::ArrayRef links); + Result convert(const feature::Hover& hover); private: diff --git a/include/Server/Protocol.h b/include/Server/Protocol.h index 2f9cd2f4..7524ea8b 100644 --- a/include/Server/Protocol.h +++ b/include/Server/Protocol.h @@ -289,6 +289,11 @@ enum class FoldingRangeKind { Region, }; +struct DocumentLink { + Range range; + URI target; +}; + /// Represents a folding range. To be valid, start and end line must be bigger /// than zero and smaller than the number of lines in the document. Clients /// are free to ignore invalid ranges. @@ -366,6 +371,14 @@ struct ServerCapabilities { /// The server provides semantic tokens support. SemanticTokensOptions semanticTokensProvider; + struct DocumentLinkOptions { + /// Document links have a resolve provider as well. + bool resolveProvider = false; + }; + + /// The server provides document link support. + DocumentLinkOptions documentLinkProvider; + /// The server provides folding provider support. bool foldingRangeProvider = true; }; @@ -425,6 +438,8 @@ using SemanticTokensParams = TextDocumentParams; using FoldingRangeParams = TextDocumentParams; +using DocumentLinkParams = TextDocumentParams; + struct HeaderContext { /// The path of context file. std::string file; diff --git a/src/Compiler/AST.cpp b/src/Compiler/AST.cpp index 61005d3f..e314bc96 100644 --- a/src/Compiler/AST.cpp +++ b/src/Compiler/AST.cpp @@ -7,7 +7,7 @@ const llvm::DenseSet& ASTInfo::files() { /// FIXME: handle preamble and embed file id. for(auto& [fid, diretive]: directives()) { for(auto& include: diretive.includes) { - if(include.fid.isValid()) { + if(!include.skipped) { allFiles.insert(include.fid); } } @@ -24,15 +24,15 @@ std::vector ASTInfo::deps() { for(auto& [fid, diretive]: directives()) { for(auto& include: diretive.includes) { - if(include.fid.isValid()) { - auto entry = srcMgr().getFileEntryRefForID(include.fid); - assert(entry && "Invalid file entry"); - deps.try_emplace(entry->getName()); + if(!include.skipped) { + deps.try_emplace(getFilePath(include.fid)); } } for(auto& hasInclude: diretive.hasIncludes) { - deps.try_emplace(hasInclude.path); + if(hasInclude.fid.isValid()) { + deps.try_emplace(getFilePath(hasInclude.fid)); + } } } @@ -46,6 +46,7 @@ std::vector ASTInfo::deps() { } llvm::StringRef ASTInfo::getFilePath(clang::FileID fid) { + assert(fid.isValid() && "Invalid fid"); if(auto it = pathCache.find(fid); it != pathCache.end()) { return it->second; } diff --git a/src/Compiler/Directive.cpp b/src/Compiler/Directive.cpp index a563dcb0..2d528f77 100644 --- a/src/Compiler/Directive.cpp +++ b/src/Compiler/Directive.cpp @@ -78,6 +78,28 @@ struct PPCallback : public clang::PPCallbacks { /// Rewritten Preprocessor Callbacks /// ============================================================================ + void InclusionDirective(clang::SourceLocation hashLoc, + const clang::Token& includeTok, + llvm::StringRef, + bool, + clang::CharSourceRange filenameRange, + clang::OptionalFileEntryRef, + llvm::StringRef, + llvm::StringRef, + const clang::Module*, + bool, + clang::SrcMgr::CharacteristicKind) override { + prevFID = SM.getFileID(hashLoc); + + /// An `IncludeDirective` call is always followed by either a `LexedFileChanged` + /// or a `FileSkipped`. so we cannot get the file id of included file here. + directives[prevFID].includes.emplace_back(Include{ + .fid = {}, + .location = includeTok.getLocation(), + .fileNameRange = filenameRange.getAsRange(), + }); + } + void LexedFileChanged(clang::FileID currFID, LexedFileChangeReason reason, clang::SrcMgr::CharacteristicKind, @@ -85,29 +107,27 @@ struct PPCallback : public clang::PPCallbacks { clang::SourceLocation) override { if(reason == LexedFileChangeReason::EnterFile && currFID.isValid() && prevFID.isValid() && this->prevFID.isValid() && prevFID == this->prevFID) { - directives[prevFID].includes.back().fid = currFID; + /// Once the file has changed, it means that the last include is not skipped. + /// Therefore, we initialize its file id with the current file id. + auto& include = directives[prevFID].includes.back(); + include.skipped = false; + include.fid = currFID; } } - void InclusionDirective(clang::SourceLocation hashLoc, - const clang::Token& includeTok, - llvm::StringRef fileName, - bool, - clang::CharSourceRange, - clang::OptionalFileEntryRef, - llvm::StringRef searchPath, - llvm::StringRef relativePath, - const clang::Module*, - bool, - clang::SrcMgr::CharacteristicKind) override { - prevFID = SM.getFileID(hashLoc); - directives[prevFID].includes.emplace_back(Include{ - .fid = {}, - .location = includeTok.getLocation(), - .fileName = fileName.str(), - .searchPath = searchPath.str(), - .relativePath = relativePath.str(), - }); + void FileSkipped(const clang::FileEntryRef& file, + const clang::Token&, + clang::SrcMgr::CharacteristicKind) override { + if(prevFID.isValid()) { + /// File with guard will have only one file id in `SourceManager`, use + /// `translateFile` to find it. + auto& include = directives[prevFID].includes.back(); + include.skipped = true; + + /// Get the FileID for the given file. If the source file is included multiple + /// times, the FileID will be the first inclusion. + include.fid = SM.translateFile(file); + } } void HasInclude(clang::SourceLocation location, @@ -115,10 +135,12 @@ struct PPCallback : public clang::PPCallbacks { bool, clang::OptionalFileEntryRef file, clang::SrcMgr::CharacteristicKind) override { - directives[SM.getFileID(location)].hasIncludes.emplace_back(clice::HasInclude{ - file ? file->getName() : "", - location, - }); + clang::FileID fid; + if(file) { + fid = SM.translateFile(*file); + } + + directives[SM.getFileID(location)].hasIncludes.emplace_back(fid, location); } void PragmaDirective(clang::SourceLocation Loc, diff --git a/src/Compiler/Preamble.cpp b/src/Compiler/Preamble.cpp index 4bc9a8ec..a364d478 100644 --- a/src/Compiler/Preamble.cpp +++ b/src/Compiler/Preamble.cpp @@ -26,8 +26,7 @@ std::vector computePreambleBounds(llvm::StringRef content) { bool isAfterModule = false; auto addResult = [&](const clang::Token& token) { - auto offset = - token.getLocation().getRawEncoding() - fakeLoc.getRawEncoding() + token.getLength(); + auto offset = token.getEndLoc().getRawEncoding() - fakeLoc.getRawEncoding(); if(result.empty() || result.back() != offset) { result.emplace_back(offset); } diff --git a/src/Feature/DocumentLink.cpp b/src/Feature/DocumentLink.cpp new file mode 100644 index 00000000..c89179c4 --- /dev/null +++ b/src/Feature/DocumentLink.cpp @@ -0,0 +1,60 @@ +#include "Compiler/AST.h" +#include "Feature/DocumentLink.h" +#include "Support/Ranges.h" +#include "Support/Compare.h" + +namespace clice::feature { + +namespace {} + +DocumentLinkResult documentLink(ASTInfo& AST); + +index::Shared indexDocumentLink(ASTInfo& AST) { + index::Shared result; + + for(auto& [fid, diretives]: AST.directives()) { + for(auto& include: diretives.includes) { + auto [_, range] = AST.toLocalRange(include.fileNameRange); + result[fid].emplace_back(range, AST.getFilePath(include.fid).str()); + } + + auto content = AST.getFileContent(fid); + for(auto& hasInclude: diretives.hasIncludes) { + /// If the include path is empty, skip it. + if(hasInclude.fid.isInvalid()) { + continue; + } + + auto location = hasInclude.location; + auto [_, offset] = AST.getDecomposedLoc(location); + + auto subContent = content.substr(offset); + + bool isFirst = true; + std::uint32_t endOffset = offset; + tokenize(subContent, [&](const clang::Token& token) { + if(token.is(clang::tok::r_paren) || (!isFirst && token.isAtStartOfLine())) { + return false; + } + + if(isFirst) { + isFirst = false; + } + + endOffset = offset + token.getEndLoc().getRawEncoding() - fakeLoc.getRawEncoding(); + return true; + }); + + result[fid].emplace_back(LocalSourceRange{offset, endOffset}, + AST.getFilePath(hasInclude.fid).str()); + } + } + + for(auto& [_, links]: result) { + ranges::sort(links, refl::less); + } + + return result; +} + +} // namespace clice::feature diff --git a/src/Index/FeatureIndex.cpp b/src/Index/FeatureIndex.cpp index 74d8eb84..1a9d528d 100644 --- a/src/Index/FeatureIndex.cpp +++ b/src/Index/FeatureIndex.cpp @@ -9,6 +9,7 @@ namespace memory { struct FeatureIndex { std::vector tokens; std::vector foldings; + std::vector links; }; } // namespace memory @@ -24,17 +25,13 @@ Shared indexFeature(ASTInfo& info) { indices[fid].foldings = std::move(result); } + for(auto&& [fid, result]: feature::indexDocumentLink(info)) { + indices[fid].links = std::move(result); + } + Shared result; for(auto&& [fid, index]: indices) { - /// FIXME: Figure out why index result will contain them. - auto& SM = info.srcMgr(); - auto loc = SM.getLocForStartOfFile(fid); - if(SM.isWrittenInBuiltinFile(loc) || SM.isWrittenInCommandLineFile(loc) || - SM.isWrittenInScratchSpace(loc)) { - continue; - } - auto [buffer, size] = binary::binarify(static_cast(index)); result.try_emplace( fid, @@ -65,4 +62,19 @@ std::vector FeatureIndex::foldingRanges() const { return result; } +std::vector FeatureIndex::documentLinks() const { + auto array = binary::Proxy{base, base}.get<"links">(); + + std::vector result; + result.reserve(array.size()); + + /// FIXME: Use iterator or other thing to make cast easier. + for(std::size_t i = 0, n = array.size(); i < n; ++i) { + auto&& range = array[i]; + result.emplace_back(range.get<"range">().value(), range.get<"file">().as_string().str()); + } + + return result; +} + } // namespace clice::index diff --git a/src/Server/IncludeGraph.cpp b/src/Server/IncludeGraph.cpp index 6b60b6cf..aaa813ab 100644 --- a/src/Server/IncludeGraph.cpp +++ b/src/Server/IncludeGraph.cpp @@ -221,7 +221,7 @@ void IncludeGraph::addContexts(ASTInfo& AST, /// If the include is invalid, it indicates that the file is skipped because of /// include guard, or `#pragma once`. Such file cannot provide header context. /// So we just skip it. - if(include.fid.isInvalid()) { + if(include.skipped) { continue; } diff --git a/src/Server/Indexer.cpp b/src/Server/Indexer.cpp index 140b1fe7..41cdc43d 100644 --- a/src/Server/Indexer.cpp +++ b/src/Server/Indexer.cpp @@ -435,4 +435,16 @@ async::Task> Indexer::foldingRanges(llvm::Str co_return index->foldingRanges(); } +async::Task> Indexer::documentLinks(llvm::StringRef file) const { + std::vector result; + + std::string buffer; + auto index = co_await getFeatureIndex(buffer, file); + if(!index) { + co_return result; + } + + co_return index->documentLinks(); +} + } // namespace clice diff --git a/src/Server/LSPConverter.cpp b/src/Server/LSPConverter.cpp index 6e1c38be..c5dc4666 100644 --- a/src/Server/LSPConverter.cpp +++ b/src/Server/LSPConverter.cpp @@ -351,6 +351,22 @@ std::vector return result; } +std::vector + LSPConverter::transform(llvm::StringRef content, llvm::ArrayRef links) { + PositionConverter converter(content, encoding()); + + std::vector result; + for(auto& link: links) { + proto::Range range{ + converter.toPosition(link.range.begin), + converter.toPosition(link.range.end), + }; + result.emplace_back(range, SourceConverter::toURI(link.file)); + } + + return result; +} + LSPConverter::Result LSPConverter::convert(llvm::StringRef path, llvm::ArrayRef tokens) { auto file = co_await async::fs::read(path.str()); @@ -371,6 +387,16 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path, co_return json::serialize(transform(content, foldings)); } +LSPConverter::Result LSPConverter::convert(llvm::StringRef path, + llvm::ArrayRef links) { + auto file = co_await async::fs::read(path.str()); + if(!file) { + co_return json::Value(nullptr); + } + llvm::StringRef content = *file; + co_return json::serialize(transform(content, links)); +} + LSPConverter::Result LSPConverter::convert(const feature::Hover& hover) { /// FIXME: Implement hover information render here. co_return json::Value(""); diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 78aee909..a6dc488a 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -86,6 +86,11 @@ async::Task Server::onTextDocument(llvm::StringRef method, json::Va auto path = SourceConverter::toPath(params2.textDocument.uri); auto foldings = co_await indexer.foldingRanges(path); co_return co_await converter.convert(path, foldings); + } else if(method == "documentLink") { + auto params2 = json::deserialize(value); + auto path = SourceConverter::toPath(params2.textDocument.uri); + auto links = co_await indexer.documentLinks(path); + co_return co_await converter.convert(path, links); } co_return json::Value(nullptr); diff --git a/unittests/Compiler/Directive.cpp b/unittests/Compiler/Directive.cpp index d8f5d0ac..015f8c3d 100644 --- a/unittests/Compiler/Directive.cpp +++ b/unittests/Compiler/Directive.cpp @@ -24,42 +24,56 @@ struct Directive : ::testing::Test, Tester { pragmas = info->directives()[fid].pragmas; } - void EXPECT_INCLUDE(std::size_t index, llvm::StringRef position, llvm::StringRef path, + void EXPECT_INCLUDE(std::size_t index, + llvm::StringRef position, + llvm::StringRef path, std::source_location current = std::source_location::current()) { auto& include = includes[index]; - auto entry = SM->getFileEntryRefForID(include.fid); - EXPECT_EQ(SourceConverter().toPosition(include.location, *SM), pos(position), current); - EXPECT_EQ(entry ? entry->getName() : "", path, current); + auto [_, offset] = info->getDecomposedLoc(include.location); + EXPECT_EQ(offset, this->offset(position), current); + EXPECT_EQ(include.skipped ? "" : info->getFilePath(include.fid), path, current); } - void EXPECT_HAS_INCLUDE(std::size_t index, llvm::StringRef position, llvm::StringRef path, + void EXPECT_HAS_INCLUDE(std::size_t index, + llvm::StringRef position, + llvm::StringRef path, std::source_location current = std::source_location::current()) { auto& hasInclude = hasIncludes[index]; - EXPECT_EQ(SourceConverter().toPosition(hasInclude.location, *SM), pos(position), current); - EXPECT_EQ(hasInclude.path, path, current); + auto [_, offset] = info->getDecomposedLoc(hasInclude.location); + EXPECT_EQ(offset, this->offset(position), current); + EXPECT_EQ(hasInclude.fid.isValid() ? info->getFilePath(hasInclude.fid) : "", path, current); } - void EXPECT_CON(std::size_t index, Condition::BranchKind kind, llvm::StringRef position, + void EXPECT_CON(std::size_t index, + Condition::BranchKind kind, + llvm::StringRef position, std::source_location current = std::source_location::current()) { auto& condition = conditions[index]; + auto [_, offset] = info->getDecomposedLoc(condition.loc); EXPECT_EQ(condition.kind, kind, current); - EXPECT_EQ(SourceConverter().toPosition(condition.loc, *SM), pos(position), current); + EXPECT_EQ(offset, this->offset(position), current); } - void EXPECT_MACRO(std::size_t index, MacroRef::Kind kind, llvm::StringRef position, + void EXPECT_MACRO(std::size_t index, + MacroRef::Kind kind, + llvm::StringRef position, std::source_location current = std::source_location::current()) { auto& macro = macros[index]; + auto [_, offset] = info->getDecomposedLoc(macro.loc); EXPECT_EQ(macro.kind, kind, current); - EXPECT_EQ(SourceConverter().toPosition(macro.loc, *SM), pos(position), current); + EXPECT_EQ(offset, this->offset(position), current); } - void EXPECT_PRAGMA(std::size_t index, Pragma::Kind kind, llvm::StringRef position, + void EXPECT_PRAGMA(std::size_t index, + Pragma::Kind kind, + llvm::StringRef position, llvm::StringRef text, std::source_location current = std::source_location::current()) { auto& pragma = pragmas[index]; + auto [_, offset] = info->getDecomposedLoc(pragma.loc); EXPECT_EQ(pragma.kind, kind, current); EXPECT_EQ(pragma.stmt, text, current); - EXPECT_EQ(SourceConverter().toPosition(pragma.loc, *SM), pos(position), current); + EXPECT_EQ(offset, this->offset(position), current); } }; @@ -107,26 +121,31 @@ TEST_F(Directive, Include) { EXPECT_INCLUDE(3, "3", ""); EXPECT_INCLUDE(4, "4", pguard_macro); EXPECT_INCLUDE(5, "5", ""); + + /// TODO: test include source range. } TEST_F(Directive, HasInclude) { const char* test = ""; - const char* main = R"cpp( +#include "test.h" #if __has_include($(0)"test.h") #endif + +#if __has_include($(1)"test2.h") +#endif )cpp"; addMain("main.cpp", main); - llvm::SmallString<128> path; - path::append(path, ".", "test.h"); + auto path = path::join(".", "test.h"); addFile(path, test); run(); - EXPECT_EQ(hasIncludes.size(), 1); + EXPECT_EQ(hasIncludes.size(), 2); EXPECT_HAS_INCLUDE(0, "0", path); + EXPECT_HAS_INCLUDE(1, "1", ""); } TEST_F(Directive, Condition) { diff --git a/unittests/Feature/DocumentLink.cpp b/unittests/Feature/DocumentLink.cpp new file mode 100644 index 00000000..68660736 --- /dev/null +++ b/unittests/Feature/DocumentLink.cpp @@ -0,0 +1,102 @@ +#include "Test/CTest.h" +#include "Feature/DocumentLink.h" + +namespace clice::testing { + +namespace { + +struct DocumentLink : ::testing::Test, Tester { + index::Shared result; + + void run(llvm::StringRef code) { + addMain("main.cpp", code); + Tester::run(); + result = feature::indexDocumentLink(*info); + } + + void EXPECT_LINK(uint32_t index, + llvm::StringRef begin, + llvm::StringRef end, + llvm::StringRef path, + std::source_location current = std::source_location::current()) { + auto& link = result[info->getInterestedFile()][index]; + EXPECT_EQ(link.range.begin, offset(begin), current); + EXPECT_EQ(link.range.end, offset(end), current); + EXPECT_EQ(link.file, path, current); + } + + void dump() { + println("{}", clice::dump(result[info->getInterestedFile()])); + } +}; + +TEST_F(DocumentLink, Include) { + const char* test = ""; + + const char* test2 = R"cpp( +#include "test.h" +)cpp"; + + const char* pragma_once = R"cpp( +#pragma once +)cpp"; + + const char* guard_macro = R"cpp( +#ifndef TEST3_H +#define TEST3_H +#endif +)cpp"; + + const char* main = R"cpp( +#include $(0)"test.h"$(0e) +#include $(1)"test.h"$(1e) +#include $(2)"pragma_once.h"$(2e) +#include $(3)"pragma_once.h"$(3e) +#include $(4)"guard_macro.h"$(4e) +#include $(5)"guard_macro.h"$(5e) +)cpp"; + + auto ptest = path::join(".", "test.h"); + auto ppragma_once = path::join(".", "pragma_once.h"); + auto pguard_macro = path::join(".", "guard_macro.h"); + + addFile(ptest, test); + addFile(ppragma_once, pragma_once); + addFile(pguard_macro, guard_macro); + run(main); + + auto& links = result[info->getInterestedFile()]; + EXPECT_EQ(links.size(), 6); + EXPECT_LINK(0, "0", "0e", ptest); + EXPECT_LINK(1, "1", "1e", ptest); + EXPECT_LINK(2, "2", "2e", ppragma_once); + EXPECT_LINK(3, "3", "3e", ppragma_once); + EXPECT_LINK(4, "4", "4e", pguard_macro); + EXPECT_LINK(5, "5", "5e", pguard_macro); +} + +TEST_F(DocumentLink, HasInclude) { + const char* test = ""; + const char* main = R"cpp( +#include $(0)"test.h"$(0e) +#if __has_include($(1)"test.h"$(1e)) +#endif + +#if __has_include("test2.h") +#endif +)cpp"; + + auto path = path::join(".", "test.h"); + addFile(path, test); + + run(main); + + auto& links = result[info->getInterestedFile()]; + EXPECT_EQ(links.size(), 2); + EXPECT_LINK(0, "0", "0e", path); + EXPECT_LINK(1, "1", "1e", path); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Feature/SemanticTokens.cpp b/unittests/Feature/SemanticTokens.cpp index 22868d27..12bf32b9 100644 --- a/unittests/Feature/SemanticTokens.cpp +++ b/unittests/Feature/SemanticTokens.cpp @@ -74,6 +74,8 @@ $(2)#include $(3)"stddef.h" $(4)# $(5)include $(6)"stddef.h" )cpp"); + /// FIXME: Included file could be macro. + EXPECT_TOKEN("0", Directive, 8); EXPECT_TOKEN("1", Header, 10); EXPECT_TOKEN("2", Directive, 8);