Index: include/clang/Tooling/Refactoring/Rename/RenamingAction.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/RenamingAction.h +++ include/clang/Tooling/Refactoring/Rename/RenamingAction.h @@ -66,6 +66,28 @@ std::string NewName; }; +class QualifiedRenameRule final : public SourceChangeRefactoringRule { +public: + static Expected 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 + 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> Index: lib/Tooling/Refactoring/RefactoringActions.cpp =================================================================== --- lib/Tooling/Refactoring/RefactoringActions.cpp +++ lib/Tooling/Refactoring/RefactoringActions.cpp @@ -46,6 +46,22 @@ } }; +class OldQualifiedNameOption : public RequiredRefactoringOption { +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 { +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 { public: StringRef getName() const override { return "new-name"; } @@ -70,6 +86,10 @@ RefactoringActionRules Rules; Rules.push_back(createRefactoringActionRule( SourceRangeSelectionRequirement(), OptionRequirement())); + // FIXME: Use NewNameOption. + Rules.push_back(createRefactoringActionRule( + OptionRequirement(), + OptionRequirement())); return Rules; } }; Index: lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -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 #include @@ -93,6 +95,60 @@ *Occurrences, Context.getASTContext().getSourceManager(), Name); } +Expected +QualifiedRenameRule::initiate(RefactoringRuleContext &Context, + std::string OldQualifiedName, + std::string NewQualifiedName) { + const NamedDecl *ND = + getNamedDeclFor(Context.getASTContext(), OldQualifiedName); + if (!ND) + return llvm::make_error("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 with no indexer support. +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 fter rename: + namespace foo { + class B {}; + } + + namespace bar { + void f(B b) {} + })" + }; + return Descriptor; +} + +Expected +QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) { + auto USRs = getUSRsForDeclaration(ND, Context.getASTContext()); + assert(!USRs.empty()); + return tooling::createRenameAtomicChanges( + USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl()); +} + Expected> createRenameReplacements(const SymbolOccurrences &Occurrences, const SourceManager &SM, const SymbolName &NewName) { Index: test/Refactor/LocalRename/QualifiedRename.cpp =================================================================== --- /dev/null +++ test/Refactor/LocalRename/QualifiedRename.cpp @@ -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: } Index: tools/clang-refactor/ClangRefactor.cpp =================================================================== --- tools/clang-refactor/ClangRefactor.cpp +++ tools/clang-refactor/ClangRefactor.cpp @@ -257,20 +257,19 @@ 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>( + "selection", + cl::desc( + "The selected source range in which the refactoring should " + "be initiated (::-: or " + "::)"), + cl::cat(Category), cl::sub(*this)); break; - } - if (HasSelection) { - Selection = llvm::make_unique>( - "selection", - cl::desc("The selected source range in which the refactoring should " - "be initiated (::-: or " - "::)"), - cl::cat(Category), cl::sub(*this)); + } } // Create the refactoring options. for (const auto &Rule : this->ActionRules) { @@ -284,10 +283,10 @@ 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 @@ 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 @@ std::unique_ptr> Selection; std::unique_ptr 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 @@ // If the selection option is test specific, we use a test-specific // consumer. std::unique_ptr 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 @@ 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 @@ } llvm::Expected - getMatchingRule(const RefactoringActionSubcommand &Subcommand) { + getMatchingRule(RefactoringActionSubcommand &Subcommand) { SmallVector 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 @@ Error, llvm::inconvertibleErrorCode()); } RefactoringActionSubcommand *Subcommand = &(**It); - if (Subcommand->parseArguments()) - return llvm::make_error( - llvm::Twine("Failed to parse arguments for subcommand ") + - Subcommand->getName(), - llvm::inconvertibleErrorCode()); return Subcommand; }