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