[clang-refactor] Introduce a new rename rule for qualified symbols

Summary: Prototype of a new rename rule for renaming qualified symbol.

Reviewers: arphaman, ioeric, sammccall

Reviewed By: arphaman, sammccall

Subscribers: jklaehn, cfe-commits, klimek

Differential Revision: https://reviews.llvm.org/D39332

llvm-svn: 317672
This commit is contained in:
Haojian Wu 2017-11-08 08:56:56 +00:00
parent f6ee94c1c6
commit 200458f342
5 changed files with 157 additions and 40 deletions

View File

@ -66,6 +66,28 @@ private:
std::string NewName;
};
class QualifiedRenameRule final : public SourceChangeRefactoringRule {
public:
static Expected<QualifiedRenameRule> initiate(RefactoringRuleContext &Context,
std::string OldQualifiedName,
std::string NewQualifiedName);
static const RefactoringDescriptor &describe();
private:
QualifiedRenameRule(const NamedDecl *ND,
std::string NewQualifiedName)
: ND(ND), NewQualifiedName(std::move(NewQualifiedName)) {}
Expected<AtomicChanges>
createSourceReplacements(RefactoringRuleContext &Context) override;
// A NamedDecl which indentifies the the symbol being renamed.
const NamedDecl *ND;
// The new qualified name to change the symbol to.
std::string NewQualifiedName;
};
/// Returns source replacements that correspond to the rename of the given
/// symbol occurrences.
llvm::Expected<std::vector<AtomicChange>>

View File

@ -46,6 +46,22 @@ public:
}
};
class OldQualifiedNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "old-qualified-name"; }
StringRef getDescription() const override {
return "The old qualified name to be renamed";
}
};
class NewQualifiedNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "new-qualified-name"; }
StringRef getDescription() const override {
return "The new qualified name to change the symbol to";
}
};
class NewNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "new-name"; }
@ -70,6 +86,10 @@ public:
RefactoringActionRules Rules;
Rules.push_back(createRefactoringActionRule<RenameOccurrences>(
SourceRangeSelectionRequirement(), OptionRequirement<NewNameOption>()));
// FIXME: Use NewNameOption.
Rules.push_back(createRefactoringActionRule<QualifiedRenameRule>(
OptionRequirement<OldQualifiedNameOption>(),
OptionRequirement<NewQualifiedNameOption>()));
return Rules;
}
};

View File

@ -31,6 +31,8 @@
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include <string>
#include <vector>
@ -93,6 +95,60 @@ RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) {
*Occurrences, Context.getASTContext().getSourceManager(), Name);
}
Expected<QualifiedRenameRule>
QualifiedRenameRule::initiate(RefactoringRuleContext &Context,
std::string OldQualifiedName,
std::string NewQualifiedName) {
const NamedDecl *ND =
getNamedDeclFor(Context.getASTContext(), OldQualifiedName);
if (!ND)
return llvm::make_error<llvm::StringError>("Could not find symbol " +
OldQualifiedName,
llvm::errc::invalid_argument);
return QualifiedRenameRule(ND, std::move(NewQualifiedName));
}
const RefactoringDescriptor &QualifiedRenameRule::describe() {
static const RefactoringDescriptor Descriptor = {
/*Name=*/"local-qualified-rename",
/*Title=*/"Qualified Rename",
/*Description=*/
R"(Finds and renames qualified symbols in code within a translation unit.
It is used to move/rename a symbol to a new namespace/name:
* Supported symbols: classes, class members, functions, enums, and type alias.
* Renames all symbol occurrences from the old qualified name to the new
qualified name. All symbol references will be correctly qualified; For
symbol definitions, only name will be changed.
For example, rename "A::Foo" to "B::Bar":
Old code:
namespace foo {
class A {};
}
namespace bar {
void f(foo::A a) {}
}
New code after rename:
namespace foo {
class B {};
}
namespace bar {
void f(B b) {}
})"
};
return Descriptor;
}
Expected<AtomicChanges>
QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) {
auto USRs = getUSRsForDeclaration(ND, Context.getASTContext());
assert(!USRs.empty());
return tooling::createRenameAtomicChanges(
USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl());
}
Expected<std::vector<AtomicChange>>
createRenameReplacements(const SymbolOccurrences &Occurrences,
const SourceManager &SM, const SymbolName &NewName) {

View File

@ -0,0 +1,24 @@
// RUN: clang-refactor local-rename -old-qualified-name="foo::A" -new-qualified-name="bar::B" %s -- -std=c++11 2>&1 | grep -v CHECK | FileCheck %s
namespace foo {
class A {};
}
// CHECK: namespace foo {
// CHECK-NEXT: class B {};
// CHECK-NEXT: }
namespace bar {
void f(foo::A* a) {
foo::A b;
}
// CHECK: void f(B* a) {
// CHECK-NEXT: B b;
// CHECK-NEXT: }
}
void f(foo::A* a) {
foo::A b;
}
// CHECK: void f(bar::B* a) {
// CHECK-NEXT: bar::B b;
// CHECK-NEXT: }

View File

@ -257,20 +257,19 @@ public:
RefactoringActionRules ActionRules,
cl::OptionCategory &Category)
: SubCommand(Action->getCommand(), Action->getDescription()),
Action(std::move(Action)), ActionRules(std::move(ActionRules)),
HasSelection(false) {
Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
// Check if the selection option is supported.
for (const auto &Rule : this->ActionRules) {
if ((HasSelection = Rule->hasSelectionRequirement()))
if (Rule->hasSelectionRequirement()) {
Selection = llvm::make_unique<cl::opt<std::string>>(
"selection",
cl::desc(
"The selected source range in which the refactoring should "
"be initiated (<file>:<line>:<column>-<line>:<column> or "
"<file>:<line>:<column>)"),
cl::cat(Category), cl::sub(*this));
break;
}
if (HasSelection) {
Selection = llvm::make_unique<cl::opt<std::string>>(
"selection",
cl::desc("The selected source range in which the refactoring should "
"be initiated (<file>:<line>:<column>-<line>:<column> or "
"<file>:<line>:<column>)"),
cl::cat(Category), cl::sub(*this));
}
}
// Create the refactoring options.
for (const auto &Rule : this->ActionRules) {
@ -284,10 +283,10 @@ public:
const RefactoringActionRules &getActionRules() const { return ActionRules; }
/// Parses the command-line arguments that are specific to this rule.
/// Parses the "-selection" command-line argument.
///
/// \returns true on error, false otherwise.
bool parseArguments() {
bool parseSelectionArgument() {
if (Selection) {
ParsedSelection = SourceSelectionArgument::fromString(*Selection);
if (!ParsedSelection)
@ -296,9 +295,6 @@ public:
return false;
}
// Whether the selection is supported by any rule in the subcommand.
bool hasSelection() const { return HasSelection; }
SourceSelectionArgument *getSelection() const {
assert(Selection && "selection not supported!");
return ParsedSelection.get();
@ -314,8 +310,6 @@ private:
std::unique_ptr<cl::opt<std::string>> Selection;
std::unique_ptr<SourceSelectionArgument> ParsedSelection;
RefactoringActionCommandLineOptions Options;
// Whether the selection is supported by any rule in the subcommand.
bool HasSelection;
};
class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface {
@ -403,13 +397,19 @@ public:
// If the selection option is test specific, we use a test-specific
// consumer.
std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;
if (SelectedSubcommand->hasSelection())
bool HasSelection = MatchingRule->hasSelectionRequirement();
if (HasSelection)
TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer();
ClangRefactorToolConsumerInterface *ActiveConsumer =
TestConsumer ? TestConsumer.get() : Consumer.get();
ActiveConsumer->beginTU(AST);
// FIXME (Alex L): Implement non-selection based invocation path.
if (SelectedSubcommand->hasSelection()) {
auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
if (opts::Verbose)
logInvocation(*SelectedSubcommand, Context);
MatchingRule->invoke(*ActiveConsumer, Context);
};
if (HasSelection) {
assert(SelectedSubcommand->getSelection() &&
"Missing selection argument?");
if (opts::Verbose)
@ -417,14 +417,13 @@ public:
if (SelectedSubcommand->getSelection()->forAllRanges(
Context.getSources(), [&](SourceRange R) {
Context.setSelectionRange(R);
if (opts::Verbose)
logInvocation(*SelectedSubcommand, Context);
MatchingRule->invoke(*ActiveConsumer, Context);
InvokeRule(*ActiveConsumer);
}))
HasFailed = true;
ActiveConsumer->endTU();
return;
}
InvokeRule(*ActiveConsumer);
ActiveConsumer->endTU();
}
@ -529,23 +528,24 @@ private:
}
llvm::Expected<RefactoringActionRule *>
getMatchingRule(const RefactoringActionSubcommand &Subcommand) {
getMatchingRule(RefactoringActionSubcommand &Subcommand) {
SmallVector<RefactoringActionRule *, 4> MatchingRules;
llvm::StringSet<> MissingOptions;
for (const auto &Rule : Subcommand.getActionRules()) {
bool SelectionMatches = true;
if (Rule->hasSelectionRequirement()) {
if (!Subcommand.getSelection()) {
MissingOptions.insert("selection");
SelectionMatches = false;
}
}
CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
Rule->visitRefactoringOptions(Visitor);
if (SelectionMatches && Visitor.getMissingRequiredOptions().empty()) {
MatchingRules.push_back(Rule.get());
continue;
if (Visitor.getMissingRequiredOptions().empty()) {
if (!Rule->hasSelectionRequirement()) {
MatchingRules.push_back(Rule.get());
} else {
Subcommand.parseSelectionArgument();
if (Subcommand.getSelection()) {
MatchingRules.push_back(Rule.get());
} else {
MissingOptions.insert("selection");
}
}
}
for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
MissingOptions.insert(Opt->getName());
@ -593,11 +593,6 @@ private:
Error, llvm::inconvertibleErrorCode());
}
RefactoringActionSubcommand *Subcommand = &(**It);
if (Subcommand->parseArguments())
return llvm::make_error<llvm::StringError>(
llvm::Twine("Failed to parse arguments for subcommand ") +
Subcommand->getName(),
llvm::inconvertibleErrorCode());
return Subcommand;
}