Clang’s refactoring engine¶
This document describes the design of Clang’s refactoring engine and providesa couple of examples that show how various primitives in the refactoring APIcan be used to implement different refactoring actions. TheLibToolinglibrary provides several other APIs that are used when developing arefactoring action.
Refactoring engine can be used to implement local refactorings that areinitiated using a selection in an editor or an IDE. You can combineAST matchers and the refactoring engine to implementrefactorings that don’t lend themselves well to source selection and/or have toquery ASTs for some particular nodes.
We assume basic knowledge about the Clang AST. See theIntroductionto the Clang AST if you want to learn moreabout how the AST is structured.
Introduction¶
Clang’s refactoring engine defines a set refactoring actions that implementa number of different source transformations. Theclang-refactorcommand-line tool can be used to perform these refactorings. Certainrefactorings are also available in other clients like text editors and IDEs.
A refactoring action is a class that defines a list of related refactoringoperations (rules). These rules are grouped under a common umbrella - a singleclang-refactor command. In addition to rules, the refactoring actionprovides the action’s command name and description toclang-refactor.Each action must implement theRefactoringAction interface. Here’s anoutline of alocal-rename action:
classLocalRenamefinal:publicRefactoringAction{public:StringRefgetCommand()constoverride{return"local-rename";}StringRefgetDescription()constoverride{return"Finds and renames symbols in code with no indexer support";}RefactoringActionRulescreateActionRules()constoverride{...}};
Refactoring Action Rules¶
An individual refactoring action is responsible for creating the set ofgrouped refactoring action rules that represent one refactoring operation.Although the rules in one action may have a number of different implementations,they should strive to produce a similar result. It should be easy for users toidentify which refactoring action produced the result regardless of whichrefactoring action rule was used.
The distinction between actions and rules enables the creation of actionsthat define a set of different rules that produce similar results. For example,the “add missing switch cases” refactoring operation typically adds missingcases to one switch at a time. However, it could be useful to have arefactoring that works on all switches that operate on a particular enum, asone could then automatically update all of them after adding a new enumconstant. To achieve that, we can create two different rules that will use oneclang-refactor subcommand. The first rule will describe a local operationthat’s initiated when the user selects a single switch. The second rule willdescribe a global operation that works across translation units and is initiatedwhen the user provides the name of the enum to clang-refactor (or the user couldselect the enum declaration instead). The clang-refactor tool will then analyzethe selection and other options passed to the refactoring action, and will pickthe most appropriate rule for the given selection and other options.
Rule Types¶
Clang’s refactoring engine supports several different refactoring rules:
SourceChangeRefactoringRuleproduces source replacements that are appliedto the source files. Subclasses that choose to implement this rule have toimplement thecreateSourceReplacementsmember function. This type ofrule is typically used to implement local refactorings that transform thesource in one translation unit only.FindSymbolOccurrencesRefactoringRuleproduces a “partial” refactoringresult: a set of occurrences that refer to a particular symbol. This typeof rule is typically used to implement an interactive renaming action thatallows users to specify which occurrences should be renamed during therefactoring. Subclasses that choose to implement this rule have to implementthefindSymbolOccurrencesmember function.
The following set of quick checks might help if you are unsure about the typeof rule you should use:
If you would like to transform the source in one translation unit and ifyou don’t need any cross-TU information, then the
SourceChangeRefactoringRuleshould work for you.If you would like to implement a rename-like operation with potentialinteractive components, then
FindSymbolOccurrencesRefactoringRulemightwork for you.
How to Create a Rule¶
Once you determine which type of rule is suitable for your needs you canimplement the refactoring by subclassing the rule and implementing itsinterface. The subclass should have a constructor that takes the inputs thatare needed to perform the refactoring. For example, if you want to implement arule that simply deletes a selection, you should create a subclass ofSourceChangeRefactoringRule with a constructor that accepts the selectionrange:
classDeleteSelectedRangefinal:publicSourceChangeRefactoringRule{public:DeleteSelection(SourceRangeSelection):Selection(Selection){}Expected<AtomicChanges>createSourceReplacements(RefactoringRuleContext&Context)override{AtomicChangeReplacement(Context.getSources(),Selection.getBegin());Replacement.replace(Context.getSource,CharSourceRange::getCharRange(Selection),"");return{Replacement};}private:SourceRangeSelection;};
The rule’s subclass can then be added to the list of refactoring action’srules for a particular action using thecreateRefactoringActionRulefunction. For example, the class that’s shown above can be added to thelist of action rules using the following code:
RefactoringActionRulesRules;Rules.push_back(createRefactoringActionRule<DeleteSelectedRange>(SourceRangeSelectionRequirement()));
ThecreateRefactoringActionRule function takes in a list of refactoringaction rule requirement values. These values describe the initiationrequirements that have to be satisfied by the refactoring engine before theprovided action rule can be constructed and invoked. The next sectiondescribes how these requirements are evaluated and lists all the possiblerequirements that can be used to construct a refactoring action rule.
Refactoring Action Rule Requirements¶
A refactoring action rule requirement is a value whose type derives from theRefactoringActionRuleRequirement class. The type must define anevaluate member function that returns a value of typeExpected<...>.When a requirement value is used as an argument tocreateRefactoringActionRule, that value is evaluated during the initiationof the action rule. The evaluated result is then passed to the rule’sconstructor unless the evaluation produced an error. For example, theDeleteSelectedRange sample rule that’s defined in the previous sectionwill be evaluated using the following steps:
SourceRangeSelectionRequirement’sevaluatemember function will becalled first. It will return anExpected<SourceRange>.If the return value is an error the initiation will fail and the errorwill be reported to the client. Note that the client may not report theerror to the user.
Otherwise the source range return value will be used to construct the
DeleteSelectedRangerule. The rule will then be invoked as the initiationsucceeded (all requirements were evaluated successfully).
The same series of steps applies to any refactoring rule. Firstly, the enginewill evaluate all of the requirements. Then it will check if these requirementsare satisfied (they should not produce an error). Then it will construct therule and invoke it.
The separation of requirements, their evaluation and the invocation of therefactoring action rule allows the refactoring clients to:
Disable refactoring action rules whose requirements are not supported.
Gather the set of options and define a command-line / visual interfacethat allows users to input these options without ever invoking theaction.
Selection Requirements¶
The refactoring rule requirements that require some form of source selectionare listed below:
SourceRangeSelectionRequirementevaluates to a source range when theaction is invoked with some sort of selection. This requirement should besatisfied when a refactoring is initiated in an editor, even when the userhas not selected anything (the range will contain the cursor’s location inthat case).
Other Requirements¶
There are several other requirements types that can be used when creatinga refactoring rule:
The
RefactoringOptionsRequirementrequirement is an abstract class thatshould be subclassed by requirements working with options. The moreconcreteOptionRequirementrequirement is a simple implementation of theaforementioned class that returns the value of the specified option whenit’s evaluated. The next section talks more about refactoring options andhow they can be used when creating a rule.
Refactoring Options¶
Refactoring options are values that affect a refactoring operation and arespecified either using command-line options or another client-specificmechanism. Options should be created using a class that derives either fromtheOptionalRequiredOption orRequiredRefactoringOption. The followingexample shows how one can created a required string option that corresponds tothe-new-name command-line option in clang-refactor:
classNewNameOption:publicRequiredRefactoringOption<std::string>{public:StringRefgetName()constoverride{return"new-name";}StringRefgetDescription()constoverride{return"The new name to change the symbol to";}};
The option that’s shown in the example above can then be used to createa requirement for a refactoring rule using a requirement likeOptionRequirement:
createRefactoringActionRule<RenameOccurrences>(...,OptionRequirement<NewNameOption>()));