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