Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -25,6 +25,7 @@ TUScheduler.cpp URI.cpp XRefs.cpp + index/CanonicalIncludes.cpp index/FileIndex.cpp index/Index.cpp index/MemIndex.cpp Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -113,7 +113,9 @@ {"renameProvider", true}, {"executeCommandProvider", json::obj{ - {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + {"commands", + {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}}, }}, }}}}); if (Params.rootUri && !Params.rootUri->file.empty()) @@ -169,6 +171,31 @@ // Ideally, we would wait for the response and if there is no error, we // would reply success/failure to the original RPC. call("workspace/applyEdit", ApplyEdit); + } else if (Params.command == + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + auto &FileURI = Params.includeInsertion->textDocument.uri; + auto Code = Server.getDocument(FileURI.file); + if (!Code) + return replyError(ErrorCode::InvalidParams, + ("command " + + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE + + " called on non-added file " + FileURI.file) + .str()); + auto Replaces = Server.insertInclude(FileURI.file, *Code, + Params.includeInsertion->headerUri); + if (!Replaces) { + replyError(ErrorCode::InternalError, + llvm::toString(Replaces.takeError())); + return; + } + auto Edits = replacementsToEdits(*Code, *Replaces); + WorkspaceEdit WE; + WE.changes = {{FileURI.uri(), Edits}}; + + ApplyWorkspaceEditParams ApplyEdit; + ApplyEdit.edit = WE; + reply("Inserted header " + Params.includeInsertion->headerUri); + call("workspace/applyEdit", ApplyEdit); } else { // We should not get here because ExecuteCommandParams would not have // parsed in the first place and this handler should not be called. But if Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -240,6 +240,12 @@ Expected> rename(PathRef File, Position Pos, llvm::StringRef NewName); + /// Inserts a new #include of \o HeaderUri into \p File. \p HeaderUri is an + /// URI that can be resolved to an #include path that is suitable to be + /// inserted. + Expected insertInclude(PathRef File, StringRef Code, + StringRef HeaderUri); + /// Gets current document contents for \p File. Returns None if \p File is not /// currently tracked. /// FIXME(ibiryukov): This function is here to allow offset-to-Position Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -9,12 +9,16 @@ #include "ClangdServer.h" #include "CodeComplete.h" +#include "Compiler.h" #include "SourceCode.h" #include "XRefs.h" #include "index/Merge.h" #include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/PreprocessorOptions.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" @@ -361,6 +365,95 @@ return blockingRunWithAST(WorkScheduler, File, std::move(Action)); } +Expected +ClangdServer::insertInclude(PathRef File, StringRef Code, + llvm::StringRef HeaderUri) { + std::string ToInclude; + if (HeaderUri.startswith("<")) { + ToInclude = HeaderUri; + } else { + auto U = URI::parse(HeaderUri); + if (!U) + return U.takeError(); + auto Resolved = URI::resolve(*U); + if (!Resolved) + return Resolved.takeError(); + + auto FS = FSProvider.getTaggedFileSystem(File).Value; + tooling::CompileCommand CompileCommand = + CompileArgs.getCompileCommand(File); + FS->setCurrentWorkingDirectory(CompileCommand.Directory); + + std::vector Argv; + for (const auto &S : CompileCommand.CommandLine) + Argv.push_back(S.c_str()); + llvm::errs() << "~~~ Build dir:" << CompileCommand.Directory << "\n"; + IgnoringDiagConsumer IgnoreDiags; + auto CI = clang::createInvocationFromCommandLine( + Argv, + CompilerInstance::createDiagnostics(new DiagnosticOptions(), + &IgnoreDiags, false), + FS); + if (!CI) + return llvm::make_error( + "Failed to create a compiler instance for " + File, + llvm::inconvertibleErrorCode()); + CI->getFrontendOpts().DisableFree = false; + CI->getPreprocessorOpts().SingleFileParseMode = true; + + auto Clang = prepareCompilerInstance( + std::move(CI), /*Preamble=*/nullptr, + llvm::MemoryBuffer::getMemBuffer(Code, File), + std::make_shared(), FS, IgnoreDiags); + auto &DiagOpts = Clang->getDiagnosticOpts(); + DiagOpts.IgnoreWarnings = true; + + if (Clang->getFrontendOpts().Inputs.empty()) + return llvm::make_error( + "Empty frontend action inputs empty for file " + *Resolved, + llvm::inconvertibleErrorCode()); + PreprocessOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) + return llvm::make_error( + "Failed to begin preprocessor only action for file " + *Resolved, + llvm::inconvertibleErrorCode()); + + auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); + std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( + *Resolved, CompileCommand.Directory); + + llvm::errs() << "Suggested #include is: " << Suggested << "\n"; + ToInclude = "\"" + Suggested + "\""; + + //auto &FM = Clang->getPreprocessor().getSourceManager().getFileManager(); + //const FileEntry *FE = FM.getFile(*Resolved); + //if (FE) { + // auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); + // std::string Suggested = + // HeaderSearchInfo.suggestPathToFileForDiagnostics(FE); + + // llvm::errs() << "Suggested #include is: " << Suggested << "\n"; + // ToInclude = "\"" + Suggested + "\""; + //} else { + // llvm::errs() << "Failed to get FileEntry for " << *Resolved << "\n"; + // ToInclude = "\"" + *Resolved + "\""; + //} + } + ToInclude = "#include " + ToInclude; + tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, ToInclude); + auto Style = format::getStyle("file", File, "llvm"); + if (!Style) { + llvm::consumeError(Style.takeError()); + // FIXME(ioeric): support fallback style in clangd server. + Style = format::getLLVMStyle(); + } + auto Replaces = + format::cleanupAroundReplacements(Code, tooling::Replacements(R), *Style); + if (!Replaces) + return Replaces.takeError(); + return std::move(*Replaces); +} + llvm::Optional ClangdServer::getDocument(PathRef File) { auto Latest = DraftMgr.getDraft(File); if (!Latest.Draft) Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -19,13 +19,16 @@ #include "Compiler.h" #include "FuzzyMatch.h" #include "Logger.h" +#include "SourceCode.h" #include "Trace.h" #include "index/Index.h" +#include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Index/USRGeneration.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Sema/Sema.h" +#include "clang/Tooling/Core/Replacement.h" #include "llvm/Support/Format.h" #include @@ -249,7 +252,8 @@ } // Builds an LSP completion item. - CompletionItem build(const CompletionItemScores &Scores, + CompletionItem build(llvm::StringRef FileName, llvm::StringRef Contents, + const CompletionItemScores &Scores, const CodeCompleteOptions &Opts, CodeCompletionString *SemaCCS) const { assert(bool(SemaResult) == bool(SemaCCS)); @@ -282,6 +286,16 @@ I.documentation = D->Documentation; if (I.detail.empty()) I.detail = D->CompletionDetail; + if (!D->IncludeURI.empty()) { + Command Cmd; + Cmd.title = "Insert #include"; + Cmd.command = ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE; + IncludeInsertion Insertion; + Insertion.headerUri = D->IncludeURI; + Insertion.textDocument.uri.file = FileName; + Cmd.includeInsertion = std::move(Insertion); + I.command = std::move(Cmd); + } } } I.scoreInfo = Scores; @@ -822,7 +836,8 @@ semaCodeComplete(std::move(RecorderOwner), Opts.getClangCompleteOpts(), SemaCCInput, [&] { if (Recorder.CCSema) - Output = runWithSema(); + Output = runWithSema(SemaCCInput.FileName, + SemaCCInput.Contents); else log("Code complete: no Sema callback, 0 results"); }); @@ -845,7 +860,8 @@ private: // This is called by run() once Sema code completion is done, but before the // Sema data structures are torn down. It does all the real work. - CompletionList runWithSema() { + CompletionList runWithSema(llvm::StringRef FileName, + llvm::StringRef Contents) { Filter = FuzzyMatcher( Recorder.CCSema->getPreprocessor().getCodeCompletionFilter()); // Sema provides the needed context to query the index. @@ -859,7 +875,8 @@ // Convert the results to the desired LSP structs. CompletionList Output; for (auto &C : Top) - Output.items.push_back(toCompletionItem(C.first, C.second)); + Output.items.push_back( + toCompletionItem(FileName, Contents, C.first, C.second)); Output.isIncomplete = Incomplete; return Output; } @@ -944,12 +961,14 @@ Incomplete |= Candidates.push({C, Scores}); } - CompletionItem toCompletionItem(const CompletionCandidate &Candidate, + CompletionItem toCompletionItem(llvm::StringRef FileName, + llvm::StringRef Content, + const CompletionCandidate &Candidate, const CompletionItemScores &Scores) { CodeCompletionString *SemaCCS = nullptr; if (auto *SR = Candidate.SemaResult) SemaCCS = Recorder.codeCompletionString(*SR, Opts.IncludeBriefComments); - return Candidate.build(Scores, Opts, SemaCCS); + return Candidate.build(FileName, Content, Scores, Opts, SemaCCS); } }; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -74,6 +74,7 @@ /// The text document's URI. URIForFile uri; }; +json::Expr toJSON(const TextDocumentIdentifier &); bool fromJSON(const json::Expr &, TextDocumentIdentifier &); struct Position { @@ -372,6 +373,17 @@ bool fromJSON(const json::Expr &, WorkspaceEdit &); json::Expr toJSON(const WorkspaceEdit &WE); +struct IncludeInsertion { + /// The document in which the command was invoked. + TextDocumentIdentifier textDocument; + + std::string headerUri; + /// Note: "documentChanges" is not currently used because currently there is + /// no support for versioned edits. +}; +bool fromJSON(const json::Expr &, IncludeInsertion &); +json::Expr toJSON(const IncludeInsertion &II); + /// Exact commands are not specified in the protocol so we define the /// ones supported by Clangd here. The protocol specifies the command arguments /// to be "any[]" but to make this safer and more manageable, each command we @@ -384,15 +396,31 @@ // Command to apply fix-its. Uses WorkspaceEdit as argument. const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + const static llvm::StringLiteral CLANGD_INSERT_HEADER_INCLUDE; + /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND std::string command; // Arguments llvm::Optional workspaceEdit; + + llvm::Optional includeInsertion; }; bool fromJSON(const json::Expr &, ExecuteCommandParams &); +struct Command { + std::string title; + std::string command; + + // Arguments + + llvm::Optional workspaceEdit; + + llvm::Optional includeInsertion; +}; +json::Expr toJSON(const Command &C); + struct ApplyWorkspaceEditParams { WorkspaceEdit edit; }; @@ -506,12 +534,10 @@ /// themselves. std::vector additionalTextEdits; + llvm::Optional command; // TODO(krasimir): The following optional fields defined by the language // server protocol are unsupported: // - // command?: Command - An optional command that is executed *after* inserting - // this completion. - // // data?: any - A data entry field that is preserved on a completion item // between a completion and a completion resolve request. }; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -54,6 +54,10 @@ return OS << U.uri(); } +json::Expr toJSON(const TextDocumentIdentifier &R) { + return json::obj{{"uri", R.uri}}; +} + bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) { json::ObjectMapper O(Params); return O && O.map("uri", R.uri); @@ -287,6 +291,8 @@ const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = "clangd.applyFix"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE = + "clangd.insertInclude"; bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) { json::ObjectMapper O(Params); @@ -297,10 +303,22 @@ if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) { return Args && Args->size() == 1 && fromJSON(Args->front(), R.workspaceEdit); + } else if (R.command == ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.includeInsertion); } return false; // Unrecognized command. } +json::Expr toJSON(const Command &C) { + auto Cmd = json::obj{{"title", C.title}, {"command", C.command}}; + if (C.workspaceEdit) + Cmd["arguments"] = {*C.workspaceEdit}; + else if (C.includeInsertion) + Cmd["arguments"] = {*C.includeInsertion}; + return std::move(Cmd); +} + json::Expr toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::obj{}; @@ -310,6 +328,16 @@ return json::obj{{"changes", std::move(FileChanges)}}; } +bool fromJSON(const json::Expr &II, IncludeInsertion &R) { + json::ObjectMapper O(II); + return O && O.map("textDocument", R.textDocument) && + O.map("headerUri", R.headerUri); +} +json::Expr toJSON(const IncludeInsertion &II) { + return json::obj{{"textDocument", II.textDocument}, + {"headerUri", II.headerUri}}; +} + json::Expr toJSON(const ApplyWorkspaceEditParams &Params) { return json::obj{{"edit", Params.edit}}; } @@ -341,6 +369,9 @@ Result["textEdit"] = *CI.textEdit; if (!CI.additionalTextEdits.empty()) Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits); + if (CI.command) { + Result["command"] = *CI.command; + } return std::move(Result); } Index: clangd/clients/clangd-vscode/package.json =================================================================== --- clangd/clients/clangd-vscode/package.json +++ clangd/clients/clangd-vscode/package.json @@ -43,8 +43,8 @@ "@types/mocha": "^2.2.32" }, "repository": { - "type": "svn", - "url": "http://llvm.org/svn/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/" + "type": "svn", + "url": "http://llvm.org/svn/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/" }, "contributes": { "configuration": { Index: clangd/global-symbol-builder/CMakeLists.txt =================================================================== --- clangd/global-symbol-builder/CMakeLists.txt +++ clangd/global-symbol-builder/CMakeLists.txt @@ -6,7 +6,7 @@ add_clang_executable(global-symbol-builder GlobalSymbolBuilderMain.cpp - ) +) target_link_libraries(global-symbol-builder PRIVATE Index: clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp =================================================================== --- clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp +++ clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -13,9 +13,11 @@ // //===---------------------------------------------------------------------===// +#include "index/CanonicalIncludes.h" #include "index/Index.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" @@ -54,11 +56,19 @@ class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, + std::unique_ptr Includes, const index::IndexingOptions &Opts, tooling::ExecutionContext *Ctx) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), - Ctx(Ctx), Collector(C) {} + Ctx(Ctx), Collector(C), Includes(std::move(Includes)), + PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } void EndSourceFileAction() override { WrapperFrontendAction::EndSourceFileAction(); @@ -75,6 +85,8 @@ private: tooling::ExecutionContext *Ctx; std::shared_ptr Collector; + std::unique_ptr Includes; + std::unique_ptr PragmaHandler; }; index::IndexingOptions IndexOpts; @@ -83,9 +95,13 @@ IndexOpts.IndexFunctionLocals = false; auto CollectorOpts = SymbolCollector::Options(); CollectorOpts.FallbackDir = AssumedHeaderDir; + CollectorOpts.CollectIncludePath = true; + auto Includes = llvm::make_unique(); + addStandardLibraryMapping(Includes.get()); + CollectorOpts.Includes = Includes.get(); return new WrappedIndexAction( - std::make_shared(std::move(CollectorOpts)), IndexOpts, - Ctx); + std::make_shared(std::move(CollectorOpts)), + std::move(Includes), IndexOpts, Ctx); } tooling::ExecutionContext *Ctx; Index: clangd/index/CanonicalIncludes.h =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.h @@ -0,0 +1,82 @@ +//===-- CanonicalIncludes.h - remap #include header -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines functionalities for remapping #include header for an index +// symbol. For example, we can collect a mapping accoring to IWYU pragma +// comment in a header file, which ask users to include an alternative header +// instead of the header itself; file paths of C++ STL headers that defines +// a STL symbol (e.g. .../bits/basic_string.h) might not be suitable for include +// directly, and we want to map the header path to a system-style header (e.g. +// .../bits/basic_string.h to ). +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H + +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include +#include + +namespace clang { +namespace clangd { + +/// \brief CanonicalIncludes collects all remapping header files. This maps +/// complete header names or header name regex patterns to a given canonical +/// header name. +class CanonicalIncludes { +public: + CanonicalIncludes() = default; + + /// Adds a string-to-string mapping from \p Path to \p CanonicalPath. + void addMapping(llvm::StringRef Path, llvm::StringRef CanonicalPath) { + HeaderMappingTable[Path] = CanonicalPath; + }; + + /// Adds a regex-to-string mapping from \p RE to \p CanonicalPath. + void addRegexMapping(llvm::StringRef RE, llvm::StringRef CanonicalPath); + + /// Check if there is a mapping from \p Header or a regex pattern that matches + /// it to another header name. + /// \param Header A header name. + /// \return \p Header itself if there is no mapping for it; otherwise, return + /// a canonical header name. + llvm::StringRef mapHeader(llvm::StringRef Header) const; + +private: + /// A string-to-string map saving the mapping relationship. + llvm::StringMap HeaderMappingTable; + + // A map from header patterns to header names. This needs to be mutable so + // that we can match again a Regex in a const function member. + // FIXME(ioeric): All the regexes we have so far are suffix matches. The + // performance could be improved by allowing only suffix matches instead of + // arbitrary regexes. + mutable std::vector> + RegexHeaderMappingTable; +}; + +/// Returns a CommentHandler that parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes); + +/// Adds mapping for headers in C++ standard library. +void addStandardLibraryMapping(CanonicalIncludes *Includes); + +} // namespace clangd +} // namespace clang + +#endif //LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_HEADERMAPCOLLECTOR_H Index: clangd/index/CanonicalIncludes.cpp =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.cpp @@ -0,0 +1,707 @@ +//===-- CanonicalIncludes.h - remap #inclue headers--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CanonicalIncludes.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace clangd { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +void CanonicalIncludes::addRegexMapping(llvm::StringRef RE, + llvm::StringRef CanonicalPath) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(RE), CanonicalPath); +} + +llvm::StringRef CanonicalIncludes::mapHeader(llvm::StringRef Header) const { + auto Iter = HeaderMappingTable.find(Header); + if (Iter != HeaderMappingTable.end()) + return Iter->second; + // If there is no complete header name mapping for this header, check the + // regex header mapping. + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes) { + class PragmaCommentHandler : public clang::CommentHandler { + public: + PragmaCommentHandler(CanonicalIncludes *Includes) : Includes(Includes) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + size_t Pos = Text.find(IWYUPragma); + if (Pos == StringRef::npos) + return false; + StringRef RemappingFilePath = Text.substr(Pos + std::strlen(IWYUPragma)); + // FIXME(ioeric): resolve the header and store actual file path. For now, + // we simply assume the written header is suitable to be #included. + Includes->addMapping(PP.getSourceManager().getFilename(Range.getBegin()), + RemappingFilePath.startswith("<") + ? RemappingFilePath.str() + : ("\"" + RemappingFilePath + "\"").str()); + return false; + } + + private: + CanonicalIncludes *const Includes; + }; + return llvm::make_unique(Includes); +} + +void addStandardLibraryMapping(CanonicalIncludes *Includes) { + static const std::vector> + STLPostfixHeaderMap = { + {"include/__stddef_max_align_t.h$", ""}, + {"include/__wmmintrin_aes.h$", ""}, + {"include/__wmmintrin_pclmul.h$", ""}, + {"include/adxintrin.h$", ""}, + {"include/ammintrin.h$", ""}, + {"include/avx2intrin.h$", ""}, + {"include/avx512bwintrin.h$", ""}, + {"include/avx512cdintrin.h$", ""}, + {"include/avx512dqintrin.h$", ""}, + {"include/avx512erintrin.h$", ""}, + {"include/avx512fintrin.h$", ""}, + {"include/avx512ifmaintrin.h$", ""}, + {"include/avx512ifmavlintrin.h$", ""}, + {"include/avx512pfintrin.h$", ""}, + {"include/avx512vbmiintrin.h$", ""}, + {"include/avx512vbmivlintrin.h$", ""}, + {"include/avx512vlbwintrin.h$", ""}, + {"include/avx512vlcdintrin.h$", ""}, + {"include/avx512vldqintrin.h$", ""}, + {"include/avx512vlintrin.h$", ""}, + {"include/avxintrin.h$", ""}, + {"include/bmi2intrin.h$", ""}, + {"include/bmiintrin.h$", ""}, + {"include/emmintrin.h$", ""}, + {"include/f16cintrin.h$", ""}, + {"include/float.h$", ""}, + {"include/fma4intrin.h$", ""}, + {"include/fmaintrin.h$", ""}, + {"include/fxsrintrin.h$", ""}, + {"include/ia32intrin.h$", ""}, + {"include/immintrin.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/limits.h$", ""}, + {"include/lzcntintrin.h$", ""}, + {"include/mm3dnow.h$", ""}, + {"include/mm_malloc.h$", ""}, + {"include/mmintrin.h$", ""}, + {"include/mwaitxintrin.h$", ""}, + {"include/pkuintrin.h$", ""}, + {"include/pmmintrin.h$", ""}, + {"include/popcntintrin.h$", ""}, + {"include/prfchwintrin.h$", ""}, + {"include/rdseedintrin.h$", ""}, + {"include/rtmintrin.h$", ""}, + {"include/shaintrin.h$", ""}, + {"include/smmintrin.h$", ""}, + {"include/stdalign.h$", ""}, + {"include/stdarg.h$", ""}, + {"include/stdbool.h$", ""}, + {"include/stddef.h$", ""}, + {"include/stdint.h$", ""}, + {"include/tbmintrin.h$", ""}, + {"include/tmmintrin.h$", ""}, + {"include/wmmintrin.h$", ""}, + {"include/x86intrin.h$", ""}, + {"include/xmmintrin.h$", ""}, + {"include/xopintrin.h$", ""}, + {"include/xsavecintrin.h$", ""}, + {"include/xsaveintrin.h$", ""}, + {"include/xsaveoptintrin.h$", ""}, + {"include/xsavesintrin.h$", ""}, + {"include/xtestintrin.h$", ""}, + {"include/_G_config.h$", ""}, + {"include/assert.h$", ""}, + {"algorithm$", ""}, + {"array$", ""}, + {"atomic$", ""}, + {"backward/auto_ptr.h$", ""}, + {"backward/binders.h$", ""}, + {"bits/algorithmfwd.h$", ""}, + {"bits/alloc_traits.h$", ""}, + {"bits/allocator.h$", ""}, + {"bits/atomic_base.h$", ""}, + {"bits/atomic_lockfree_defines.h$", ""}, + {"bits/basic_ios.h$", ""}, + {"bits/basic_ios.tcc$", ""}, + {"bits/basic_string.h$", ""}, + {"bits/basic_string.tcc$", ""}, + {"bits/char_traits.h$", ""}, + {"bits/codecvt.h$", ""}, + {"bits/concept_check.h$", ""}, + {"bits/cpp_type_traits.h$", ""}, + {"bits/cxxabi_forced.h$", ""}, + {"bits/deque.tcc$", ""}, + {"bits/exception_defines.h$", ""}, + {"bits/exception_ptr.h$", ""}, + {"bits/forward_list.h$", ""}, + {"bits/forward_list.tcc$", ""}, + {"bits/fstream.tcc$", ""}, + {"bits/functexcept.h$", ""}, + {"bits/functional_hash.h$", ""}, + {"bits/gslice.h$", ""}, + {"bits/gslice_array.h$", ""}, + {"bits/hash_bytes.h$", ""}, + {"bits/hashtable.h$", ""}, + {"bits/hashtable_policy.h$", ""}, + {"bits/indirect_array.h$", ""}, + {"bits/ios_base.h$", ""}, + {"bits/istream.tcc$", ""}, + {"bits/list.tcc$", ""}, + {"bits/locale_classes.h$", ""}, + {"bits/locale_classes.tcc$", ""}, + {"bits/locale_facets.h$", ""}, + {"bits/locale_facets.tcc$", ""}, + {"bits/locale_facets_nonio.h$", ""}, + {"bits/locale_facets_nonio.tcc$", ""}, + {"bits/localefwd.h$", ""}, + {"bits/mask_array.h$", ""}, + {"bits/memoryfwd.h$", ""}, + {"bits/move.h$", ""}, + {"bits/nested_exception.h$", ""}, + {"bits/ostream.tcc$", ""}, + {"bits/ostream_insert.h$", ""}, + {"bits/postypes.h$", ""}, + {"bits/ptr_traits.h$", ""}, + {"bits/random.h$", ""}, + {"bits/random.tcc$", ""}, + {"bits/range_access.h$", ""}, + {"bits/regex.h$", ""}, + {"bits/regex_compiler.h$", ""}, + {"bits/regex_constants.h$", ""}, + {"bits/regex_cursor.h$", ""}, + {"bits/regex_error.h$", ""}, + {"bits/regex_grep_matcher.h$", ""}, + {"bits/regex_grep_matcher.tcc$", ""}, + {"bits/regex_nfa.h$", ""}, + {"bits/shared_ptr.h$", ""}, + {"bits/shared_ptr_base.h$", ""}, + {"bits/slice_array.h$", ""}, + {"bits/sstream.tcc$", ""}, + {"bits/stl_algo.h$", ""}, + {"bits/stl_algobase.h$", ""}, + {"bits/stl_bvector.h$", ""}, + {"bits/stl_construct.h$", ""}, + {"bits/stl_deque.h$", ""}, + {"bits/stl_function.h$", ""}, + {"bits/stl_heap.h$", ""}, + {"bits/stl_iterator.h$", ""}, + {"bits/stl_iterator_base_funcs.h$", ""}, + {"bits/stl_iterator_base_types.h$", ""}, + {"bits/stl_list.h$", ""}, + {"bits/stl_map.h$", ""}, + {"bits/stl_multimap.h$", ""}, + {"bits/stl_multiset.h$", ""}, + {"bits/stl_numeric.h$", ""}, + {"bits/stl_pair.h$", ""}, + {"bits/stl_queue.h$", ""}, + {"bits/stl_raw_storage_iter.h$", ""}, + {"bits/stl_relops.h$", ""}, + {"bits/stl_set.h$", ""}, + {"bits/stl_stack.h$", ""}, + {"bits/stl_tempbuf.h$", ""}, + {"bits/stl_tree.h$", ""}, + {"bits/stl_uninitialized.h$", ""}, + {"bits/stl_vector.h$", ""}, + {"bits/stream_iterator.h$", ""}, + {"bits/streambuf.tcc$", ""}, + {"bits/streambuf_iterator.h$", ""}, + {"bits/stringfwd.h$", ""}, + {"bits/unique_ptr.h$", ""}, + {"bits/unordered_map.h$", ""}, + {"bits/unordered_set.h$", ""}, + {"bits/uses_allocator.h$", ""}, + {"bits/valarray_after.h$", ""}, + {"bits/valarray_array.h$", ""}, + {"bits/valarray_array.tcc$", ""}, + {"bits/valarray_before.h$", ""}, + {"bits/vector.tcc$", ""}, + {"bitset$", ""}, + {"ccomplex$", ""}, + {"cctype$", ""}, + {"cerrno$", ""}, + {"cfenv$", ""}, + {"cfloat$", ""}, + {"chrono$", ""}, + {"cinttypes$", ""}, + {"climits$", ""}, + {"clocale$", ""}, + {"cmath$", ""}, + {"complex$", ""}, + {"complex.h$", ""}, + {"condition_variable$", ""}, + {"csetjmp$", ""}, + {"csignal$", ""}, + {"cstdalign$", ""}, + {"cstdarg$", ""}, + {"cstdbool$", ""}, + {"cstdint$", ""}, + {"cstdio$", ""}, + {"cstdlib$", ""}, + {"cstring$", ""}, + {"ctgmath$", ""}, + {"ctime$", ""}, + {"cwchar$", ""}, + {"cwctype$", ""}, + {"cxxabi.h$", ""}, + {"debug/debug.h$", ""}, + {"deque$", ""}, + {"exception$", ""}, + {"ext/alloc_traits.h$", ""}, + {"ext/atomicity.h$", ""}, + {"ext/concurrence.h$", ""}, + {"ext/new_allocator.h$", ""}, + {"ext/numeric_traits.h$", ""}, + {"ext/string_conversions.h$", ""}, + {"ext/type_traits.h$", ""}, + {"fenv.h$", ""}, + {"forward_list$", ""}, + {"fstream$", ""}, + {"functional$", ""}, + {"future$", ""}, + {"initializer_list$", ""}, + {"iomanip$", ""}, + {"ios$", ""}, + {"iosfwd$", ""}, + {"iostream$", ""}, + {"istream$", ""}, + {"iterator$", ""}, + {"limits$", ""}, + {"list$", ""}, + {"locale$", ""}, + {"map$", ""}, + {"memory$", ""}, + {"mutex$", ""}, + {"new$", ""}, + {"numeric$", ""}, + {"ostream$", ""}, + {"queue$", ""}, + {"random$", ""}, + {"ratio$", ""}, + {"regex$", ""}, + {"scoped_allocator$", ""}, + {"set$", ""}, + {"sstream$", ""}, + {"stack$", ""}, + {"stdexcept$", ""}, + {"streambuf$", ""}, + {"string$", ""}, + {"system_error$", ""}, + {"tgmath.h$", ""}, + {"thread$", ""}, + {"tuple$", ""}, + {"type_traits$", ""}, + {"typeindex$", ""}, + {"typeinfo$", ""}, + {"unordered_map$", ""}, + {"unordered_set$", ""}, + {"utility$", ""}, + {"valarray$", ""}, + {"vector$", ""}, + {"include/complex.h$", ""}, + {"include/ctype.h$", ""}, + {"include/errno.h$", ""}, + {"include/fenv.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/libio.h$", ""}, + {"include/limits.h$", ""}, + {"include/locale.h$", ""}, + {"include/math.h$", ""}, + {"include/setjmp.h$", ""}, + {"include/signal.h$", ""}, + {"include/stdint.h$", ""}, + {"include/stdio.h$", ""}, + {"include/stdlib.h$", ""}, + {"include/string.h$", ""}, + {"include/time.h$", ""}, + {"include/wchar.h$", ""}, + {"include/wctype.h$", ""}, + {"bits/cmathcalls.h$", ""}, + {"bits/errno.h$", ""}, + {"bits/fenv.h$", ""}, + {"bits/huge_val.h$", ""}, + {"bits/huge_valf.h$", ""}, + {"bits/huge_vall.h$", ""}, + {"bits/inf.h$", ""}, + {"bits/local_lim.h$", ""}, + {"bits/locale.h$", ""}, + {"bits/mathcalls.h$", ""}, + {"bits/mathdef.h$", ""}, + {"bits/nan.h$", ""}, + {"bits/posix1_lim.h$", ""}, + {"bits/posix2_lim.h$", ""}, + {"bits/setjmp.h$", ""}, + {"bits/sigaction.h$", ""}, + {"bits/sigcontext.h$", ""}, + {"bits/siginfo.h$", ""}, + {"bits/signum.h$", ""}, + {"bits/sigset.h$", ""}, + {"bits/sigstack.h$", ""}, + {"bits/stdio_lim.h$", ""}, + {"bits/sys_errlist.h$", ""}, + {"bits/time.h$", ""}, + {"bits/timex.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/wchar.h$", ""}, + {"bits/wordsize.h$", ""}, + {"bits/xopen_lim.h$", ""}, + {"include/xlocale.h$", ""}, + {"bits/atomic_word.h$", ""}, + {"bits/basic_file.h$", ""}, + {"bits/c\\+\\+allocator.h$", ""}, + {"bits/c\\+\\+config.h$", ""}, + {"bits/c\\+\\+io.h$", ""}, + {"bits/c\\+\\+locale.h$", ""}, + {"bits/cpu_defines.h$", ""}, + {"bits/ctype_base.h$", ""}, + {"bits/cxxabi_tweaks.h$", ""}, + {"bits/error_constants.h$", ""}, + {"bits/gthr-default.h$", ""}, + {"bits/gthr.h$", ""}, + {"bits/opt_random.h$", ""}, + {"bits/os_defines.h$", ""}, + // GNU C headers + {"include/aio.h$", ""}, + {"include/aliases.h$", ""}, + {"include/alloca.h$", ""}, + {"include/ar.h$", ""}, + {"include/argp.h$", ""}, + {"include/argz.h$", ""}, + {"include/arpa/nameser.h$", ""}, + {"include/arpa/nameser_compat.h$", ""}, + {"include/byteswap.h$", ""}, + {"include/cpio.h$", ""}, + {"include/crypt.h$", ""}, + {"include/dirent.h$", ""}, + {"include/dlfcn.h$", ""}, + {"include/elf.h$", ""}, + {"include/endian.h$", ""}, + {"include/envz.h$", ""}, + {"include/err.h$", ""}, + {"include/error.h$", ""}, + {"include/execinfo.h$", ""}, + {"include/fcntl.h$", ""}, + {"include/features.h$", ""}, + {"include/fenv.h$", ""}, + {"include/fmtmsg.h$", ""}, + {"include/fnmatch.h$", ""}, + {"include/fstab.h$", ""}, + {"include/fts.h$", ""}, + {"include/ftw.h$", ""}, + {"include/gconv.h$", ""}, + {"include/getopt.h$", ""}, + {"include/glob.h$", ""}, + {"include/grp.h$", ""}, + {"include/gshadow.h$", ""}, + {"include/iconv.h$", ""}, + {"include/ifaddrs.h$", ""}, + {"include/kdb.h$", ""}, + {"include/langinfo.h$", ""}, + {"include/libgen.h$", ""}, + {"include/libintl.h$", ""}, + {"include/link.h$", ""}, + {"include/malloc.h$", ""}, + {"include/mcheck.h$", ""}, + {"include/memory.h$", ""}, + {"include/mntent.h$", ""}, + {"include/monetary.h$", ""}, + {"include/mqueue.h$", ""}, + {"include/netdb.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/nl_types.h$", ""}, + {"include/nss.h$", ""}, + {"include/obstack.h$", ""}, + {"include/panel.h$", ""}, + {"include/paths.h$", ""}, + {"include/printf.h$", ""}, + {"include/profile.h$", ""}, + {"include/pthread.h$", ""}, + {"include/pty.h$", ""}, + {"include/pwd.h$", ""}, + {"include/re_comp.h$", ""}, + {"include/regex.h$", ""}, + {"include/regexp.h$", ""}, + {"include/resolv.h$", ""}, + {"include/rpc/netdb.h$", ""}, + {"include/sched.h$", ""}, + {"include/search.h$", ""}, + {"include/semaphore.h$", ""}, + {"include/sgtty.h$", ""}, + {"include/shadow.h$", ""}, + {"include/spawn.h$", ""}, + {"include/stab.h$", ""}, + {"include/stdc-predef.h$", ""}, + {"include/stdio_ext.h$", ""}, + {"include/strings.h$", ""}, + {"include/stropts.h$", ""}, + {"include/sudo_plugin.h$", ""}, + {"include/sysexits.h$", ""}, + {"include/tar.h$", ""}, + {"include/tcpd.h$", ""}, + {"include/term.h$", ""}, + {"include/term_entry.h$", ""}, + {"include/termcap.h$", ""}, + {"include/termios.h$", ""}, + {"include/thread_db.h$", ""}, + {"include/tic.h$", ""}, + {"include/ttyent.h$", ""}, + {"include/uchar.h$", ""}, + {"include/ucontext.h$", ""}, + {"include/ulimit.h$", ""}, + {"include/unctrl.h$", ""}, + {"include/unistd.h$", ""}, + {"include/utime.h$", ""}, + {"include/utmp.h$", ""}, + {"include/utmpx.h$", ""}, + {"include/values.h$", ""}, + {"include/wordexp.h$", ""}, + {"fpu_control.h$", ""}, + {"ieee754.h$", ""}, + {"include/xlocale.h$", ""}, + {"gnu/lib-names.h$", ""}, + {"gnu/libc-version.h$", ""}, + {"gnu/option-groups.h$", ""}, + {"gnu/stubs-32.h$", ""}, + {"gnu/stubs-64.h$", ""}, + {"gnu/stubs-x32.h$", ""}, + {"include/rpc/auth_des.h$", ""}, + {"include/rpc/rpc_msg.h$", ""}, + {"include/rpc/pmap_clnt.h$", ""}, + {"include/rpc/rpc.h$", ""}, + {"include/rpc/types.h$", ""}, + {"include/rpc/auth_unix.h$", ""}, + {"include/rpc/key_prot.h$", ""}, + {"include/rpc/pmap_prot.h$", ""}, + {"include/rpc/auth.h$", ""}, + {"include/rpc/svc_auth.h$", ""}, + {"include/rpc/xdr.h$", ""}, + {"include/rpc/pmap_rmt.h$", ""}, + {"include/rpc/des_crypt.h$", ""}, + {"include/rpc/svc.h$", ""}, + {"include/rpc/rpc_des.h$", ""}, + {"include/rpc/clnt.h$", ""}, + {"include/scsi/scsi.h$", ""}, + {"include/scsi/sg.h$", ""}, + {"include/scsi/scsi_ioctl.h$", ""}, + {"include/netrose/rose.h$", ""}, + {"include/nfs/nfs.h$", ""}, + {"include/netatalk/at.h$", ""}, + {"include/netinet/ether.h$", ""}, + {"include/netinet/icmp6.h$", ""}, + {"include/netinet/if_ether.h$", ""}, + {"include/netinet/if_fddi.h$", ""}, + {"include/netinet/if_tr.h$", ""}, + {"include/netinet/igmp.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/netinet/in_systm.h$", ""}, + {"include/netinet/ip.h$", ""}, + {"include/netinet/ip6.h$", ""}, + {"include/netinet/ip_icmp.h$", ""}, + {"include/netinet/tcp.h$", ""}, + {"include/netinet/udp.h$", ""}, + {"include/netrom/netrom.h$", ""}, + {"include/protocols/routed.h$", ""}, + {"include/protocols/rwhod.h$", ""}, + {"include/protocols/talkd.h$", ""}, + {"include/protocols/timed.h$", ""}, + {"include/rpcsvc/klm_prot.x$", ""}, + {"include/rpcsvc/rstat.h$", ""}, + {"include/rpcsvc/spray.x$", ""}, + {"include/rpcsvc/nlm_prot.x$", ""}, + {"include/rpcsvc/nis_callback.x$", ""}, + {"include/rpcsvc/yp.h$", ""}, + {"include/rpcsvc/yp.x$", ""}, + {"include/rpcsvc/nfs_prot.h$", ""}, + {"include/rpcsvc/rex.h$", ""}, + {"include/rpcsvc/yppasswd.h$", ""}, + {"include/rpcsvc/rex.x$", ""}, + {"include/rpcsvc/nis_tags.h$", ""}, + {"include/rpcsvc/nis_callback.h$", ""}, + {"include/rpcsvc/nfs_prot.x$", ""}, + {"include/rpcsvc/bootparam_prot.x$", ""}, + {"include/rpcsvc/rusers.x$", ""}, + {"include/rpcsvc/rquota.x$", ""}, + {"include/rpcsvc/nis.h$", ""}, + {"include/rpcsvc/nislib.h$", ""}, + {"include/rpcsvc/ypupd.h$", ""}, + {"include/rpcsvc/bootparam.h$", ""}, + {"include/rpcsvc/spray.h$", ""}, + {"include/rpcsvc/key_prot.h$", ""}, + {"include/rpcsvc/klm_prot.h$", ""}, + {"include/rpcsvc/sm_inter.h$", ""}, + {"include/rpcsvc/nlm_prot.h$", ""}, + {"include/rpcsvc/yp_prot.h$", ""}, + {"include/rpcsvc/ypclnt.h$", ""}, + {"include/rpcsvc/rstat.x$", ""}, + {"include/rpcsvc/rusers.h$", ""}, + {"include/rpcsvc/key_prot.x$", ""}, + {"include/rpcsvc/sm_inter.x$", ""}, + {"include/rpcsvc/rquota.h$", ""}, + {"include/rpcsvc/nis.x$", ""}, + {"include/rpcsvc/bootparam_prot.h$", ""}, + {"include/rpcsvc/mount.h$", ""}, + {"include/rpcsvc/mount.x$", ""}, + {"include/rpcsvc/nis_object.x$", ""}, + {"include/rpcsvc/yppasswd.x$", ""}, + {"sys/acct.h$", ""}, + {"sys/auxv.h$", ""}, + {"sys/cdefs.h$", ""}, + {"sys/debugreg.h$", ""}, + {"sys/dir.h$", ""}, + {"sys/elf.h$", ""}, + {"sys/epoll.h$", ""}, + {"sys/eventfd.h$", ""}, + {"sys/fanotify.h$", ""}, + {"sys/file.h$", ""}, + {"sys/fsuid.h$", ""}, + {"sys/gmon.h$", ""}, + {"sys/gmon_out.h$", ""}, + {"sys/inotify.h$", ""}, + {"sys/io.h$", ""}, + {"sys/ioctl.h$", ""}, + {"sys/ipc.h$", ""}, + {"sys/kd.h$", ""}, + {"sys/kdaemon.h$", ""}, + {"sys/klog.h$", ""}, + {"sys/mman.h$", ""}, + {"sys/mount.h$", ""}, + {"sys/msg.h$", ""}, + {"sys/mtio.h$", ""}, + {"sys/param.h$", ""}, + {"sys/pci.h$", ""}, + {"sys/perm.h$", ""}, + {"sys/personality.h$", ""}, + {"sys/poll.h$", ""}, + {"sys/prctl.h$", ""}, + {"sys/procfs.h$", ""}, + {"sys/profil.h$", ""}, + {"sys/ptrace.h$", ""}, + {"sys/queue.h$", ""}, + {"sys/quota.h$", ""}, + {"sys/raw.h$", ""}, + {"sys/reboot.h$", ""}, + {"sys/reg.h$", ""}, + {"sys/resource.h$", ""}, + {"sys/select.h$", ""}, + {"sys/sem.h$", ""}, + {"sys/sendfile.h$", ""}, + {"sys/shm.h$", ""}, + {"sys/signalfd.h$", ""}, + {"sys/socket.h$", ""}, + {"sys/stat.h$", ""}, + {"sys/statfs.h$", ""}, + {"sys/statvfs.h$", ""}, + {"sys/swap.h$", ""}, + {"sys/syscall.h$", ""}, + {"sys/sysctl.h$", ""}, + {"sys/sysinfo.h$", ""}, + {"sys/syslog.h$", ""}, + {"sys/sysmacros.h$", ""}, + {"sys/termios.h$", ""}, + {"sys/time.h$", ""}, + {"sys/timeb.h$", ""}, + {"sys/timerfd.h$", ""}, + {"sys/times.h$", ""}, + {"sys/timex.h$", ""}, + {"sys/ttychars.h$", ""}, + {"sys/ttydefaults.h$", ""}, + {"sys/types.h$", ""}, + {"sys/ucontext.h$", ""}, + {"sys/uio.h$", ""}, + {"sys/un.h$", ""}, + {"sys/user.h$", ""}, + {"sys/ustat.h$", ""}, + {"sys/utsname.h$", ""}, + {"sys/vlimit.h$", ""}, + {"sys/vm86.h$", ""}, + {"sys/vtimes.h$", ""}, + {"sys/wait.h$", ""}, + {"sys/xattr.h$", ""}, + {"bits/epoll.h$", ""}, + {"bits/eventfd.h$", ""}, + {"bits/inotify.h$", ""}, + {"bits/ipc.h$", ""}, + {"bits/ipctypes.h$", ""}, + {"bits/mman-linux.h$", ""}, + {"bits/mman.h$", ""}, + {"bits/msq.h$", ""}, + {"bits/resource.h$", ""}, + {"bits/sem.h$", ""}, + {"bits/shm.h$", ""}, + {"bits/signalfd.h$", ""}, + {"bits/statfs.h$", ""}, + {"bits/statvfs.h$", ""}, + {"bits/timerfd.h$", ""}, + {"bits/utsname.h$", ""}, + {"bits/auxv.h$", ""}, + {"bits/byteswap-16.h$", ""}, + {"bits/byteswap.h$", ""}, + {"bits/confname.h$", ""}, + {"bits/dirent.h$", ""}, + {"bits/dlfcn.h$", ""}, + {"bits/elfclass.h$", ""}, + {"bits/endian.h$", ""}, + {"bits/environments.h$", ""}, + {"bits/fcntl-linux.h$", ""}, + {"bits/fcntl.h$", ""}, + {"bits/in.h$", ""}, + {"bits/ioctl-types.h$", ""}, + {"bits/ioctls.h$", ""}, + {"bits/link.h$", ""}, + {"bits/mqueue.h$", ""}, + {"bits/netdb.h$", ""}, + {"bits/param.h$", ""}, + {"bits/poll.h$", ""}, + {"bits/posix_opt.h$", ""}, + {"bits/pthreadtypes.h$", ""}, + {"bits/sched.h$", ""}, + {"bits/select.h$", ""}, + {"bits/semaphore.h$", ""}, + {"bits/sigthread.h$", ""}, + {"bits/sockaddr.h$", ""}, + {"bits/socket.h$", ""}, + {"bits/socket_type.h$", ""}, + {"bits/stab.def$", ""}, + {"bits/stat.h$", ""}, + {"bits/stropts.h$", ""}, + {"bits/syscall.h$", ""}, + {"bits/syslog-path.h$", ""}, + {"bits/termios.h$", ""}, + {"bits/types.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/uio.h$", ""}, + {"bits/ustat.h$", ""}, + {"bits/utmp.h$", ""}, + {"bits/utmpx.h$", ""}, + {"bits/waitflags.h$", ""}, + {"bits/waitstatus.h$", ""}, + {"bits/xtitypes.h$", ""}, + }; + for (const auto &Pair : STLPostfixHeaderMap) + Includes->addRegexMapping(Pair.first, Pair.second); +} + +} // namespace clangd +} // namespace clang Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -23,8 +23,8 @@ namespace clangd { struct SymbolLocation { - // The absolute path of the source file where a symbol occurs. - llvm::StringRef FilePath; + // The URI of the source file where a symbol occurs. + llvm::StringRef FileURI; // The 0-based offset to the first character of the symbol from the beginning // of the source file. unsigned StartOffset; @@ -146,11 +146,19 @@ /// and have clients resolve full symbol information for a specific candidate /// if needed. struct Details { - // Documentation including comment for the symbol declaration. + /// Documentation including comment for the symbol declaration. llvm::StringRef Documentation; - // This is what goes into the LSP detail field in a completion item. For - // example, the result type of a function. + /// This is what goes into the LSP detail field in a completion item. For + /// example, the result type of a function. llvm::StringRef CompletionDetail; + /// A URI for the header to be #include'd for this symbol, or a literal + /// header like <...> or "..." for headers that are suitable to be included + /// directly. When this is a URI, the exact #include path needs to be + /// calculated according to the URI scheme. + /// + /// If empty, FileURI in CanonicalDeclaration should be used to calculate + /// the #include path. + llvm::StringRef IncludeURI; }; // Optional details of the symbol. Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -54,7 +54,7 @@ // We need to copy every StringRef field onto the arena. Intern(S.Name); Intern(S.Scope); - Intern(S.CanonicalDeclaration.FilePath); + Intern(S.CanonicalDeclaration.FileURI); Intern(S.CompletionLabel); Intern(S.CompletionFilterText); @@ -68,6 +68,7 @@ // Intern the actual strings. Intern(Detail->Documentation); Intern(Detail->CompletionDetail); + Intern(Detail->IncludeURI); // Replace the detail pointer with our copy. S.Detail = Detail; } Index: clangd/index/Merge.cpp =================================================================== --- clangd/index/Merge.cpp +++ clangd/index/Merge.cpp @@ -63,7 +63,7 @@ Symbol S = L; // For each optional field, fill it from R if missing in L. // (It might be missing in R too, but that's a no-op). - if (S.CanonicalDeclaration.FilePath == "") + if (S.CanonicalDeclaration.FileURI == "") S.CanonicalDeclaration = R.CanonicalDeclaration; if (S.CompletionLabel == "") S.CompletionLabel = R.CompletionLabel; @@ -81,6 +81,9 @@ Scratch->Documentation = R.Detail->Documentation; if (Scratch->CompletionDetail == "") Scratch->CompletionDetail = R.Detail->CompletionDetail; + if (Scratch->IncludeURI == "") + Scratch->IncludeURI = R.Detail->IncludeURI; + S.Detail = Scratch; } else if (L.Detail) S.Detail = L.Detail; Index: clangd/index/SymbolCollector.h =================================================================== --- clangd/index/SymbolCollector.h +++ clangd/index/SymbolCollector.h @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "CanonicalIncludes.h" #include "Index.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -30,10 +31,19 @@ /// Whether to collect symbols in main files (e.g. the source file /// corresponding to a TU). bool IndexMainFiles = false; - // When symbol paths cannot be resolved to absolute paths (e.g. files in - // VFS that does not have absolute path), combine the fallback directory - // with symbols' paths to get absolute paths. This must be an absolute path. + /// When symbol paths cannot be resolved to absolute paths (e.g. files in + /// VFS that does not have absolute path), combine the fallback directory + /// with symbols' paths to get absolute paths. This must be an absolute + /// path. std::string FallbackDir; + /// Specifies URI schemes that can be used to generate URIs for file paths + /// in symbols. The list of schemes will be tried in order until a working + /// scheme is found. If no scheme works, symbol location will be dropped. + std::vector URISchemes = {"file"}; + bool CollectIncludePath = false; + /// If set, this is used to map symbol #include path to a potentially + /// different #include path. + CanonicalIncludes *Includes = nullptr; }; SymbolCollector(Options Opts); Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -9,6 +9,9 @@ #include "SymbolCollector.h" #include "../CodeCompletionStrings.h" +#include "../Logger.h" +#include "../URI.h" +#include "CanonicalIncludes.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" @@ -22,14 +25,17 @@ namespace clangd { namespace { -// Make the Path absolute using the current working directory of the given -// SourceManager if the Path is not an absolute path. If failed, this combine -// relative paths with \p FallbackDir to get an absolute path. +// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the +// current working directory of the given SourceManager if the Path is not an +// absolute path. If failed, this resolves relative paths against \p FallbackDir +// to get an absolute path. Then, this tries creating an URI for the absolute +// path with schemes specified in \p Opts. This returns an URI with the first +// working scheme, if there is any; otherwise, this returns None. // // The Path can be a path relative to the build directory, or retrieved from // the SourceManager. -std::string makeAbsolutePath(const SourceManager &SM, StringRef Path, - StringRef FallbackDir) { +llvm::Optional toURI(const SourceManager &SM, StringRef Path, + const SymbolCollector::Options &Opts) { llvm::SmallString<128> AbsolutePath(Path); if (std::error_code EC = SM.getFileManager().getVirtualFileSystem()->makeAbsolute( @@ -56,11 +62,21 @@ llvm::sys::path::filename(AbsolutePath.str())); AbsolutePath = AbsoluteFilename; } - } else if (!FallbackDir.empty()) { - llvm::sys::fs::make_absolute(FallbackDir, AbsolutePath); + } else if (!Opts.FallbackDir.empty()) { + llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath); llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); } - return AbsolutePath.str(); + + std::string ErrMsg; + for (const auto &Scheme : Opts.URISchemes) { + auto U = URI::create(AbsolutePath, Scheme); + if (U) + return U->toString(); + ErrMsg += llvm::toString(U.takeError()) + "\n"; + } + log(llvm::Twine("Failed to create an URI for file ") + AbsolutePath + ": " + + ErrMsg); + return llvm::None; } // "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no qualifier. @@ -112,40 +128,99 @@ return false; } +// We only collect #include paths for symbols that are suitable for global code +// completion, except for namespaces since #include path for a namespace is hard +// to define. +bool shouldCollectIncludePath(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind) { + case SK::Macro: + case SK::Enum: + case SK::Struct: + case SK::Class: + case SK::Union: + case SK::TypeAlias: + case SK::Using: + case SK::Function: + case SK::Variable: + case SK::EnumConstant: + return true; + default: + return false; + } +} + +/// Gets an URI for the include path, or
or "header" for headers that +/// expected to be included literally. +llvm::Optional +getIncludeHeader(const SourceManager &SM, SourceLocation Loc, + const SymbolCollector::Options &Opts) { + llvm::StringRef FilePath; + bool InInc = false; + // Walk up the include stack to skip .inc files. Symbols in a .inc should be + // exposed by the header that includes it. + while (true) { + if (!Loc.isValid() || SM.isInMainFile(Loc)) + return llvm::None; + FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return llvm::None; + if (!FilePath.endswith(".inc")) + break; + FileID ID = SM.getFileID(Loc); + Loc = SM.getIncludeLoc(ID); + InInc = true; + } + if (Opts.Includes) { + llvm::StringRef Mapped = Opts.Includes->mapHeader(FilePath); + if (Mapped != FilePath) + return (Mapped.startswith("<") || Mapped.startswith("\"")) + ? Mapped.str() + : ("\"" + Mapped + "\"").str(); + } + // If the header path is the same as the file path of the declaration, we skip + // storing the #include path; users can use the URI in declaration location to + // calculate the #include path. + if (!InInc) + return llvm::None; + if (auto U = toURI(SM, FilePath, Opts)) + return std::move(*U); + return llvm::None; +} + // Return the symbol location of the given declaration `D`. // // For symbols defined inside macros: // * use expansion location, if the symbol is formed via macro concatenation. // * use spelling location, otherwise. -SymbolLocation GetSymbolLocation(const NamedDecl *D, SourceManager &SM, - StringRef FallbackDir, - std::string &FilePathStorage) { - SymbolLocation Location; - - SourceLocation Loc = SM.getSpellingLoc(D->getLocation()); - if (D->getLocation().isMacroID()) { - // We use the expansion location for the following symbols, as spelling - // locations of these symbols are not interesting to us: - // * symbols formed via macro concatenation, the spelling location will - // be "" - // * symbols controlled and defined by a compile command-line option - // `-DName=foo`, the spelling location will be "". - std::string PrintLoc = Loc.printToString(SM); +llvm::Optional +getSymbolLocation(const NamedDecl *D, SourceManager &SM, + const SymbolCollector::Options &Opts, + std::string &FileURIStorage) { + SourceLocation Loc = D->getLocation(); + SourceLocation StartLoc = SM.getSpellingLoc(D->getLocStart()); + SourceLocation EndLoc = SM.getSpellingLoc(D->getLocEnd()); + if (Loc.isMacroID()) { + std::string PrintLoc = SM.getSpellingLoc(Loc).printToString(SM); if (llvm::StringRef(PrintLoc).startswith("")) { - FilePathStorage = makeAbsolutePath( - SM, SM.getFilename(SM.getExpansionLoc(D->getLocation())), - FallbackDir); - return {FilePathStorage, - SM.getFileOffset(SM.getExpansionRange(D->getLocStart()).first), - SM.getFileOffset(SM.getExpansionRange(D->getLocEnd()).second)}; + // We use the expansion location for the following symbols, as spelling + // locations of these symbols are not interesting to us: + // * symbols formed via macro concatenation, the spelling location will + // be "" + // * symbols controlled and defined by a compile command-line option + // `-DName=foo`, the spelling location will be "". + StartLoc = SM.getExpansionRange(D->getLocStart()).first; + EndLoc = SM.getExpansionRange(D->getLocEnd()).second; } } - FilePathStorage = makeAbsolutePath(SM, SM.getFilename(Loc), FallbackDir); - return {FilePathStorage, - SM.getFileOffset(SM.getSpellingLoc(D->getLocStart())), - SM.getFileOffset(SM.getSpellingLoc(D->getLocEnd()))}; + auto U = toURI(SM, SM.getFilename(StartLoc), Opts); + if (!U) + return llvm::None; + FileURIStorage = std::move(*U); + return SymbolLocation{FileURIStorage, SM.getFileOffset(StartLoc), + SM.getFileOffset(EndLoc)}; } } // namespace @@ -201,8 +276,9 @@ S.ID = std::move(ID); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); S.SymInfo = index::getSymbolInfo(D); - std::string FilePath; - S.CanonicalDeclaration = GetSymbolLocation(ND, SM, Opts.FallbackDir, FilePath); + std::string URIStorage; + if (auto DeclLoc = getSymbolLocation(ND, SM, Opts, URIStorage)) + S.CanonicalDeclaration = *DeclLoc; // Add completion info. assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); @@ -223,6 +299,15 @@ std::string Documentation = getDocumentation(*CCS); std::string CompletionDetail = getDetail(*CCS); + std::string Include; + if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + // Use the expansion location to get the #include header since this is + // where the symbol is exposed. + if (auto Header = + getIncludeHeader(SM, SM.getExpansionLoc(D->getLocation()), Opts)) + Include = std::move(*Header); + } + S.CompletionFilterText = FilterText; S.CompletionLabel = Label; S.CompletionPlainInsertText = PlainInsertText; @@ -230,6 +315,7 @@ Symbol::Details Detail; Detail.Documentation = Documentation; Detail.CompletionDetail = CompletionDetail; + Detail.IncludeURI = Include; S.Detail = &Detail; Symbols.insert(S); Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -48,7 +48,7 @@ static void mapping(IO &IO, SymbolLocation &Value) { IO.mapRequired("StartOffset", Value.StartOffset); IO.mapRequired("EndOffset", Value.EndOffset); - IO.mapRequired("FilePath", Value.FilePath); + IO.mapRequired("FileURI", Value.FileURI); } }; @@ -64,6 +64,7 @@ static void mapping(IO &io, Symbol::Details &Detail) { io.mapOptional("Documentation", Detail.Documentation); io.mapOptional("CompletionDetail", Detail.CompletionDetail); + io.mapOptional("IncludeURI", Detail.IncludeURI); } }; Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -16,7 +16,9 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -24,7 +24,8 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "renameProvider": true, Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -24,7 +24,8 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "renameProvider": true, Index: unittests/clangd/IndexTests.cpp =================================================================== --- unittests/clangd/IndexTests.cpp +++ unittests/clangd/IndexTests.cpp @@ -226,8 +226,8 @@ Symbol L, R; L.ID = R.ID = SymbolID("hello"); L.Name = R.Name = "Foo"; // same in both - L.CanonicalDeclaration.FilePath = "left.h"; // differs - R.CanonicalDeclaration.FilePath = "right.h"; + L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs + R.CanonicalDeclaration.FileURI = "file:///right.h"; L.CompletionPlainInsertText = "f00"; // present in left only R.CompletionSnippetInsertText = "f0{$1:0}"; // present in right only Symbol::Details DetL, DetR; @@ -240,7 +240,7 @@ Symbol::Details Scratch; Symbol M = mergeSymbol(L, R, &Scratch); EXPECT_EQ(M.Name, "Foo"); - EXPECT_EQ(M.CanonicalDeclaration.FilePath, "left.h"); + EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:///left.h"); EXPECT_EQ(M.CompletionPlainInsertText, "f00"); EXPECT_EQ(M.CompletionSnippetInsertText, "f0{$1:0}"); ASSERT_TRUE(M.Detail); Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -46,7 +46,13 @@ return arg.CompletionSnippetInsertText == S; } MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } -MATCHER_P(CPath, P, "") { return arg.CanonicalDeclaration.FilePath == P; } +MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; } +MATCHER_P(IncludeURI, P, "") { + return arg.Detail && arg.Detail->IncludeURI == P; +} +MATCHER(HasIncludeURI, "") { + return arg.Detail && !arg.Detail->IncludeURI.empty(); +} MATCHER_P(LocationOffsets, Offsets, "") { // Offset range in SymbolLocation is [start, end] while in Clangd is [start, // end). @@ -58,38 +64,64 @@ namespace clangd { namespace { -const char TestHeaderName[] = "symbols.h"; -const char TestFileName[] = "symbol.cc"; class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: - SymbolIndexActionFactory(SymbolCollector::Options COpts) - : COpts(std::move(COpts)) {} + SymbolIndexActionFactory(SymbolCollector::Options COpts, + CommentHandler *PragmaHandler) + : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} clang::FrontendAction *create() override { + class WrappedIndexAction : public WrapperFrontendAction { + public: + WrappedIndexAction(std::shared_ptr C, + const index::IndexingOptions &Opts, + CommentHandler *PragmaHandler) + : WrapperFrontendAction( + index::createIndexingAction(C, Opts, nullptr)), + PragmaHandler(PragmaHandler) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + if (PragmaHandler) + CI.getPreprocessor().addCommentHandler(PragmaHandler); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } + + private: + index::IndexingOptions IndexOpts; + CommentHandler *PragmaHandler; + }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); - FrontendAction *Action = - index::createIndexingAction(Collector, IndexOpts, nullptr).release(); - return Action; + return new WrappedIndexAction(Collector, std::move(IndexOpts), + PragmaHandler); } std::shared_ptr Collector; SymbolCollector::Options COpts; + CommentHandler *PragmaHandler; }; class SymbolCollectorTest : public ::testing::Test { public: + SymbolCollectorTest() + : InMemoryFileSystem(new vfs::InMemoryFileSystem), + TestHeaderName(getVirtualTestFilePath("symbol.h").str()), + TestFileName(getVirtualTestFilePath("symbol.cc").str()) { + TestHeaderURI = URI::createFile(TestHeaderName).toString(); + TestFileURI = URI::createFile(TestFileName).toString(); + } + bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode, const std::vector &ExtraArgs = {}) { - llvm::IntrusiveRefCntPtr InMemoryFileSystem( - new vfs::InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); - auto Factory = llvm::make_unique(CollectorOpts); + auto Factory = llvm::make_unique( + CollectorOpts, PragmaHandler.get()); std::vector Args = {"symbol_collector", "-fsyntax-only", "-std=c++11", TestFileName}; @@ -104,7 +136,9 @@ std::string Content = MainCode; if (!HeaderCode.empty()) - Content = "#include\"" + std::string(TestHeaderName) + "\"\n" + Content; + Content = (llvm::Twine("#include\"") + + llvm::sys::path::filename(TestHeaderName) + "\"\n" + Content) + .str(); InMemoryFileSystem->addFile(TestFileName, 0, llvm::MemoryBuffer::getMemBuffer(Content)); Invocation.run(); @@ -113,8 +147,14 @@ } protected: + llvm::IntrusiveRefCntPtr InMemoryFileSystem; + std::string TestHeaderName; + std::string TestHeaderURI; + std::string TestFileName; + std::string TestFileURI; SymbolSlab Symbols; SymbolCollector::Options CollectorOpts; + std::unique_ptr PragmaHandler; }; TEST_F(SymbolCollectorTest, CollectSymbols) { @@ -169,16 +209,49 @@ CollectorOpts.IndexMainFiles = false; runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, - UnorderedElementsAre(AllOf(QName("Foo"), CPath("symbols.h")))); + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { CollectorOpts.IndexMainFiles = false; + TestHeaderName = "x.h"; + TestFileName = "x.cpp"; + TestHeaderURI = + URI::createFile(getVirtualTestFilePath(TestHeaderName)).toString(); CollectorOpts.FallbackDir = getVirtualTestRoot(); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, - UnorderedElementsAre(AllOf( - QName("Foo"), CPath(getVirtualTestFilePath("symbols.h"))))); + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); +} + +#ifndef LLVM_ON_WIN32 +TEST_F(SymbolCollectorTest, CustomURIScheme) { + CollectorOpts.IndexMainFiles = false; + // Use test URI scheme from URITests.cpp + CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest"); + TestHeaderName = getVirtualTestFilePath("test-root/x.h").str(); + TestFileName = getVirtualTestFilePath("test-root/x.cpp").str(); + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("unittest:x.h")))); +} +#endif + +TEST_F(SymbolCollectorTest, InvalidURIScheme) { + CollectorOpts.IndexMainFiles = false; + // Use test URI scheme from URITests.cpp + CollectorOpts.URISchemes = {"invalid"}; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("")))); +} + +TEST_F(SymbolCollectorTest, FallbackToFileURI) { + CollectorOpts.IndexMainFiles = false; + // Use test URI scheme from URITests.cpp + CollectorOpts.URISchemes = {"invalid", "file"}; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre( + AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, IncludeEnums) { @@ -233,14 +306,14 @@ )"); runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("abc_Test"), - LocationOffsets(Header.offsetRange("expansion")), - CPath(TestHeaderName)), - AllOf(QName("Test"), - LocationOffsets(Header.offsetRange("spelling")), - CPath(TestHeaderName)))); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(QName("abc_Test"), + LocationOffsets(Header.offsetRange("expansion")), + DeclURI(TestHeaderURI)), + AllOf(QName("Test"), LocationOffsets(Header.offsetRange("spelling")), + DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, SymbolFormedFromMacroInMainFile) { @@ -258,14 +331,13 @@ FF2(); )"); runSymbolCollector(/*Header=*/"", Main.code()); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("abc_Test"), - LocationOffsets(Main.offsetRange("expansion")), - CPath(TestFileName)), - AllOf(QName("Test"), - LocationOffsets(Main.offsetRange("spelling")), - CPath(TestFileName)))); + EXPECT_THAT(Symbols, UnorderedElementsAre( + AllOf(QName("abc_Test"), + LocationOffsets(Main.offsetRange("expansion")), + DeclURI(TestFileURI)), + AllOf(QName("Test"), + LocationOffsets(Main.offsetRange("spelling")), + DeclURI(TestFileURI)))); } TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { @@ -279,11 +351,10 @@ runSymbolCollector(Header.code(), /*Main=*/"", /*ExtraArgs=*/{"-DNAME=name"}); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("name"), - LocationOffsets(Header.offsetRange("expansion")), - CPath(TestHeaderName)))); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( + QName("name"), + LocationOffsets(Header.offsetRange("expansion")), + DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, IgnoreSymbolsInMainFile) { @@ -433,7 +504,7 @@ CanonicalDeclaration: StartOffset: 0 EndOffset: 1 - FilePath: /path/foo.h + FileURI: file:///path/foo.h CompletionLabel: 'Foo1-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -453,7 +524,7 @@ CanonicalDeclaration: StartOffset: 10 EndOffset: 12 - FilePath: /path/foo.h + FileURI: file:///path/bar.h CompletionLabel: 'Foo2-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' @@ -462,13 +533,14 @@ )"; auto Symbols1 = SymbolFromYAML(YAML1); - EXPECT_THAT(Symbols1, UnorderedElementsAre( - AllOf(QName("clang::Foo1"), Labeled("Foo1-label"), - Doc("Foo doc"), Detail("int")))); + EXPECT_THAT(Symbols1, + UnorderedElementsAre(AllOf( + QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"), + Detail("int"), DeclURI("file:///path/foo.h")))); auto Symbols2 = SymbolFromYAML(YAML2); - EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf(QName("clang::Foo2"), - Labeled("Foo2-label"), - Not(HasDetail())))); + EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf( + QName("clang::Foo2"), Labeled("Foo2-label"), + Not(HasDetail()), DeclURI("file:///path/bar.h")))); std::string ConcatenatedYAML = SymbolsToYAML(Symbols1) + SymbolsToYAML(Symbols2); @@ -478,6 +550,64 @@ QName("clang::Foo2"))); } +TEST_F(SymbolCollectorTest, NoIncludeURIIfSameAsFileURI) { + CollectorOpts.CollectIncludePath = true; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + Not(HasIncludeURI())))); +} + +TEST_F(SymbolCollectorTest, SkipIncFile) { + CollectorOpts.CollectIncludePath = true; + CollectorOpts.IndexMainFiles = true; + std::string IncFile = getVirtualTestFilePath("x.inc").str(); + std::string IncURI = URI::createFile(IncFile).toString(); + const std::string IncContent = R"( + class Foo {}; + )"; + InMemoryFileSystem->addFile(IncFile, 0, + llvm::MemoryBuffer::getMemBuffer(IncContent)); + + const std::string Header = R"( + #include "x.inc" + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(IncURI), + IncludeURI(TestHeaderURI)))); +} + +#ifndef LLVM_ON_WIN32 +TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + addStandardLibraryMapping(&Includes); + CollectorOpts.Includes = &Includes; + TestHeaderName = "/nasty/bits/basic_string.h"; + TestFileName = "/nasty/bits/basic_string.cpp"; + TestHeaderURI = URI::createFile(TestHeaderName).toString(); + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeURI("")))); +} +#endif + +TEST_F(SymbolCollectorTest, IWYUPragma) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + PragmaHandler = collectIWYUHeaderMaps(&Includes); + CollectorOpts.Includes = &Includes; + const std::string Header = R"( + // IWYU pragma: private, include the/good/header.h + class Foo {}; + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeURI("\"the/good/header.h\"")))); +} + } // namespace } // namespace clangd } // namespace clang