Index: include/clang/Tooling/Refactoring/Extract/ExtractFunction.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/Extract/ExtractFunction.h @@ -0,0 +1,51 @@ +//===--- ExtractFunction.h - Clang refactoring library --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_EXTRACT_EXTRACT_FUNCTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_EXTRACT_EXTRACT_FUNCTION_H + +#include "clang/Tooling/Refactoring/ASTSelection.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" + +namespace clang { +namespace tooling { + +/// An "Extract Function" refactoring moves code into a new function that's +/// then called from the place where the original code was. +class ExtractFunction final : public SourceChangeRefactoringRule { +public: + /// Initiates the extract function refactoring operation. + /// + /// \param Code The selected set of statements. + /// \param DeclName The name name of the extract function. If None, + /// "extracted" is used. + static Expected initiate(RefactoringRuleContext &Context, + CodeRangeASTSelection Code, + Optional DeclName); + + Expected + createSourceReplacements(RefactoringRuleContext &Context) override; + +private: + ExtractFunction(CodeRangeASTSelection Code, Optional DeclName) + : Code(std::move(Code)), + DeclName(DeclName ? std::move(*DeclName) : "extracted") {} + + CodeRangeASTSelection Code; + + // FIXME: Account for naming collisions: + // - error when name is specified by user. + // - rename to "extractedN" when name is implicit. + std::string DeclName; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_EXTRACT_EXTRACT_FUNCTION_H Index: include/clang/Tooling/Refactoring/RefactoringActionRegistry.def =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRegistry.def +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef REFACTORING_ACTION -#define REFACTORING_ACTION(Name) -#endif - -REFACTORING_ACTION(LocalRename) -REFACTORING_ACTION(Extract) - -#undef REFACTORING_ACTION Index: include/clang/Tooling/Refactoring/RefactoringActionRule.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRule.h +++ include/clang/Tooling/Refactoring/RefactoringActionRule.h @@ -11,6 +11,8 @@ #define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_H #include "clang/Basic/LLVM.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" #include namespace clang { @@ -20,6 +22,26 @@ class RefactoringResultConsumer; class RefactoringRuleContext; +/// Describes a refactoring rule. +class RefactoringDescriptorInterface { +public: + virtual ~RefactoringDescriptorInterface() {} + + /// Returns the name of the refactoring. + virtual StringRef getName() const = 0; + + /// Returns the human readable description of what the refactoring does. + virtual StringRef getDescription() const = 0; + + /// Returns the title that should be used in an editor for this refactoring. + /// + /// When the refactoring has no title, it won't be available in an editor + /// unless the editor client explicitly supports the refactoring through + /// a specialized interface (like a specialized protocol entry in LSP in + /// clangd). + virtual Optional getEditorTitle() const { return None; } +}; + /// A common refactoring action rule interface that defines the 'invoke' /// function that performs the refactoring operation (either fully or /// partially). @@ -52,6 +74,9 @@ /// requirements that use options, the options from the first requirement /// are visited before the options in the second requirement. virtual void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) = 0; + + /// Returns the descriptor for this refactoring rule. + virtual const RefactoringDescriptorInterface &getDescriptor() = 0; }; } // end namespace tooling Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRules.h +++ include/clang/Tooling/Refactoring/RefactoringActionRules.h @@ -36,10 +36,20 @@ /// - Gather the set of options and define a command-line / visual interface /// that allows users to input these options without ever invoking the /// action. -template +template std::unique_ptr createRefactoringActionRule(const RequirementTypes &... Requirements); +/// Creates a refactoring action rule that's accessible in an editor by +/// wrapping around the given rule. +/// +/// \param Name the name of the editor command. +/// \param Title the title (display string) of the editor command. +std::unique_ptr +createEditorBinding(std::unique_ptr Rule, StringRef Name, + StringRef Title); + /// A set of refactoring action rules that should have unique initiation /// requirements. using RefactoringActionRules = Index: include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h +++ include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h @@ -57,7 +57,11 @@ return Consumer.handleError(std::move(Err)); // Construct the target action rule by extracting the evaluated // requirements from Expected<> wrappers and then run it. - RuleType(std::move((*std::get(Values)))...).invoke(Consumer, Context); + auto Rule = + RuleType::initiate(Context, std::move((*std::get(Values)))...); + if (!Rule) + return Consumer.handleError(Rule.takeError()); + Rule->invoke(Consumer, Context); } inline void visitRefactoringOptionsImpl(RefactoringOptionVisitor &) {} @@ -110,7 +114,8 @@ } // end namespace internal -template +template std::unique_ptr createRefactoringActionRule(const RequirementTypes &... Requirements) { static_assert(std::is_base_of::value, @@ -142,8 +147,15 @@ llvm::index_sequence_for()); } + const RefactoringDescriptorInterface &getDescriptor() override { + if (!Descriptor) + Descriptor = llvm::make_unique(); + return *Descriptor; + } + private: std::tuple Requirements; + std::unique_ptr Descriptor; }; return llvm::make_unique(std::make_tuple(Requirements...)); Index: include/clang/Tooling/Refactoring/Rename/RenamingAction.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/RenamingAction.h +++ include/clang/Tooling/Refactoring/Rename/RenamingAction.h @@ -17,6 +17,7 @@ #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" #include "clang/Tooling/Refactoring/RefactoringOptions.h" #include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h" #include "llvm/Support/Error.h" @@ -46,12 +47,21 @@ bool PrintLocations; }; -class NewNameOption : public RequiredRefactoringOption { +class RenameOccurrences final : public SourceChangeRefactoringRule { public: - StringRef getName() const override { return "new-name"; } - StringRef getDescription() const override { - return "The new name to change the symbol to"; - } + static Expected initiate(RefactoringRuleContext &Context, + SourceRange SelectionRange, + std::string NewName); + + Expected + createSourceReplacements(RefactoringRuleContext &Context) override; + +private: + RenameOccurrences(const NamedDecl *ND, std::string NewName) + : ND(ND), NewName(std::move(NewName)) {} + + const NamedDecl *ND; + std::string NewName; }; /// Returns source replacements that correspond to the rename of the given Index: include/clang/module.modulemap =================================================================== --- include/clang/module.modulemap +++ include/clang/module.modulemap @@ -146,8 +146,6 @@ // importing the AST matchers library gives a link dependency on the AST // matchers (and thus the AST), which clang-format should not have. exclude header "Tooling/RefactoringCallbacks.h" - - textual header "Tooling/Refactoring/RefactoringActionRegistry.def" } module Clang_ToolingCore { Index: lib/Tooling/Refactoring/Extract.cpp =================================================================== --- lib/Tooling/Refactoring/Extract.cpp +++ lib/Tooling/Refactoring/Extract.cpp @@ -16,9 +16,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/Refactoring/RefactoringAction.h" -#include "clang/Tooling/Refactoring/RefactoringActionRules.h" -#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Extract/ExtractFunction.h" namespace clang { namespace tooling { @@ -44,58 +42,34 @@ } } -class ExtractableCodeSelectionRequirement final - : public CodeRangeASTSelectionRequirement { -public: - Expected - evaluate(RefactoringRuleContext &Context) const { - Expected Selection = - CodeRangeASTSelectionRequirement::evaluate(Context); - if (!Selection) - return Selection.takeError(); - CodeRangeASTSelection &Code = *Selection; - - // We would like to extract code out of functions/methods/blocks. - // Prohibit extraction from things like global variable / field - // initializers and other top-level expressions. - if (!Code.isInFunctionLikeBodyOfCode()) - return Context.createDiagnosticError( - diag::err_refactor_code_outside_of_function); - - // Avoid extraction of simple literals and references. - if (Code.size() == 1 && isSimpleExpression(dyn_cast(Code[0]))) - return Context.createDiagnosticError( - diag::err_refactor_extract_simple_expression); - - // FIXME (Alex L): Prohibit extraction of Objective-C property setters. - return Selection; - } -}; - -class ExtractFunction final : public SourceChangeRefactoringRule { -public: - ExtractFunction(CodeRangeASTSelection Code, Optional DeclName) - : Code(std::move(Code)), - DeclName(DeclName ? std::move(*DeclName) : "extracted") {} - - Expected - createSourceReplacements(RefactoringRuleContext &Context) override; - -private: - CodeRangeASTSelection Code; - - // FIXME: Account for naming collisions: - // - error when name is specified by user. - // - rename to "extractedN" when name is implicit. - std::string DeclName; -}; - SourceLocation computeFunctionExtractionLocation(const Decl *D) { // FIXME (Alex L): Method -> function extraction should place function before // C++ record if the method is defined inside the record. return D->getLocStart(); } +} // end anonymous namespace + +Expected +ExtractFunction::initiate(RefactoringRuleContext &Context, + CodeRangeASTSelection Code, + Optional DeclName) { + // We would like to extract code out of functions/methods/blocks. + // Prohibit extraction from things like global variable / field + // initializers and other top-level expressions. + if (!Code.isInFunctionLikeBodyOfCode()) + return Context.createDiagnosticError( + diag::err_refactor_code_outside_of_function); + + // Avoid extraction of simple literals and references. + if (Code.size() == 1 && isSimpleExpression(dyn_cast(Code[0]))) + return Context.createDiagnosticError( + diag::err_refactor_extract_simple_expression); + + // FIXME (Alex L): Prohibit extraction of Objective-C property setters. + return ExtractFunction(std::move(Code), DeclName); +} + // FIXME: Support C++ method extraction. // FIXME: Support Objective-C method extraction. Expected @@ -194,39 +168,5 @@ return AtomicChanges{std::move(Change)}; } -class DeclNameOption final : public OptionalRefactoringOption { -public: - StringRef getName() const { return "name"; } - StringRef getDescription() const { - return "Name of the extracted declaration"; - } -}; - -class ExtractRefactoring final : public RefactoringAction { -public: - StringRef getCommand() const override { return "extract"; } - - StringRef getDescription() const override { - return "(WIP action; use with caution!) Extracts code into a new function " - "/ method / variable"; - } - - /// Returns a set of refactoring actions rules that are defined by this - /// action. - RefactoringActionRules createActionRules() const override { - RefactoringActionRules Rules; - Rules.push_back(createRefactoringActionRule( - ExtractableCodeSelectionRequirement(), - OptionRequirement())); - return Rules; - } -}; - -} // end anonymous namespace - -std::unique_ptr createExtractAction() { - return llvm::make_unique(); -} - } // end namespace tooling } // end namespace clang Index: lib/Tooling/Refactoring/RefactoringActions.cpp =================================================================== --- lib/Tooling/Refactoring/RefactoringActions.cpp +++ lib/Tooling/Refactoring/RefactoringActions.cpp @@ -7,21 +7,113 @@ // //===----------------------------------------------------------------------===// +#include "clang/Tooling/Refactoring/Extract/ExtractFunction.h" #include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" namespace clang { namespace tooling { -// Forward declare the individual create*Action functions. -#define REFACTORING_ACTION(Name) \ - std::unique_ptr create##Name##Action(); -#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def" +namespace { + +template class RefactoringDescriptor; + +// The generic descriptors for all of the refactorings. + +template <> +class RefactoringDescriptor + : public RefactoringDescriptorInterface { +public: + StringRef getName() const override { return "extract-function"; } + + StringRef getDescription() const override { + return "(WIP action; use with caution!) Extracts code into a new function"; + } + + Optional getEditorTitle() const override { + return StringRef("Extract Function"); + } +}; + +template <> +class RefactoringDescriptor + : public RefactoringDescriptorInterface { +public: + StringRef getName() const override { return "local-rename"; } + + StringRef getDescription() const override { + return "Finds and renames symbols in code with no indexer support"; + } +}; + +class DeclNameOption final : public OptionalRefactoringOption { +public: + StringRef getName() const { return "name"; } + StringRef getDescription() const { + return "Name of the extracted declaration"; + } +}; + +// FIXME: Remove the Actions alltogether. +class ExtractRefactoring final : public RefactoringAction { +public: + StringRef getCommand() const override { return "extract"; } + + StringRef getDescription() const override { + return "(WIP action; use with caution!) Extracts code into a new function"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back( + createRefactoringActionRule>( + CodeRangeASTSelectionRequirement(), + OptionRequirement())); + return Rules; + } +}; + +class NewNameOption : public RequiredRefactoringOption { +public: + StringRef getName() const override { return "new-name"; } + StringRef getDescription() const override { + return "The new name to change the symbol to"; + } +}; + +// FIXME: Remove the Actions alltogether. +class LocalRename final : public RefactoringAction { +public: + StringRef getCommand() const override { return "local-rename"; } + + StringRef getDescription() const override { + return "Finds and renames symbols in code with no indexer support"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back( + createRefactoringActionRule>( + SourceRangeSelectionRequirement(), + OptionRequirement())); + return Rules; + } +}; + +} // end anonymous namespace std::vector> createRefactoringActions() { std::vector> Actions; -#define REFACTORING_ACTION(Name) Actions.push_back(create##Name##Action()); -#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def" + Actions.push_back(llvm::make_unique()); + Actions.push_back(llvm::make_unique()); return Actions; } Index: lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -41,22 +41,6 @@ namespace { -class SymbolSelectionRequirement : public SourceRangeSelectionRequirement { -public: - Expected evaluate(RefactoringRuleContext &Context) const { - Expected Selection = - SourceRangeSelectionRequirement::evaluate(Context); - if (!Selection) - return Selection.takeError(); - const NamedDecl *ND = - getNamedDeclAt(Context.getASTContext(), Selection->getBegin()); - if (!ND) - return Context.createDiagnosticError( - Selection->getBegin(), diag::err_refactor_selection_no_symbol); - return getCanonicalSymbolDeclaration(ND); - } -}; - class OccurrenceFinder final : public FindSymbolOccurrencesRefactoringRule { public: OccurrenceFinder(const NamedDecl *ND) : ND(ND) {} @@ -74,50 +58,29 @@ const NamedDecl *ND; }; -class RenameOccurrences final : public SourceChangeRefactoringRule { -public: - RenameOccurrences(const NamedDecl *ND, std::string NewName) - : Finder(ND), NewName(std::move(NewName)) {} - - Expected - createSourceReplacements(RefactoringRuleContext &Context) override { - Expected Occurrences = - Finder.findSymbolOccurrences(Context); - if (!Occurrences) - return Occurrences.takeError(); - // FIXME: Verify that the new name is valid. - SymbolName Name(NewName); - return createRenameReplacements( - *Occurrences, Context.getASTContext().getSourceManager(), Name); - } - -private: - OccurrenceFinder Finder; - std::string NewName; -}; - -class LocalRename final : public RefactoringAction { -public: - StringRef getCommand() const override { return "local-rename"; } - - StringRef getDescription() const override { - return "Finds and renames symbols in code with no indexer support"; - } - - /// Returns a set of refactoring actions rules that are defined by this - /// action. - RefactoringActionRules createActionRules() const override { - RefactoringActionRules Rules; - Rules.push_back(createRefactoringActionRule( - SymbolSelectionRequirement(), OptionRequirement())); - return Rules; - } -}; - } // end anonymous namespace -std::unique_ptr createLocalRenameAction() { - return llvm::make_unique(); +Expected +RenameOccurrences::initiate(RefactoringRuleContext &Context, + SourceRange SelectionRange, std::string NewName) { + const NamedDecl *ND = + getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin()); + if (!ND) + return Context.createDiagnosticError( + SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol); + return RenameOccurrences(getCanonicalSymbolDeclaration(ND), NewName); +} + +Expected +RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) { + Expected Occurrences = + OccurrenceFinder(ND).findSymbolOccurrences(Context); + if (!Occurrences) + return Occurrences.takeError(); + // FIXME: Verify that the new name is valid. + SymbolName Name(NewName); + return createRenameReplacements( + *Occurrences, Context.getASTContext().getSourceManager(), Name); } Expected> Index: unittests/Tooling/RefactoringActionRulesTest.cpp =================================================================== --- unittests/Tooling/RefactoringActionRulesTest.cpp +++ unittests/Tooling/RefactoringActionRulesTest.cpp @@ -7,10 +7,11 @@ // //===----------------------------------------------------------------------===// +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" #include "ReplacementTest.h" #include "RewriterTestContext.h" #include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h" #include "clang/Tooling/Refactoring/Rename/SymbolName.h" #include "clang/Tooling/Tooling.h" @@ -22,6 +23,12 @@ namespace { +class TestDescriptor : public RefactoringDescriptorInterface { +public: + virtual StringRef getName() const { return "test"; } + virtual StringRef getDescription() const { return ""; } +}; + class RefactoringActionRulesTest : public ::testing::Test { protected: void SetUp() override { @@ -63,6 +70,12 @@ ReplaceAWithB(std::pair Selection) : Selection(Selection) {} + static Expected + initiate(RefactoringRuleContext &Cotnext, + std::pair Selection) { + return ReplaceAWithB(Selection); + } + Expected createSourceReplacements(RefactoringRuleContext &Context) { const SourceManager &SM = Context.getSources(); @@ -87,8 +100,8 @@ return std::make_pair(*R, 20); } }; - auto Rule = - createRefactoringActionRule(SelectionRequirement()); + auto Rule = createRefactoringActionRule( + SelectionRequirement()); // When the requirements are satisifed, the rule's function must be invoked. { @@ -141,6 +154,11 @@ TEST_F(RefactoringActionRulesTest, ReturnError) { class ErrorRule : public SourceChangeRefactoringRule { public: + static Expected initiate(RefactoringRuleContext &, + SourceRange R) { + return ErrorRule(R); + } + ErrorRule(SourceRange R) {} Expected createSourceReplacements(RefactoringRuleContext &) { return llvm::make_error( @@ -148,8 +166,8 @@ } }; - auto Rule = - createRefactoringActionRule(SourceRangeSelectionRequirement()); + auto Rule = createRefactoringActionRule( + SourceRangeSelectionRequirement()); RefactoringRuleContext RefContext(Context.Sources); SourceLocation Cursor = Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); @@ -191,6 +209,11 @@ public: FindOccurrences(SourceRange Selection) : Selection(Selection) {} + static Expected initiate(RefactoringRuleContext &, + SourceRange Selection) { + return FindOccurrences(Selection); + } + Expected findSymbolOccurrences(RefactoringRuleContext &) override { SymbolOccurrences Occurrences; @@ -201,7 +224,7 @@ } }; - auto Rule = createRefactoringActionRule( + auto Rule = createRefactoringActionRule( SourceRangeSelectionRequirement()); RefactoringRuleContext RefContext(Context.Sources); @@ -219,4 +242,27 @@ SourceRange(Cursor, Cursor.getLocWithOffset(strlen("test")))); } +TEST_F(RefactoringActionRulesTest, EditorCommandBinding) { + std::vector> Actions = + createRefactoringActions(); + for (auto &Action : Actions) { + if (Action->getCommand() == "extract") { + std::vector> Rules = + Action->createActiveActionRules(); + ASSERT_FALSE(Rules.empty()); + const RefactoringDescriptorInterface &Descriptor = + Rules[0]->getDescriptor(); + EXPECT_EQ(Descriptor.getName(), "extract-function"); + EXPECT_EQ( + Descriptor.getDescription(), + "(WIP action; use with caution!) Extracts code into a new function"); + EXPECT_EQ(Descriptor.getEditorTitle(), + Optional("Extract Function")); + return; + } + } + // Never found 'extract'? + ASSERT_TRUE(false); +} + } // end anonymous namespace