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
31namespace clang {
32namespace clangd {
33namespace {
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.
48class AddUsing : public Tweak {
49public:
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
59private:
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};
74REGISTER_TWEAK(AddUsing)
75
76std::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.
82class UsingFinder : public RecursiveASTVisitor<UsingFinder> {
83public:
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
112private:
113 std::vector<const UsingDecl *> &Results;
114 const DeclContext *SelectionDeclContext;
115 const SourceManager &SM;
116};
117
118bool isFullyQualified(const NestedNameSpecifier *NNS) {
119 if (!NNS)
120 return false;
121 return NNS->getKind() == NestedNameSpecifier::Global ||
122 isFullyQualified(NNS: NNS->getPrefix());
123}
124
125struct 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.
145llvm::Expected<InsertionPointData>
146findInsertionPoint(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
233bool 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
246std::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
254bool 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
361Expected<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

source code of clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp