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"
48class AddUsing :
public Tweak {
50 const char *id()
const override;
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 {
62 NestedNameSpecifierLoc QualifierToRemove;
65 std::string QualifierToSpell;
67 llvm::StringRef SpelledQualifier;
68 llvm::StringRef SpelledName;
72 SourceLocation MustInsertAfterLoc;
76std::string AddUsing::title()
const {
77 return std::string(llvm::formatv(
78 "Add using-declaration for {0} and remove qualifier", SpelledName));
82class UsingFinder :
public RecursiveASTVisitor<UsingFinder> {
84 UsingFinder(std::vector<const UsingDecl *> &Results,
85 const DeclContext *SelectionDeclContext,
const SourceManager &SM)
86 : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {}
88 bool VisitUsingDecl(UsingDecl *D) {
89 auto Loc =
D->getUsingLoc();
90 if (SM.getFileID(Loc) != SM.getMainFileID()) {
93 if (
D->getDeclContext()->Encloses(SelectionDeclContext)) {
99 bool TraverseDecl(Decl *Node) {
105 if (!Node->getDeclContext() ||
106 Node->getDeclContext()->Encloses(SelectionDeclContext)) {
107 return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);
113 std::vector<const UsingDecl *> &Results;
114 const DeclContext *SelectionDeclContext;
115 const SourceManager &SM;
118struct InsertionPointData {
128 bool AlwaysFullyQualify =
false;
138llvm::Expected<InsertionPointData>
139findInsertionPoint(
const Tweak::Selection &Inputs,
140 const NestedNameSpecifierLoc &QualifierToRemove,
141 const llvm::StringRef Name,
142 const SourceLocation MustInsertAfterLoc) {
143 auto &SM = Inputs.AST->getSourceManager();
148 SourceLocation LastUsingLoc;
149 std::vector<const UsingDecl *> Usings;
150 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),
152 .TraverseAST(Inputs.AST->getASTContext());
154 auto IsValidPoint = [&](
const SourceLocation Loc) {
155 return MustInsertAfterLoc.isInvalid() ||
156 SM.isBeforeInTranslationUnit(MustInsertAfterLoc, Loc);
159 bool AlwaysFullyQualify =
true;
160 for (
auto &U : Usings) {
163 if (!U->getQualifier().isFullyQualified())
164 AlwaysFullyQualify =
false;
166 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
169 if (NestedNameSpecifier Qualifier = U->getQualifier();
170 Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) {
172 U->getQualifier().getAsNamespaceAndPrefix().Namespace;
174 QualifierToRemove.getNestedNameSpecifier()
175 .getAsNamespaceAndPrefix()
176 .Namespace->getCanonicalDecl() &&
177 U->getName() == Name) {
178 return InsertionPointData();
185 LastUsingLoc = U->getUsingLoc();
187 if (LastUsingLoc.isValid() && IsValidPoint(LastUsingLoc)) {
188 InsertionPointData Out;
189 Out.Loc = LastUsingLoc;
190 Out.AlwaysFullyQualify = AlwaysFullyQualify;
195 const DeclContext *ParentDeclCtx =
196 &Inputs.ASTSelection.commonAncestor()->getDeclContext();
197 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) {
198 ParentDeclCtx = ParentDeclCtx->getLexicalParent();
200 if (
auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) {
201 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange());
202 const auto *Tok = llvm::find_if(Toks, [](
const syntax::Token &Tok) {
203 return Tok.kind() == tok::l_brace;
205 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) {
206 return error(
"Namespace with no {{");
208 if (!Tok->endLocation().isMacroID() && IsValidPoint(Tok->endLocation())) {
209 InsertionPointData Out;
210 Out.Loc = Tok->endLocation();
217 auto TLDs = Inputs.AST->getLocalTopLevelDecls();
218 for (
const auto &TLD : TLDs) {
219 if (!IsValidPoint(TLD->getBeginLoc()))
221 InsertionPointData Out;
222 Out.Loc = SM.getExpansionLoc(TLD->getBeginLoc());
226 return error(
"Cannot find place to insert \"using\"");
229bool isNamespaceForbidden(
const Tweak::Selection &Inputs,
232 dyn_cast<NamespaceDecl>(
Namespace.getAsNamespaceAndPrefix().Namespace);
237 for (StringRef Banned :
Config::current().Style.FullyQualifiedNamespaces) {
238 StringRef PrefixMatch = NamespaceStr;
239 if (PrefixMatch.consume_front(Banned) && PrefixMatch.consume_front(
"::"))
246std::string getNNSLAsString(NestedNameSpecifierLoc NNSL,
247 const PrintingPolicy &Policy) {
249 llvm::raw_string_ostream OutStream(Out);
250 NNSL.getNestedNameSpecifier().print(OutStream, Policy);
251 return OutStream.str();
254bool AddUsing::prepare(
const Selection &Inputs) {
255 auto &SM = Inputs.AST->getSourceManager();
256 const auto &TB = Inputs.AST->getTokens();
259 if (
isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(),
260 Inputs.AST->getLangOpts()))
263 auto *Node = Inputs.ASTSelection.commonAncestor();
270 for (; Node->Parent; Node = Node->Parent) {
271 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {
274 if (
auto *T = Node->ASTNode.get<TypeLoc>()) {
276 if (Node->Parent->ASTNode.get<NestedNameSpecifierLoc>())
278 if (isa<TagType, TemplateSpecializationType, TypedefType, UsingType,
279 UnresolvedUsingType>(
T->getTypePtr()))
282 if (Node->Parent->ASTNode.get<TypeLoc>())
291 SourceRange SpelledNameRange;
292 if (
auto *D = Node->ASTNode.get<DeclRefExpr>()) {
293 if (
D->getDecl()->getIdentifier()) {
294 QualifierToRemove =
D->getQualifierLoc();
297 SpelledNameRange =
D->getSourceRange();
300 if (
auto AngleLoc =
D->getLAngleLoc(); AngleLoc.isValid())
301 SpelledNameRange.setEnd(AngleLoc.getLocWithOffset(-1));
302 MustInsertAfterLoc =
D->getDecl()->getBeginLoc();
304 }
else if (
auto *T = Node->ASTNode.get<TypeLoc>()) {
305 switch (
T->getTypeLocClass()) {
306 case TypeLoc::TemplateSpecialization: {
307 auto TL =
T->castAs<TemplateSpecializationTypeLoc>();
308 QualifierToRemove = TL.getQualifierLoc();
309 if (!QualifierToRemove)
311 SpelledNameRange = TL.getTemplateNameLoc();
312 if (
auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl(
314 MustInsertAfterLoc = TD->getBeginLoc();
318 case TypeLoc::Record:
319 case TypeLoc::InjectedClassName: {
320 auto TL =
T->castAs<TagTypeLoc>();
321 QualifierToRemove = TL.getQualifierLoc();
322 if (!QualifierToRemove)
324 SpelledNameRange = TL.getNameLoc();
325 MustInsertAfterLoc = TL.getOriginalDecl()->getBeginLoc();
328 case TypeLoc::Typedef: {
329 auto TL =
T->castAs<TypedefTypeLoc>();
330 QualifierToRemove = TL.getQualifierLoc();
331 if (!QualifierToRemove)
333 SpelledNameRange = TL.getNameLoc();
334 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
337 case TypeLoc::UnresolvedUsing: {
338 auto TL =
T->castAs<UnresolvedUsingTypeLoc>();
339 QualifierToRemove = TL.getQualifierLoc();
340 if (!QualifierToRemove)
342 SpelledNameRange = TL.getNameLoc();
343 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
346 case TypeLoc::Using: {
347 auto TL =
T->castAs<UsingTypeLoc>();
348 QualifierToRemove = TL.getQualifierLoc();
349 if (!QualifierToRemove)
351 SpelledNameRange = TL.getNameLoc();
352 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
358 if (QualifierToRemove)
359 SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc());
361 if (!QualifierToRemove ||
365 QualifierToRemove.getNestedNameSpecifier().getKind() !=
366 NestedNameSpecifier::Kind::Namespace ||
368 isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier()))
375 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||
376 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),
377 QualifierToRemove.getEndLoc())) {
382 TB.spelledForExpanded(TB.expandedTokens(SpelledNameRange));
386 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back());
388 std::tie(SpelledQualifier, SpelledName) =
390 QualifierToSpell = getNNSLAsString(
391 QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
392 if (!llvm::StringRef(QualifierToSpell).ends_with(SpelledQualifier) ||
398Expected<Tweak::Effect> AddUsing::apply(
const Selection &Inputs) {
399 auto &SM = Inputs.AST->getSourceManager();
401 tooling::Replacements R;
402 if (
auto Err = R.add(tooling::Replacement(
403 SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),
404 SpelledQualifier.size(),
""))) {
405 return std::move(Err);
408 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove,
409 SpelledName, MustInsertAfterLoc);
410 if (!InsertionPoint) {
411 return InsertionPoint.takeError();
414 if (InsertionPoint->Loc.isValid()) {
416 std::string UsingText;
417 llvm::raw_string_ostream UsingTextStream(UsingText);
418 UsingTextStream <<
"using ";
419 if (InsertionPoint->AlwaysFullyQualify &&
420 !QualifierToRemove.getNestedNameSpecifier().isFullyQualified())
421 UsingTextStream <<
"::";
422 UsingTextStream << QualifierToSpell << SpelledName <<
";"
423 << InsertionPoint->Suffix;
425 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());
426 if (
auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
427 UsingTextStream.str()))) {
428 return std::move(Err);
432 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(),
#define REGISTER_TWEAK(Subclass)
An interface base for small context-sensitive refactoring actions.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
std::pair< StringRef, StringRef > splitQualifiedName(StringRef QName)
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
std::string printNamespaceScope(const DeclContext &DC)
Returns the first enclosing namespace scope starting from DC.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const llvm::StringLiteral REFACTOR_KIND
static const Config & current()
Returns the Config of the current Context, or an empty configuration.