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