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