#include "AST.h"
#include "Config.h"
#include "SourceCode.h"
#include "refactor/Tweak.h"
#include "support/Logger.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
#include <tuple>
#include <utility>
namespace clang {
namespace clangd {
namespace {
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:
NestedNameSpecifierLoc QualifierToRemove;
std::string QualifierToSpell;
llvm::StringRef SpelledQualifier;
llvm::StringRef SpelledName;
SourceLocation MustInsertAfterLoc;
};
REGISTER_TWEAK(AddUsing)
std::string AddUsing::title() const {
return std::string(llvm::formatv(
"Add using-declaration for {0} and remove qualifier", SpelledName));
}
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) {
if (!Node)
return true;
if (!Node->getDeclContext() ||
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 {
SourceLocation Loc;
std::string Suffix;
bool AlwaysFullyQualify = false;
};
llvm::Expected<InsertionPointData>
findInsertionPoint(const Tweak::Selection &Inputs,
const NestedNameSpecifierLoc &QualifierToRemove,
const llvm::StringRef Name,
const SourceLocation MustInsertAfterLoc) {
auto &SM = Inputs.AST->getSourceManager();
SourceLocation LastUsingLoc;
std::vector<const UsingDecl *> Usings;
UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),
SM)
.TraverseAST(Inputs.AST->getASTContext());
auto IsValidPoint = [&](const SourceLocation Loc) {
return MustInsertAfterLoc.isInvalid() ||
SM.isBeforeInTranslationUnit(MustInsertAfterLoc, Loc);
};
bool AlwaysFullyQualify = true;
for (auto &U : Usings) {
if (!isFullyQualified(U->getQualifier()))
AlwaysFullyQualify = false;
if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
break;
if (const auto *Namespace = U->getQualifier()->getAsNamespace()) {
if (Namespace->getCanonicalDecl() ==
QualifierToRemove.getNestedNameSpecifier()
->getAsNamespace()
->getCanonicalDecl() &&
U->getName() == Name) {
return InsertionPointData();
}
}
LastUsingLoc = U->getUsingLoc();
}
if (LastUsingLoc.isValid() && IsValidPoint(LastUsingLoc)) {
InsertionPointData Out;
Out.Loc = LastUsingLoc;
Out.AlwaysFullyQualify = AlwaysFullyQualify;
return Out;
}
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() && IsValidPoint(Tok->endLocation())) {
InsertionPointData Out;
Out.Loc = Tok->endLocation();
Out.Suffix = "\n";
return Out;
}
}
auto TLDs = Inputs.AST->getLocalTopLevelDecls();
for (const auto &TLD : TLDs) {
if (!IsValidPoint(TLD->getBeginLoc()))
continue;
InsertionPointData Out;
Out.Loc = SM.getExpansionLoc(TLD->getBeginLoc());
Out.Suffix = "\n\n";
return Out;
}
return error("Cannot find place to insert \"using\"");
}
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;
}
std::string getNNSLAsString(NestedNameSpecifierLoc &NNSL,
const PrintingPolicy &Policy) {
std::string Out;
llvm::raw_string_ostream OutStream(Out);
NNSL.getNestedNameSpecifier()->print(OutStream, Policy);
return OutStream.str();
}
bool AddUsing::prepare(const Selection &Inputs) {
auto &SM = Inputs.AST->getSourceManager();
const auto &TB = Inputs.AST->getTokens();
if (isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(),
Inputs.AST->getLangOpts()))
return false;
auto *Node = Inputs.ASTSelection.commonAncestor();
if (Node == nullptr)
return false;
for (; Node->Parent; Node = Node->Parent) {
if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {
continue;
}
if (auto *T = Node->ASTNode.get<TypeLoc>()) {
if (T->getAs<ElaboratedTypeLoc>()) {
break;
}
if (Node->Parent->ASTNode.get<TypeLoc>() ||
Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) {
continue;
}
}
break;
}
if (Node == nullptr)
return false;
SourceRange SpelledNameRange;
if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {
if (D->getDecl()->getIdentifier()) {
QualifierToRemove = D->getQualifierLoc();
SpelledNameRange = D->getSourceRange();
if (auto AngleLoc = D->getLAngleLoc(); AngleLoc.isValid())
SpelledNameRange.setEnd(AngleLoc.getLocWithOffset(-1));
MustInsertAfterLoc = D->getDecl()->getBeginLoc();
}
} else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
if (auto E = T->getAs<ElaboratedTypeLoc>()) {
QualifierToRemove = E.getQualifierLoc();
SpelledNameRange = E.getSourceRange();
if (auto T = E.getNamedTypeLoc().getAs<TemplateSpecializationTypeLoc>()) {
SpelledNameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1));
}
if (const auto *ET = E.getTypePtr()) {
if (const auto *TDT =
dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) {
MustInsertAfterLoc = TDT->getDecl()->getBeginLoc();
} else if (auto *TD = ET->getAsTagDecl()) {
MustInsertAfterLoc = TD->getBeginLoc();
}
}
}
}
if (!QualifierToRemove ||
!QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() ||
isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier()))
return false;
if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||
!SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),
QualifierToRemove.getEndLoc())) {
return false;
}
auto SpelledTokens =
TB.spelledForExpanded(TB.expandedTokens(SpelledNameRange));
if (!SpelledTokens)
return false;
auto SpelledRange =
syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back());
std::tie(SpelledQualifier, SpelledName) =
splitQualifiedName(SpelledRange.text(SM));
QualifierToSpell = getNNSLAsString(
QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
if (!llvm::StringRef(QualifierToSpell).ends_with(SpelledQualifier) ||
SpelledName.empty())
return false;
return true;
}
Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
auto &SM = Inputs.AST->getSourceManager();
tooling::Replacements R;
if (auto Err = R.add(tooling::Replacement(
SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),
SpelledQualifier.size(), ""))) {
return std::move(Err);
}
auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove,
SpelledName, MustInsertAfterLoc);
if (!InsertionPoint) {
return InsertionPoint.takeError();
}
if (InsertionPoint->Loc.isValid()) {
std::string UsingText;
llvm::raw_string_ostream UsingTextStream(UsingText);
UsingTextStream << "using ";
if (InsertionPoint->AlwaysFullyQualify &&
!isFullyQualified(QualifierToRemove.getNestedNameSpecifier()))
UsingTextStream << "::";
UsingTextStream << QualifierToSpell << SpelledName << ";"
<< 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));
}
}
}
}