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