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