Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -14,6 +14,7 @@ DraftStore.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp + Headers.cpp JSONExpr.cpp JSONRPCDispatcher.cpp Logger.cpp @@ -25,6 +26,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 @@ -121,7 +121,9 @@ {"renameProvider", true}, {"executeCommandProvider", json::obj{ - {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + {"commands", + {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}}, }}, }}}}); } @@ -155,6 +157,14 @@ } void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { + auto ApplyEdit = [](WorkspaceEdit WE) { + ApplyWorkspaceEditParams Edit; + Edit.edit = std::move(WE); + // We don't need the response so id == 1 is OK. + // 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", Edit); + }; if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && Params.workspaceEdit) { // The flow for "apply-fix" : @@ -166,13 +176,35 @@ // 6. The editor applies the changes (applyEdit), and sends us a reply (but // we ignore it) - ApplyWorkspaceEditParams ApplyEdit; - ApplyEdit.edit = *Params.workspaceEdit; reply("Fix applied."); - // We don't need the response so id == 1 is OK. - // 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); + ApplyEdit(*Params.workspaceEdit); + } 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->header); + if (!Replaces) { + std::string ErrMsg = + ("Failed to generate include insertion edits for adding " + + Params.includeInsertion->header + " into " + FileURI.file()) + .str(); + log(ErrMsg + ":" + llvm::toString(Replaces.takeError())); + replyError(ErrorCode::InternalError, ErrMsg); + return; + } + auto Edits = replacementsToEdits(*Code, *Replaces); + WorkspaceEdit WE; + WE.changes = {{FileURI.uri(), Edits}}; + + reply("Inserted header " + Params.includeInsertion->header); + ApplyEdit(std::move(WE)); } 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 @@ -231,6 +231,13 @@ UniqueFunction>)> Callback); + /// Inserts a new #include of \p Header into \p File, if it's not present. + /// \p Header is either an URI that can be resolved to an #include path that + /// is suitable to be inserted or a literal string quoted with <> or "" that + /// can be #included directly. + Expected insertInclude(PathRef File, StringRef Code, + StringRef Header); + /// 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,6 +9,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" +#include "Headers.h" #include "SourceCode.h" #include "XRefs.h" #include "index/Merge.h" @@ -310,6 +311,47 @@ BindWithForward(Action, File.str(), NewName.str(), std::move(Callback))); } +Expected +ClangdServer::insertInclude(PathRef File, StringRef Code, + llvm::StringRef Header) { + std::string ToInclude; + if (Header.startswith("<") || Header.startswith("\"")) { + ToInclude = Header; + } else { + auto U = URI::parse(Header); + if (!U) + return U.takeError(); + auto Resolved = URI::resolve(*U, /*HintPath=*/File); + if (!Resolved) + return Resolved.takeError(); + + auto FS = FSProvider.getTaggedFileSystem(File).Value; + tooling::CompileCommand CompileCommand = + CompileArgs.getCompileCommand(File); + FS->setCurrentWorkingDirectory(CompileCommand.Directory); + + auto Include = + shortenIncludePath(File, Code, *Resolved, CompileCommand, FS); + if (!Include) + return Include.takeError(); + if (Include->empty()) + return tooling::Replacements(); + ToInclude = std::move(*Include); + } + + auto Style = format::getStyle("file", File, "llvm"); + if (!Style) { + llvm::consumeError(Style.takeError()); + // FIXME(ioeric): needs more consistent style support in clangd server. + Style = format::getLLVMStyle(); + } + // Replacement with offset UINT_MAX and length 0 will be treated as include + // insertion. + tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, "#include " + ToInclude); + return format::cleanupAroundReplacements(Code, tooling::Replacements(R), + *Style); +} + 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, + const CompletionItemScores &Scores, const CodeCompleteOptions &Opts, CodeCompletionString *SemaCCS) const { assert(bool(SemaResult) == bool(SemaCCS)); @@ -282,6 +286,28 @@ I.documentation = D->Documentation; if (I.detail.empty()) I.detail = D->CompletionDetail; + // We only insert #include for items with details, since we can't tell + // whether the file URI of the canonical declaration would be the + // canonical #include without checking IncludeHeader in the detail. + // FIXME: delay creating include insertion command to + // "completionItem/resolve", when it is supported + if (!D->IncludeHeader.empty() || + !IndexResult->CanonicalDeclaration.FileURI.empty()) { + // LSP favors additionalTextEdits over command. But we are still using + // command here because it would be expensive to calculate #include + // insertion edits for all candidates, and the include insertion edit + // is unlikely to conflict with the code completion edits. + Command Cmd; + // Command title is not added since this is not a user-facing command. + Cmd.command = ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE; + IncludeInsertion Insertion; + Insertion.header = D->IncludeHeader.empty() + ? IndexResult->CanonicalDeclaration.FileURI + : D->IncludeHeader; + Insertion.textDocument.uri = URIForFile(FileName); + Cmd.includeInsertion = std::move(Insertion); + I.command = std::move(Cmd); + } } } I.scoreInfo = Scores; @@ -806,6 +832,7 @@ // This score is combined with the result quality score for the final score. // - TopN determines the results with the best score. class CodeCompleteFlow { + PathRef FileName; const CodeCompleteOptions &Opts; // Sema takes ownership of Recorder. Recorder is valid until Sema cleanup. std::unique_ptr RecorderOwner; @@ -816,9 +843,9 @@ public: // A CodeCompleteFlow object is only useful for calling run() exactly once. - CodeCompleteFlow(const CodeCompleteOptions &Opts) - : Opts(Opts), RecorderOwner(new CompletionRecorder(Opts)), - Recorder(*RecorderOwner) {} + CodeCompleteFlow(PathRef FileName, const CodeCompleteOptions &Opts) + : FileName(FileName), Opts(Opts), + RecorderOwner(new CompletionRecorder(Opts)), Recorder(*RecorderOwner) {} CompletionList run(const SemaCompleteInput &SemaCCInput) && { trace::Span Tracer("CodeCompleteFlow"); @@ -956,7 +983,7 @@ CodeCompletionString *SemaCCS = nullptr; if (auto *SR = Candidate.SemaResult) SemaCCS = Recorder.codeCompletionString(*SR, Opts.IncludeBriefComments); - return Candidate.build(Scores, Opts, SemaCCS); + return Candidate.build(FileName, Scores, Opts, SemaCCS); } }; @@ -967,8 +994,8 @@ IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, CodeCompleteOptions Opts) { - return CodeCompleteFlow(Opts).run( - {FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); + return CodeCompleteFlow(FileName, Opts) + .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); } SignatureHelp signatureHelp(PathRef FileName, Index: clangd/Headers.h =================================================================== --- clangd/Headers.h +++ clangd/Headers.h @@ -0,0 +1,37 @@ +//===--- Headers.h - Include headers -----------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H + +#include "Path.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +/// Determines the preferred way to #include a file, taking into account the +/// search path. Usually this will prefer a shorter representation like +/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'. +/// +/// \param Header is an absolute file path. +/// \return A quoted "path" or . If \p Header is already (directly) +/// included in the file (including those included via different paths), this +/// returns an empty string. +llvm::Expected +shortenIncludePath(PathRef File, llvm::StringRef Code, llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H Index: clangd/Headers.cpp =================================================================== --- clangd/Headers.cpp +++ clangd/Headers.cpp @@ -0,0 +1,117 @@ +//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "Compiler.h" +#include "Logger.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" + +namespace clang { +namespace clangd { +namespace { + +class RecordHeaders : public PPCallbacks { +public: + RecordHeaders(std::set &Headers) : Headers(Headers) {} + + void InclusionDirective(SourceLocation /*HashLoc*/, + const Token & /*IncludeTok*/, + llvm::StringRef /*FileName*/, bool /*IsAngled*/, + CharSourceRange /*FilenameRange*/, + const FileEntry *File, llvm::StringRef /*SearchPath*/, + llvm::StringRef /*RelativePath*/, + const Module * /*Imported*/) override { + if (File != nullptr && !File->tryGetRealPathName().empty()) + Headers.insert(File->tryGetRealPathName()); + } + +private: + std::set &Headers; +}; + +} // namespace + +/// FIXME(ioeric): we might not want to insert an absolute include path if the +/// path is not shortened. +llvm::Expected +shortenIncludePath(llvm::StringRef File, llvm::StringRef Code, + llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS) { + // Set up a CompilerInstance and create a preprocessor to collect existing + // #include headers in \p Code. Preprocesor also provides HeaderSearch with + // which we can calculate the shortest include path for \p Header. + std::vector Argv; + for (const auto &S : CompileCommand.CommandLine) + Argv.push_back(S.c_str()); + 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; + // Parse the main file to get all existing #includes in the file, and then we + // can make sure the same header (even with different include path) is not + // added more than once. + 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 " + File, + llvm::inconvertibleErrorCode()); + PreprocessOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) + return llvm::make_error( + "Failed to begin preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + std::set ExistingHeaders; + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(ExistingHeaders)); + if (!Action.Execute()) + return llvm::make_error( + "Failed to execute preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + if (ExistingHeaders.find(Header) != ExistingHeaders.end()) { + return llvm::make_error( + Header + " is already included in " + File, + llvm::inconvertibleErrorCode()); + } + + auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); + bool IsSystem = false; + std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( + Header, CompileCommand.Directory, &IsSystem); + if (IsSystem) + Suggested = "<" + Suggested + ">"; + else + Suggested = "\"" + Suggested + "\""; + + log("Suggested #include for " + Header + " is: " + Suggested); + return Suggested; +} + +} // namespace clangd +} // namespace clang Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -82,6 +82,7 @@ /// The text document's URI. URIForFile uri; }; +json::Expr toJSON(const TextDocumentIdentifier &); bool fromJSON(const json::Expr &, TextDocumentIdentifier &); struct Position { @@ -424,6 +425,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; + + /// The header to be inserted. This could be either a URI ir a literal string + /// quoted with <> or "" that can be #included directly. + std::string header; +}; +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 @@ -435,15 +447,25 @@ struct ExecuteCommandParams { // Command to apply fix-its. Uses WorkspaceEdit as argument. const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + // Command to insert an #include into code. + 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 : public ExecuteCommandParams { + std::string title; +}; + +json::Expr toJSON(const Command &C); + struct ApplyWorkspaceEditParams { WorkspaceEdit edit; }; @@ -560,12 +582,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 @@ -57,6 +57,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); @@ -326,6 +330,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); @@ -336,10 +342,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{}; @@ -349,6 +367,15 @@ 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("header", R.header); +} +json::Expr toJSON(const IncludeInsertion &II) { + return json::obj{{"textDocument", II.textDocument}, {"header", II.header}}; +} + json::Expr toJSON(const ApplyWorkspaceEditParams &Params) { return json::obj{{"edit", Params.edit}}; } @@ -380,6 +407,8 @@ 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/global-symbol-builder/GlobalSymbolBuilderMain.cpp =================================================================== --- clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp +++ clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -13,10 +13,12 @@ // //===---------------------------------------------------------------------===// +#include "index/CanonicalIncludes.h" #include "index/Index.h" #include "index/Merge.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Index/IndexDataConsumer.h" @@ -57,11 +59,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(); @@ -78,6 +88,8 @@ private: tooling::ExecutionContext *Ctx; std::shared_ptr Collector; + std::unique_ptr Includes; + std::unique_ptr PragmaHandler; }; index::IndexingOptions IndexOpts; @@ -86,9 +98,13 @@ IndexOpts.IndexFunctionLocals = false; auto CollectorOpts = SymbolCollector::Options(); CollectorOpts.FallbackDir = AssumedHeaderDir; + CollectorOpts.CollectIncludePath = true; + auto Includes = llvm::make_unique(); + addSystemHeadersMapping(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 =================================================================== --- clangd/index/CanonicalIncludes.h +++ clangd/index/CanonicalIncludes.h @@ -0,0 +1,79 @@ +//===-- 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. +// +//===----------------------------------------------------------------------===// +// +// At indexing time, we decide which file to #included for a symbol. +// Usually this is the file with the canonical decl, but there are exceptions: +// - private headers may have pragmas pointing to the matching public header. +// (These are "IWYU" pragmas, named after the include-what-you-use tool). +// - the standard library is implemented in many files, without any pragmas. +// We have a lookup table for common standard library implementations. +// libstdc++ puts char_traits in bits/char_traits.h, but we #include . +// +//===---------------------------------------------------------------------===// + +#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 { + +/// Maps a definition location onto an #include file, based on a set of filename +/// rules. +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); + + /// Maps all files matching \p RE to \p CanonicalPath + void addRegexMapping(llvm::StringRef RE, llvm::StringRef CanonicalPath); + + /// \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 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. Mappinps are registered with \p Includes. +/// +/// 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 system headers. Approximately, the following system headers +/// are handled: +/// - C++ standard library e.g. bits/basic_string.h$ -> +/// - Posix library e.g. bits/pthreadtypes.h$ -> +/// - Compiler extensions, e.g. include/avx512bwintrin.h$ -> +/// The mapping is hardcoded and hand-maintained, so it might not cover all +/// headers. +void addSystemHeadersMapping(CanonicalIncludes *Includes); + +} // namespace clangd +} // namespace clang + +#endif //LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_HEADERMAPCOLLECTOR_H Index: clangd/index/CanonicalIncludes.cpp =================================================================== --- clangd/index/CanonicalIncludes.cpp +++ clangd/index/CanonicalIncludes.cpp @@ -0,0 +1,704 @@ +//===-- 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::addMapping(llvm::StringRef Path, + llvm::StringRef CanonicalPath) { + addRegexMapping((llvm::Twine("^") + Path + "$").str(), CanonicalPath); +}; + +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 { + 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()); + if (!Text.consume_front(IWYUPragma)) + return false; + // 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()), + Text.startswith("<") ? Text.str() + : ("\"" + Text + "\"").str()); + return false; + } + + private: + CanonicalIncludes *const Includes; + }; + return llvm::make_unique(Includes); +} + +void addSystemHeadersMapping(CanonicalIncludes *Includes) { + static const std::vector> + SystemHeaderMap = { + {"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 : SystemHeaderMap) + Includes->addRegexMapping(Pair.first, Pair.second); +} + +} // namespace clangd +} // namespace clang Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -152,11 +152,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; + /// This can be either a URI of the header to be #include'd for this symbol, + /// or a literal header quoted with <> or "" that is 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 IncludeHeader; }; // Optional details of the symbol. Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -80,6 +80,7 @@ // Intern the actual strings. Intern(Detail->Documentation); Intern(Detail->CompletionDetail); + Intern(Detail->IncludeHeader); // Replace the detail pointer with our copy. S.Detail = Detail; } Index: clangd/index/Merge.cpp =================================================================== --- clangd/index/Merge.cpp +++ clangd/index/Merge.cpp @@ -90,6 +90,8 @@ Scratch->Documentation = O.Detail->Documentation; if (Scratch->CompletionDetail == "") Scratch->CompletionDetail = O.Detail->CompletionDetail; + if (Scratch->IncludeHeader == "") + Scratch->IncludeHeader = O.Detail->IncludeHeader; S.Detail = Scratch; } else S.Detail = O.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" @@ -39,6 +40,10 @@ /// 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. + const CanonicalIncludes *Includes = nullptr; }; SymbolCollector(Options Opts); Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -11,6 +11,7 @@ #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" @@ -127,6 +128,51 @@ 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 a canonical include (
or "header") for header of \p Loc. +/// Returns None if the header has no canonical include. +/// FIXME: we should handle .inc files whose symbols are expected be exported by +/// their containing headers. +llvm::Optional +getIncludeHeader(const SourceManager &SM, SourceLocation Loc, + const SymbolCollector::Options &Opts) { + llvm::StringRef FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return llvm::None; + 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. + return llvm::None; +} + // Return the symbol location of the given declaration `D`. // // For symbols defined inside macros: @@ -252,6 +298,14 @@ 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(ND.getLocation()), Opts)) + Include = std::move(*Header); + } S.CompletionFilterText = FilterText; S.CompletionLabel = Label; S.CompletionPlainInsertText = PlainInsertText; @@ -259,6 +313,7 @@ Symbol::Details Detail; Detail.Documentation = Documentation; Detail.CompletionDetail = CompletionDetail; + Detail.IncludeHeader = Include; S.Detail = &Detail; Symbols.insert(S); Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -64,6 +64,7 @@ static void mapping(IO &io, Symbol::Details &Detail) { io.mapOptional("Documentation", Detail.Documentation); io.mapOptional("CompletionDetail", Detail.CompletionDetail); + io.mapOptional("IncludeHeader", Detail.IncludeHeader); } }; Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -16,6 +16,7 @@ #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 Index: test/clangd/completion-snippets.test =================================================================== --- test/clangd/completion-snippets.test +++ test/clangd/completion-snippets.test @@ -28,9 +28,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "isIncomplete": {{.*}} # CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "func_with_args", +# CHECK: "filterText": "func_with_args", # CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})", # CHECK-NEXT: "insertTextFormat": 2, # CHECK-NEXT: "kind": 3, 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: test/clangd/insert-include.test =================================================================== --- test/clangd/insert-include.test +++ test/clangd/insert-include.test @@ -0,0 +1,36 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void f() {}"}}} +--- +{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{"arguments":[{"header":"","textDocument":{"uri":"test:///main.cpp"}}],"command":"clangd.insertInclude"}} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": "Inserted header " +# CHECK-NEXT: } +# CHECK: "method": "workspace/applyEdit", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "edit": { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///clangd-test/main.cpp": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "#include \n", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"} Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -17,6 +17,7 @@ ContextTests.cpp FileIndexTests.cpp FuzzyMatchTests.cpp + HeadersTests.cpp IndexTests.cpp JSONExprTests.cpp SourceCodeTests.cpp Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -12,6 +12,7 @@ #include "Matchers.h" #include "SyncAPI.h" #include "TestFS.h" +#include "URI.h" #include "clang/Config/config.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" @@ -100,8 +101,6 @@ return replacePtrsInDump(DumpWithMemLocs); } -} // namespace - class ClangdVFSTest : public ::testing::Test { protected: std::string parseSourceAndDumpAST( @@ -819,5 +818,38 @@ ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? } +TEST_F(ClangdVFSTest, InsertIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + // No need to sync reparses, because reparses are performed on the calling + // thread. + auto FooCpp = testPath("foo.cpp"); + const auto Code = R"cpp( +#include "x.h" + +void f() {} +)cpp"; + FS.Files[FooCpp] = Code; + Server.addDocument(FooCpp, Code); + + auto Inserted = [&](llvm::StringRef Header, llvm::StringRef Expected) { + auto Replaces = Server.insertInclude(FooCpp, Code, Header); + EXPECT_TRUE(static_cast(Replaces)); + auto Changed = tooling::applyAllReplacements(Code, *Replaces); + EXPECT_TRUE(static_cast(Changed)); + return llvm::StringRef(*Changed).contains( + (llvm::Twine("#include ") + Expected + "").str()); + }; + + EXPECT_TRUE(Inserted("\"y.h\"", "\"y.h\"")); + EXPECT_TRUE(Inserted("", "")); +} + +} // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/HeadersTests.cpp =================================================================== --- unittests/clangd/HeadersTests.cpp +++ unittests/clangd/HeadersTests.cpp @@ -0,0 +1,98 @@ +//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "TestFS.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +class HeadersTest : public ::testing::Test { +public: + HeadersTest() { + CDB.ExtraClangFlags = {SearchDirArg.c_str()}; + FS.Files[MainFile] = ""; + } + +protected: + // Adds \p File with content \p Code to the sub directory and returns the + // absolute path. +// std::string addToSubdir(PathRef File, llvm::StringRef Code = "") { +// assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); +// llvm::SmallString<32> Path; +// llvm::sys::path::append(Path, Subdir, File); +// FS.Files[Path] = Code; +// return Path.str(); +// }; + + // Shortens the header and returns "" on error. + std::string shorten(PathRef Header) { + auto VFS = FS.getTaggedFileSystem(MainFile).Value; + auto Cmd = CDB.getCompileCommand(MainFile); + assert(static_cast(Cmd)); + VFS->setCurrentWorkingDirectory(Cmd->Directory); + auto Path = + shortenIncludePath(MainFile, FS.Files[MainFile], Header, *Cmd, VFS); + if (!Path) { + llvm::consumeError(Path.takeError()); + return std::string(); + } + return std::move(*Path); + } + MockFSProvider FS; + MockCompilationDatabase CDB; + std::string MainFile = testPath("main.cpp"); + std::string Subdir = testPath("sub"); + std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str(); +}; + +TEST_F(HeadersTest, InsertInclude) { + std::string Path = testPath("sub/bar.h"); + FS.Files[Path] = ""; + EXPECT_EQ(shorten(Path), "\"bar.h\""); +} + +TEST_F(HeadersTest, DontInsertDuplicateSameName) { + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + EXPECT_EQ(shorten(BarHeader), ""); +} + +TEST_F(HeadersTest, DontInsertDuplicateDifferentName) { + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + FS.Files[MainFile] = R"cpp( +#include "sub/bar.h" // not shortest +)cpp"; + EXPECT_EQ(shorten(BarHeader), ""); +} + +TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) { + std::string BazHeader = testPath("sub/baz.h"); + FS.Files[BazHeader] = ""; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = R"cpp( +#include "baz.h" +)cpp"; + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + EXPECT_EQ(shorten(BazHeader), "\"baz.h\""); +} + +} // namespace +} // namespace clangd +} // namespace clang + Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -47,6 +47,9 @@ } MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; } +MATCHER_P(IncludeHeader, P, "") { + return arg.Detail && arg.Detail->IncludeHeader == P; +} MATCHER_P(DeclRange, Offsets, "") { return arg.CanonicalDeclaration.StartOffset == Offsets.first && arg.CanonicalDeclaration.EndOffset == Offsets.second; @@ -62,28 +65,50 @@ namespace { 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() - : TestHeaderName(testPath("symbol.h")), + : InMemoryFileSystem(new vfs::InMemoryFileSystem), + TestHeaderName(testPath("symbol.h")), TestFileName(testPath("symbol.cc")) { TestHeaderURI = URI::createFile(TestHeaderName).toString(); TestFileURI = URI::createFile(TestFileName).toString(); @@ -91,12 +116,11 @@ 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", "-include", @@ -117,12 +141,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) { @@ -554,6 +580,46 @@ QName("clang::Foo2"))); } +TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { + CollectorOpts.CollectIncludePath = true; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeHeader("")))); +} + +#ifndef LLVM_ON_WIN32 +TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + addSystemHeadersMapping(&Includes); + CollectorOpts.Includes = &Includes; + // bits/basic_string.h$ should be mapped to + TestHeaderName = "/nasty/bits/basic_string.h"; + TestFileName = "/nasty/bits/basic_string.cpp"; + TestHeaderURI = URI::createFile(TestHeaderName).toString(); + runSymbolCollector("class string {};", /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), + DeclURI(TestHeaderURI), + IncludeHeader("")))); +} +#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), + IncludeHeader("\"the/good/header.h\"")))); +} + } // namespace } // namespace clangd } // namespace clang