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