1//===--- HeaderSourceSwitch.cpp - --------------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "HeaderSourceSwitch.h"
10#include "AST.h"
11#include "SourceCode.h"
12#include "index/SymbolCollector.h"
13#include "support/Logger.h"
14#include "support/Path.h"
15#include "clang/AST/Decl.h"
16#include <optional>
17
18namespace clang {
19namespace clangd {
20
21std::optional<Path> getCorrespondingHeaderOrSource(
22 PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
23 llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
24 ".c++", ".m", ".mm"};
25 llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx",
26 ".inc", ".cppm", ".ccm", ".cxxm",
27 ".c++m", ".ixx"};
28
29 llvm::StringRef PathExt = llvm::sys::path::extension(path: OriginalFile);
30
31 // Lookup in a list of known extensions.
32 bool IsSource = llvm::any_of(Range&: SourceExtensions, P: [&PathExt](PathRef SourceExt) {
33 return SourceExt.equals_insensitive(RHS: PathExt);
34 });
35
36 bool IsHeader = llvm::any_of(Range&: HeaderExtensions, P: [&PathExt](PathRef HeaderExt) {
37 return HeaderExt.equals_insensitive(RHS: PathExt);
38 });
39
40 // We can only switch between the known extensions.
41 if (!IsSource && !IsHeader)
42 return std::nullopt;
43
44 // Array to lookup extensions for the switch. An opposite of where original
45 // extension was found.
46 llvm::ArrayRef<llvm::StringRef> NewExts;
47 if (IsSource)
48 NewExts = HeaderExtensions;
49 else
50 NewExts = SourceExtensions;
51
52 // Storage for the new path.
53 llvm::SmallString<128> NewPath = OriginalFile;
54
55 // Loop through switched extension candidates.
56 for (llvm::StringRef NewExt : NewExts) {
57 llvm::sys::path::replace_extension(path&: NewPath, extension: NewExt);
58 if (VFS->exists(Path: NewPath))
59 return Path(NewPath);
60
61 // Also check NewExt in upper-case, just in case.
62 llvm::sys::path::replace_extension(path&: NewPath, extension: NewExt.upper());
63 if (VFS->exists(Path: NewPath))
64 return Path(NewPath);
65 }
66 return std::nullopt;
67}
68
69std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
70 ParsedAST &AST,
71 const SymbolIndex *Index) {
72 if (!Index) {
73 // FIXME: use the AST to do the inference.
74 return std::nullopt;
75 }
76 LookupRequest Request;
77 // Find all symbols present in the original file.
78 for (const auto *D : getIndexableLocalDecls(AST)) {
79 if (auto ID = getSymbolID(D))
80 Request.IDs.insert(V: ID);
81 }
82 llvm::StringMap<int> Candidates; // Target path => score.
83 auto AwardTarget = [&](const char *TargetURI) {
84 if (auto TargetPath = URI::resolve(FileURI: TargetURI, HintPath: OriginalFile)) {
85 if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
86 ++Candidates[*TargetPath];
87 } else {
88 elog(Fmt: "Failed to resolve URI {0}: {1}", Vals&: TargetURI, Vals: TargetPath.takeError());
89 }
90 };
91 // If we switch from a header, we are looking for the implementation
92 // file, so we use the definition loc; otherwise we look for the header file,
93 // we use the decl loc;
94 //
95 // For each symbol in the original file, we get its target location (decl or
96 // def) from the index, then award that target file.
97 bool IsHeader = isHeaderFile(FileName: OriginalFile, LangOpts: AST.getLangOpts());
98 Index->lookup(Req: Request, Callback: [&](const Symbol &Sym) {
99 if (IsHeader)
100 AwardTarget(Sym.Definition.FileURI);
101 else
102 AwardTarget(Sym.CanonicalDeclaration.FileURI);
103 });
104 // FIXME: our index doesn't have any interesting information (this could be
105 // that the background-index is not finished), we should use the decl/def
106 // locations from the AST to do the inference (from .cc to .h).
107 if (Candidates.empty())
108 return std::nullopt;
109
110 // Pickup the winner, who contains most of symbols.
111 // FIXME: should we use other signals (file proximity) to help score?
112 auto Best = Candidates.begin();
113 for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
114 if (It->second > Best->second)
115 Best = It;
116 else if (It->second == Best->second && It->first() < Best->first())
117 // Select the first one in the lexical order if we have multiple
118 // candidates.
119 Best = It;
120 }
121 return Path(Best->first());
122}
123
124std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
125 std::vector<const Decl *> Results;
126 std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
127 auto *ND = llvm::dyn_cast<NamedDecl>(Val: D);
128 if (!ND || ND->isImplicit())
129 return;
130 if (!SymbolCollector::shouldCollectSymbol(ND: *ND, ASTCtx: D->getASTContext(), Opts: {},
131 /*IsMainFileSymbol=*/false))
132 return;
133 if (!llvm::isa<FunctionDecl>(Val: ND)) {
134 // Visit the children, but we skip function decls as we are not interested
135 // in the function body.
136 if (auto *Scope = llvm::dyn_cast<DeclContext>(Val: ND)) {
137 for (auto *D : Scope->decls())
138 TraverseDecl(D);
139 }
140 }
141 if (llvm::isa<NamespaceDecl>(Val: D))
142 return; // namespace is indexable, but we're not interested.
143 Results.push_back(x: D);
144 };
145 // Traverses the ParsedAST directly to collect all decls present in the main
146 // file.
147 for (auto *TopLevel : AST.getLocalTopLevelDecls())
148 TraverseDecl(TopLevel);
149 return Results;
150}
151
152} // namespace clangd
153} // namespace clang
154

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clangd/HeaderSourceSwitch.cpp