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