#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 {
// All of the following are set by prepare().
// The qualifier to remove.
NestedNameSpecifierLoc QualifierToRemove;
- // The name following QualifierToRemove.
- llvm::StringRef Name;
+ // Qualified name to use when spelling the using declaration. This might be
+ // different than SpelledQualifier in presence of error correction.
+ std::string QualifierToSpell;
+ // The name and qualifier as spelled in the code.
+ llvm::StringRef SpelledQualifier;
+ llvm::StringRef SpelledName;
// If valid, the insertion point for "using" statement must come after this.
// This is relevant when the type is defined in the main file, to make sure
// the type/function is already defined at the point where "using" is added.
std::string AddUsing::title() const {
return std::string(llvm::formatv(
- "Add using-declaration for {0} and remove qualifier", Name));
+ "Add using-declaration for {0} and remove qualifier", SpelledName));
}
// Locates all "using" statements relevant to SelectionDeclContext.
if (Node == nullptr)
return false;
+ SourceRange SpelledNameRange;
if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {
if (auto *II = D->getDecl()->getIdentifier()) {
QualifierToRemove = D->getQualifierLoc();
- Name = II->getName();
+ SpelledNameRange = D->getSourceRange();
MustInsertAfterLoc = D->getDecl()->getBeginLoc();
}
} else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
if (auto E = T->getAs<ElaboratedTypeLoc>()) {
QualifierToRemove = E.getQualifierLoc();
- if (!QualifierToRemove)
- return false;
- auto NameRange = E.getSourceRange();
+ SpelledNameRange = E.getSourceRange();
if (auto T = E.getNamedTypeLoc().getAs<TemplateSpecializationTypeLoc>()) {
// Remove the template arguments from the name.
- NameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1));
+ SpelledNameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1));
}
- auto SpelledTokens = TB.spelledForExpanded(TB.expandedTokens(NameRange));
- if (!SpelledTokens)
- return false;
- auto SpelledRange = syntax::Token::range(SM, SpelledTokens->front(),
- SpelledTokens->back());
- Name = SpelledRange.text(SM);
-
- std::string QualifierToRemoveStr = getNNSLAsString(
- QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
- if (!Name.consume_front(QualifierToRemoveStr))
- return false; // What's spelled doesn't match the qualifier.
-
if (const auto *ET = E.getTypePtr()) {
if (const auto *TDT =
dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) {
}
}
}
-
- // 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() ||
+ if (!QualifierToRemove ||
+ // 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.
!QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() ||
- Name.empty()) {
- return false;
- }
-
- if (isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier()))
+ // Respect user config.
+ 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.
return false;
}
+ auto SpelledTokens =
+ TB.spelledForExpanded(TB.expandedTokens(SpelledNameRange));
+ if (!SpelledTokens)
+ return false;
+ auto SpelledRange =
+ syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back());
+ // We only drop qualifiers that're namespaces, so this is safe.
+ std::tie(SpelledQualifier, SpelledName) =
+ splitQualifiedName(SpelledRange.text(SM));
+ QualifierToSpell = getNNSLAsString(
+ QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
+ if (!llvm::StringRef(QualifierToSpell).endswith(SpelledQualifier) ||
+ SpelledName.empty())
+ return false; // What's spelled doesn't match the qualifier.
return true;
}
Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
auto &SM = Inputs.AST->getSourceManager();
- std::string QualifierToRemoveStr = getNNSLAsString(
- QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
tooling::Replacements R;
if (auto Err = R.add(tooling::Replacement(
SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),
- QualifierToRemoveStr.length(), ""))) {
+ SpelledQualifier.size(), ""))) {
return std::move(Err);
}
- auto InsertionPoint =
- findInsertionPoint(Inputs, QualifierToRemove, Name, MustInsertAfterLoc);
+ auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove,
+ SpelledName, MustInsertAfterLoc);
if (!InsertionPoint) {
return InsertionPoint.takeError();
}
if (InsertionPoint->AlwaysFullyQualify &&
!isFullyQualified(QualifierToRemove.getNestedNameSpecifier()))
UsingTextStream << "::";
- UsingTextStream << QualifierToRemoveStr << Name << ";"
+ UsingTextStream << QualifierToSpell << SpelledName << ";"
<< InsertionPoint->Suffix;
assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());