1 //===--- AddUsing.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 "AST.h"
10 #include "Config.h"
11 #include "FindTarget.h"
12 #include "refactor/Tweak.h"
13 #include "support/Logger.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/AST/RecursiveASTVisitor.h"
16
17 namespace clang {
18 namespace clangd {
19 namespace {
20
21 // Tweak for removing full namespace qualifier under cursor on DeclRefExpr and
22 // types and adding "using" statement instead.
23 //
24 // Only qualifiers that refer exclusively to namespaces (no record types) are
25 // supported. There is some guessing of appropriate place to insert the using
26 // declaration. If we find any existing usings, we insert it there. If not, we
27 // insert right after the inner-most relevant namespace declaration. If there is
28 // none, or there is, but it was declared via macro, we insert above the first
29 // top level decl.
30 //
31 // Currently this only removes qualifier from under the cursor. In the future,
32 // we should improve this to remove qualifier from all occurrences of this
33 // symbol.
34 class AddUsing : public Tweak {
35 public:
36 const char *id() const override;
37
38 bool prepare(const Selection &Inputs) override;
39 Expected<Effect> apply(const Selection &Inputs) override;
40 std::string title() const override;
kind() const41 llvm::StringLiteral kind() const override {
42 return CodeAction::REFACTOR_KIND;
43 }
44
45 private:
46 // All of the following are set by prepare().
47 // The qualifier to remove.
48 NestedNameSpecifierLoc QualifierToRemove;
49 // The name following QualifierToRemove.
50 llvm::StringRef Name;
51 // If valid, the insertion point for "using" statement must come after this.
52 // This is relevant when the type is defined in the main file, to make sure
53 // the type/function is already defined at the point where "using" is added.
54 SourceLocation MustInsertAfterLoc;
55 };
REGISTER_TWEAK(AddUsing)56 REGISTER_TWEAK(AddUsing)
57
58 std::string AddUsing::title() const {
59 return std::string(llvm::formatv(
60 "Add using-declaration for {0} and remove qualifier", Name));
61 }
62
63 // Locates all "using" statements relevant to SelectionDeclContext.
64 class UsingFinder : public RecursiveASTVisitor<UsingFinder> {
65 public:
UsingFinder(std::vector<const UsingDecl * > & Results,const DeclContext * SelectionDeclContext,const SourceManager & SM)66 UsingFinder(std::vector<const UsingDecl *> &Results,
67 const DeclContext *SelectionDeclContext, const SourceManager &SM)
68 : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {}
69
VisitUsingDecl(UsingDecl * D)70 bool VisitUsingDecl(UsingDecl *D) {
71 auto Loc = D->getUsingLoc();
72 if (SM.getFileID(Loc) != SM.getMainFileID()) {
73 return true;
74 }
75 if (D->getDeclContext()->Encloses(SelectionDeclContext)) {
76 Results.push_back(D);
77 }
78 return true;
79 }
80
TraverseDecl(Decl * Node)81 bool TraverseDecl(Decl *Node) {
82 // There is no need to go deeper into nodes that do not enclose selection,
83 // since "using" there will not affect selection, nor would it make a good
84 // insertion point.
85 if (!Node->getDeclContext() ||
86 Node->getDeclContext()->Encloses(SelectionDeclContext)) {
87 return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);
88 }
89 return true;
90 }
91
92 private:
93 std::vector<const UsingDecl *> &Results;
94 const DeclContext *SelectionDeclContext;
95 const SourceManager &SM;
96 };
97
isFullyQualified(const NestedNameSpecifier * NNS)98 bool isFullyQualified(const NestedNameSpecifier *NNS) {
99 if (!NNS)
100 return false;
101 return NNS->getKind() == NestedNameSpecifier::Global ||
102 isFullyQualified(NNS->getPrefix());
103 }
104
105 struct InsertionPointData {
106 // Location to insert the "using" statement. If invalid then the statement
107 // should not be inserted at all (it already exists).
108 SourceLocation Loc;
109 // Extra suffix to place after the "using" statement. Depending on what the
110 // insertion point is anchored to, we may need one or more \n to ensure
111 // proper formatting.
112 std::string Suffix;
113 // Whether using should be fully qualified, even if what the user typed was
114 // not. This is based on our detection of the local style.
115 bool AlwaysFullyQualify = false;
116 };
117
118 // Finds the best place to insert the "using" statement. Returns invalid
119 // SourceLocation if the "using" statement already exists.
120 //
121 // The insertion point might be a little awkward if the decl we're anchoring to
122 // has a comment in an unfortunate place (e.g. directly above function or using
123 // decl, or immediately following "namespace {". We should add some helpers for
124 // dealing with that and use them in other code modifications as well.
125 llvm::Expected<InsertionPointData>
findInsertionPoint(const Tweak::Selection & Inputs,const NestedNameSpecifierLoc & QualifierToRemove,const llvm::StringRef Name,const SourceLocation MustInsertAfterLoc)126 findInsertionPoint(const Tweak::Selection &Inputs,
127 const NestedNameSpecifierLoc &QualifierToRemove,
128 const llvm::StringRef Name,
129 const SourceLocation MustInsertAfterLoc) {
130 auto &SM = Inputs.AST->getSourceManager();
131
132 // Search for all using decls that affect this point in file. We need this for
133 // two reasons: to skip adding "using" if one already exists and to find best
134 // place to add it, if it doesn't exist.
135 SourceLocation LastUsingLoc;
136 std::vector<const UsingDecl *> Usings;
137 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),
138 SM)
139 .TraverseAST(Inputs.AST->getASTContext());
140
141 auto IsValidPoint = [&](const SourceLocation Loc) {
142 return MustInsertAfterLoc.isInvalid() ||
143 SM.isBeforeInTranslationUnit(MustInsertAfterLoc, Loc);
144 };
145
146 bool AlwaysFullyQualify = true;
147 for (auto &U : Usings) {
148 // Only "upgrade" to fully qualified is all relevant using decls are fully
149 // qualified. Otherwise trust what the user typed.
150 if (!isFullyQualified(U->getQualifier()))
151 AlwaysFullyQualify = false;
152
153 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
154 // "Usings" is sorted, so we're done.
155 break;
156 if (const auto *Namespace = U->getQualifier()->getAsNamespace()) {
157 if (Namespace->getCanonicalDecl() ==
158 QualifierToRemove.getNestedNameSpecifier()
159 ->getAsNamespace()
160 ->getCanonicalDecl() &&
161 U->getName() == Name) {
162 return InsertionPointData();
163 }
164 }
165
166 // Insertion point will be before last UsingDecl that affects cursor
167 // position. For most cases this should stick with the local convention of
168 // add using inside or outside namespace.
169 LastUsingLoc = U->getUsingLoc();
170 }
171 if (LastUsingLoc.isValid() && IsValidPoint(LastUsingLoc)) {
172 InsertionPointData Out;
173 Out.Loc = LastUsingLoc;
174 Out.AlwaysFullyQualify = AlwaysFullyQualify;
175 return Out;
176 }
177
178 // No relevant "using" statements. Try the nearest namespace level.
179 const DeclContext *ParentDeclCtx =
180 &Inputs.ASTSelection.commonAncestor()->getDeclContext();
181 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) {
182 ParentDeclCtx = ParentDeclCtx->getLexicalParent();
183 }
184 if (auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) {
185 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange());
186 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) {
187 return Tok.kind() == tok::l_brace;
188 });
189 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) {
190 return error("Namespace with no {");
191 }
192 if (!Tok->endLocation().isMacroID() && IsValidPoint(Tok->endLocation())) {
193 InsertionPointData Out;
194 Out.Loc = Tok->endLocation();
195 Out.Suffix = "\n";
196 return Out;
197 }
198 }
199 // No using, no namespace, no idea where to insert. Try above the first
200 // top level decl after MustInsertAfterLoc.
201 auto TLDs = Inputs.AST->getLocalTopLevelDecls();
202 for (const auto &TLD : TLDs) {
203 if (!IsValidPoint(TLD->getBeginLoc()))
204 continue;
205 InsertionPointData Out;
206 Out.Loc = SM.getExpansionLoc(TLD->getBeginLoc());
207 Out.Suffix = "\n\n";
208 return Out;
209 }
210 return error("Cannot find place to insert \"using\"");
211 }
212
isNamespaceForbidden(const Tweak::Selection & Inputs,const NestedNameSpecifier & Namespace)213 bool isNamespaceForbidden(const Tweak::Selection &Inputs,
214 const NestedNameSpecifier &Namespace) {
215 std::string NamespaceStr = printNamespaceScope(*Namespace.getAsNamespace());
216
217 for (StringRef Banned : Config::current().Style.FullyQualifiedNamespaces) {
218 StringRef PrefixMatch = NamespaceStr;
219 if (PrefixMatch.consume_front(Banned) && PrefixMatch.consume_front("::"))
220 return true;
221 }
222
223 return false;
224 }
225
getNNSLAsString(NestedNameSpecifierLoc & NNSL,const PrintingPolicy & Policy)226 std::string getNNSLAsString(NestedNameSpecifierLoc &NNSL,
227 const PrintingPolicy &Policy) {
228 std::string Out;
229 llvm::raw_string_ostream OutStream(Out);
230 NNSL.getNestedNameSpecifier()->print(OutStream, Policy);
231 return OutStream.str();
232 }
233
prepare(const Selection & Inputs)234 bool AddUsing::prepare(const Selection &Inputs) {
235 auto &SM = Inputs.AST->getSourceManager();
236 const auto &TB = Inputs.AST->getTokens();
237
238 // Do not suggest "using" in header files. That way madness lies.
239 if (isHeaderFile(SM.getFileEntryForID(SM.getMainFileID())->getName(),
240 Inputs.AST->getLangOpts()))
241 return false;
242
243 auto *Node = Inputs.ASTSelection.commonAncestor();
244 if (Node == nullptr)
245 return false;
246
247 // If we're looking at a type or NestedNameSpecifier, walk up the tree until
248 // we find the "main" node we care about, which would be ElaboratedTypeLoc or
249 // DeclRefExpr.
250 for (; Node->Parent; Node = Node->Parent) {
251 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {
252 continue;
253 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
254 if (T->getAs<ElaboratedTypeLoc>()) {
255 break;
256 } else if (Node->Parent->ASTNode.get<TypeLoc>() ||
257 Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) {
258 // Node is TypeLoc, but it's parent is either TypeLoc or
259 // NestedNameSpecifier. In both cases, we want to go up, to find
260 // the outermost TypeLoc.
261 continue;
262 }
263 }
264 break;
265 }
266 if (Node == nullptr)
267 return false;
268
269 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {
270 if (auto *II = D->getDecl()->getIdentifier()) {
271 QualifierToRemove = D->getQualifierLoc();
272 Name = II->getName();
273 MustInsertAfterLoc = D->getDecl()->getBeginLoc();
274 }
275 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
276 if (auto E = T->getAs<ElaboratedTypeLoc>()) {
277 QualifierToRemove = E.getQualifierLoc();
278 if (!QualifierToRemove)
279 return false;
280
281 auto SpelledTokens =
282 TB.spelledForExpanded(TB.expandedTokens(E.getSourceRange()));
283 if (!SpelledTokens)
284 return false;
285 auto SpelledRange = syntax::Token::range(SM, SpelledTokens->front(),
286 SpelledTokens->back());
287 Name = SpelledRange.text(SM);
288
289 std::string QualifierToRemoveStr = getNNSLAsString(
290 QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
291 if (!Name.consume_front(QualifierToRemoveStr))
292 return false; // What's spelled doesn't match the qualifier.
293
294 if (const auto *ET = E.getTypePtr()) {
295 if (const auto *TDT =
296 dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) {
297 MustInsertAfterLoc = TDT->getDecl()->getBeginLoc();
298 } else if (auto *TD = ET->getAsTagDecl()) {
299 MustInsertAfterLoc = TD->getBeginLoc();
300 }
301 }
302 }
303 }
304
305 // FIXME: This only supports removing qualifiers that are made up of just
306 // namespace names. If qualifier contains a type, we could take the longest
307 // namespace prefix and remove that.
308 if (!QualifierToRemove.hasQualifier() ||
309 !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() ||
310 Name.empty()) {
311 return false;
312 }
313
314 if (isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier()))
315 return false;
316
317 // Macros are difficult. We only want to offer code action when what's spelled
318 // under the cursor is a namespace qualifier. If it's a macro that expands to
319 // a qualifier, user would not know what code action will actually change.
320 // On the other hand, if the qualifier is part of the macro argument, we
321 // should still support that.
322 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||
323 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),
324 QualifierToRemove.getEndLoc())) {
325 return false;
326 }
327
328 return true;
329 }
330
apply(const Selection & Inputs)331 Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
332 auto &SM = Inputs.AST->getSourceManager();
333
334 std::string QualifierToRemoveStr = getNNSLAsString(
335 QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
336 tooling::Replacements R;
337 if (auto Err = R.add(tooling::Replacement(
338 SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),
339 QualifierToRemoveStr.length(), ""))) {
340 return std::move(Err);
341 }
342
343 auto InsertionPoint =
344 findInsertionPoint(Inputs, QualifierToRemove, Name, MustInsertAfterLoc);
345 if (!InsertionPoint) {
346 return InsertionPoint.takeError();
347 }
348
349 if (InsertionPoint->Loc.isValid()) {
350 // Add the using statement at appropriate location.
351 std::string UsingText;
352 llvm::raw_string_ostream UsingTextStream(UsingText);
353 UsingTextStream << "using ";
354 if (InsertionPoint->AlwaysFullyQualify &&
355 !isFullyQualified(QualifierToRemove.getNestedNameSpecifier()))
356 UsingTextStream << "::";
357 UsingTextStream << QualifierToRemoveStr << Name << ";"
358 << InsertionPoint->Suffix;
359
360 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());
361 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
362 UsingTextStream.str()))) {
363 return std::move(Err);
364 }
365 }
366
367 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(),
368 std::move(R));
369 }
370
371 } // namespace
372 } // namespace clangd
373 } // namespace clang
374