1 //===--- HeaderSourceSwitch.cpp - --------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "HeaderSourceSwitch.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"
20 llvm::Optional
<Path
> getCorrespondingHeaderOrSource(
21 PathRef OriginalFile
, llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> VFS
) {
22 llvm::StringRef SourceExtensions
[] = {".cpp", ".c", ".cc", ".cxx",
24 llvm::StringRef HeaderExtensions
[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
26 llvm::StringRef PathExt
= llvm::sys::path::extension(OriginalFile
);
28 // Lookup in a list of known extensions.
30 llvm::find_if(SourceExtensions
, [&PathExt
](PathRef SourceExt
) {
31 return SourceExt
.equals_insensitive(PathExt
);
33 bool IsSource
= SourceIter
!= std::end(SourceExtensions
);
36 llvm::find_if(HeaderExtensions
, [&PathExt
](PathRef HeaderExt
) {
37 return HeaderExt
.equals_insensitive(PathExt
);
39 bool IsHeader
= HeaderIter
!= std::end(HeaderExtensions
);
41 // We can only switch between the known extensions.
42 if (!IsSource
&& !IsHeader
)
45 // Array to lookup extensions for the switch. An opposite of where original
46 // extension was found.
47 llvm::ArrayRef
<llvm::StringRef
> NewExts
;
49 NewExts
= HeaderExtensions
;
51 NewExts
= SourceExtensions
;
53 // Storage for the new path.
54 llvm::SmallString
<128> NewPath
= OriginalFile
;
56 // Loop through switched extension candidates.
57 for (llvm::StringRef NewExt
: NewExts
) {
58 llvm::sys::path::replace_extension(NewPath
, NewExt
);
59 if (VFS
->exists(NewPath
))
62 // Also check NewExt in upper-case, just in case.
63 llvm::sys::path::replace_extension(NewPath
, NewExt
.upper());
64 if (VFS
->exists(NewPath
))
70 llvm::Optional
<Path
> getCorrespondingHeaderOrSource(PathRef OriginalFile
,
72 const SymbolIndex
*Index
) {
74 // FIXME: use the AST to do the inference.
77 LookupRequest Request
;
78 // Find all symbols present in the original file.
79 for (const auto *D
: getIndexableLocalDecls(AST
)) {
80 if (auto ID
= getSymbolID(D
))
81 Request
.IDs
.insert(ID
);
83 llvm::StringMap
<int> Candidates
; // Target path => score.
84 auto AwardTarget
= [&](const char *TargetURI
) {
85 if (auto TargetPath
= URI::resolve(TargetURI
, OriginalFile
)) {
86 if (!pathEqual(*TargetPath
, OriginalFile
)) // exclude the original file.
87 ++Candidates
[*TargetPath
];
89 elog("Failed to resolve URI {0}: {1}", TargetURI
, TargetPath
.takeError());
92 // If we switch from a header, we are looking for the implementation
93 // file, so we use the definition loc; otherwise we look for the header file,
94 // we use the decl loc;
96 // For each symbol in the original file, we get its target location (decl or
97 // def) from the index, then award that target file.
98 bool IsHeader
= isHeaderFile(OriginalFile
, AST
.getLangOpts());
99 Index
->lookup(Request
, [&](const Symbol
&Sym
) {
101 AwardTarget(Sym
.Definition
.FileURI
);
103 AwardTarget(Sym
.CanonicalDeclaration
.FileURI
);
105 // FIXME: our index doesn't have any interesting information (this could be
106 // that the background-index is not finished), we should use the decl/def
107 // locations from the AST to do the inference (from .cc to .h).
108 if (Candidates
.empty())
111 // Pickup the winner, who contains most of symbols.
112 // FIXME: should we use other signals (file proximity) to help score?
113 auto Best
= Candidates
.begin();
114 for (auto It
= Candidates
.begin(); It
!= Candidates
.end(); ++It
) {
115 if (It
->second
> Best
->second
)
117 else if (It
->second
== Best
->second
&& It
->first() < Best
->first())
118 // Select the first one in the lexical order if we have multiple
122 return Path(Best
->first());
125 std::vector
<const Decl
*> getIndexableLocalDecls(ParsedAST
&AST
) {
126 std::vector
<const Decl
*> Results
;
127 std::function
<void(Decl
*)> TraverseDecl
= [&](Decl
*D
) {
128 auto *ND
= llvm::dyn_cast
<NamedDecl
>(D
);
129 if (!ND
|| ND
->isImplicit())
131 if (!SymbolCollector::shouldCollectSymbol(*ND
, D
->getASTContext(), {},
132 /*IsMainFileSymbol=*/false))
134 if (!llvm::isa
<FunctionDecl
>(ND
)) {
135 // Visit the children, but we skip function decls as we are not interested
136 // in the function body.
137 if (auto *Scope
= llvm::dyn_cast
<DeclContext
>(ND
)) {
138 for (auto *D
: Scope
->decls())
142 if (llvm::isa
<NamespaceDecl
>(D
))
143 return; // namespace is indexable, but we're not interested.
144 Results
.push_back(D
);
146 // Traverses the ParsedAST directly to collect all decls present in the main
148 for (auto *TopLevel
: AST
.getLocalTopLevelDecls())
149 TraverseDecl(TopLevel
);
153 } // namespace clangd