clang-tools 22.0.0git
HeaderSourceSwitch.cpp
Go to the documentation of this file.
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
10#include "AST.h"
11#include "SourceCode.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
22 PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
23 static constexpr llvm::StringRef SourceExtensions[] = {
24 ".cpp", ".c", ".cc", ".cxx", ".c++", ".m", ".mm"};
25 static constexpr llvm::StringRef HeaderExtensions[] = {
26 ".h", ".hh", ".hpp", ".hxx", ".inc",
27 ".cppm", ".ccm", ".cxxm", ".c++m", ".ixx"};
28
29 llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
30
31 // Lookup in a list of known extensions.
32 const bool IsSource =
33 llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
34 return SourceExt.equals_insensitive(PathExt);
35 });
36
37 const bool IsHeader =
38 llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
39 return HeaderExt.equals_insensitive(PathExt);
40 });
41
42 // We can only switch between the known extensions.
43 if (!IsSource && !IsHeader)
44 return std::nullopt;
45
46 // Array to lookup extensions for the switch. An opposite of where original
47 // extension was found.
48 llvm::ArrayRef<llvm::StringRef> NewExts;
49 if (IsSource)
50 NewExts = HeaderExtensions;
51 else
52 NewExts = SourceExtensions;
53
54 // Storage for the new path.
55 llvm::SmallString<128> NewPath = OriginalFile;
56
57 // Loop through switched extension candidates.
58 for (llvm::StringRef NewExt : NewExts) {
59 llvm::sys::path::replace_extension(NewPath, NewExt);
60 if (VFS->exists(NewPath))
61 return Path(NewPath);
62
63 // Also check NewExt in upper-case, just in case.
64 llvm::sys::path::replace_extension(NewPath, NewExt.upper());
65 if (VFS->exists(NewPath))
66 return Path(NewPath);
67 }
68 return std::nullopt;
69}
70
71std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
73 const SymbolIndex *Index) {
74 if (!Index) {
75 // FIXME: use the AST to do the inference.
76 return std::nullopt;
77 }
78 LookupRequest Request;
79 // Find all symbols present in the original file.
80 for (const auto *D : getIndexableLocalDecls(AST)) {
81 if (auto ID = getSymbolID(D))
82 Request.IDs.insert(ID);
83 }
84 llvm::StringMap<int> Candidates; // Target path => score.
85 auto AwardTarget = [&](const char *TargetURI) {
86 if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
87 if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
88 ++Candidates[*TargetPath];
89 } else {
90 elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
91 }
92 };
93 // If we switch from a header, we are looking for the implementation
94 // file, so we use the definition loc; otherwise we look for the header file,
95 // we use the decl loc;
96 //
97 // For each symbol in the original file, we get its target location (decl or
98 // def) from the index, then award that target file.
99 const bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
100 Index->lookup(Request, [&](const Symbol &Sym) {
101 if (IsHeader)
102 AwardTarget(Sym.Definition.FileURI);
103 else
104 AwardTarget(Sym.CanonicalDeclaration.FileURI);
105 });
106 // FIXME: our index doesn't have any interesting information (this could be
107 // that the background-index is not finished), we should use the decl/def
108 // locations from the AST to do the inference (from .cc to .h).
109 if (Candidates.empty())
110 return std::nullopt;
111
112 // Pickup the winner, who contains most of symbols.
113 // FIXME: should we use other signals (file proximity) to help score?
114 auto Best = Candidates.begin();
115 for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
116 if (It->second > Best->second)
117 Best = It;
118 else if (It->second == Best->second && It->first() < Best->first())
119 // Select the first one in the lexical order if we have multiple
120 // candidates.
121 Best = It;
122 }
123 return Path(Best->first());
124}
125
126std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
127 std::vector<const Decl *> Results;
128 std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
129 auto *ND = llvm::dyn_cast<NamedDecl>(D);
130 if (!ND || ND->isImplicit())
131 return;
132 if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
133 /*IsMainFileSymbol=*/false))
134 return;
135 if (!llvm::isa<FunctionDecl>(ND)) {
136 // Visit the children, but we skip function decls as we are not interested
137 // in the function body.
138 if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
139 for (auto *D : Scope->decls())
140 TraverseDecl(D);
141 }
142 }
143 if (llvm::isa<NamespaceDecl>(D))
144 return; // namespace is indexable, but we're not interested.
145 Results.push_back(D);
146 };
147 // Traverses the ParsedAST directly to collect all decls present in the main
148 // file.
149 for (auto *TopLevel : AST.getLocalTopLevelDecls())
150 TraverseDecl(TopLevel);
151 return Results;
152}
153
154} // namespace clangd
155} // namespace clang
Stores and provides access to parsed AST.
Definition ParsedAST.h:46
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
Definition Index.h:134
virtual void lookup(const LookupRequest &Req, llvm::function_ref< void(const Symbol &)> Callback) const =0
Looks up symbols with any of the given symbol IDs and applies Callback on each matched symbol.
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Resolves the absolute path of U.
Definition URI.cpp:244
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Definition AST.cpp:354
bool pathEqual(PathRef A, PathRef B)
Definition Path.cpp:19
std::vector< const Decl * > getIndexableLocalDecls(ParsedAST &AST)
Returns all indexable decls that are present in the main file of the AST.
std::optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
std::string Path
A typedef to represent a file path.
Definition Path.h:26
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::DenseSet< SymbolID > IDs
Definition Index.h:65
The class presents a C++ symbol, e.g.
Definition Symbol.h:39
SymbolLocation Definition
The location of the symbol's definition, if one was found.
Definition Symbol.h:50
SymbolLocation CanonicalDeclaration
The location of the preferred declaration of the symbol.
Definition Symbol.h:59