254 lines
11 KiB
ReStructuredText
254 lines
11 KiB
ReStructuredText
|
==========================
|
||
|
Clang's refactoring engine
|
||
|
==========================
|
||
|
|
||
|
This document describes the design of Clang's refactoring engine and provides
|
||
|
a couple of examples that show how various primitives in the refactoring API
|
||
|
can be used to implement different refactoring actions. The :doc:`LibTooling`
|
||
|
library provides several other APIs that are used when developing a
|
||
|
refactoring action.
|
||
|
|
||
|
Refactoring engine can be used to implement local refactorings that are
|
||
|
initiated using a selection in an editor or an IDE. You can combine
|
||
|
:doc:`AST matchers<LibASTMatchers>` and the refactoring engine to implement
|
||
|
refactorings that don't lend themselves well to source selection and/or have to
|
||
|
query ASTs for some particular nodes.
|
||
|
|
||
|
We assume basic knowledge about the Clang AST. See the :doc:`Introduction
|
||
|
to the Clang AST <IntroductionToTheClangAST>` if you want to learn more
|
||
|
about how the AST is structured.
|
||
|
|
||
|
.. FIXME: create new refactoring action tutorial and link to the tutorial
|
||
|
|
||
|
Introduction
|
||
|
------------
|
||
|
|
||
|
Clang's refactoring engine defines a set refactoring actions that implement
|
||
|
a number of different source transformations. The ``clang-refactor``
|
||
|
command-line tool can be used to perform these refactorings. Certain
|
||
|
refactorings are also available in other clients like text editors and IDEs.
|
||
|
|
||
|
A refactoring action is a class that defines a list of related refactoring
|
||
|
operations (rules). These rules are grouped under a common umbrella - a single
|
||
|
``clang-refactor`` command. In addition to rules, the refactoring action
|
||
|
provides the action's command name and description to ``clang-refactor``.
|
||
|
Each action must implement the ``RefactoringAction`` interface. Here's an
|
||
|
outline of a ``local-rename`` action:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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";
|
||
|
}
|
||
|
|
||
|
RefactoringActionRules createActionRules() const override {
|
||
|
...
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Refactoring Action Rules
|
||
|
------------------------
|
||
|
|
||
|
An individual refactoring action is responsible for creating the set of
|
||
|
grouped 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 to
|
||
|
identify which refactoring action produced the result regardless of which
|
||
|
refactoring action rule was used.
|
||
|
|
||
|
The distinction between actions and rules enables the creation of actions
|
||
|
that define a set of different rules that produce similar results. For example,
|
||
|
the "add missing switch cases" refactoring operation typically adds missing
|
||
|
cases to one switch at a time. However, it could be useful to have a
|
||
|
refactoring that works on all switches that operate on a particular enum, as
|
||
|
one could then automatically update all of them after adding a new enum
|
||
|
constant. To achieve that, we can create two different rules that will use one
|
||
|
``clang-refactor`` subcommand. The first rule will describe a local operation
|
||
|
that's initiated when the user selects a single switch. The second rule will
|
||
|
describe a global operation that works across translation units and is initiated
|
||
|
when the user provides the name of the enum to clang-refactor (or the user could
|
||
|
select the enum declaration instead). The clang-refactor tool will then analyze
|
||
|
the selection and other options passed to the refactoring action, and will pick
|
||
|
the most appropriate rule for the given selection and other options.
|
||
|
|
||
|
Rule Types
|
||
|
^^^^^^^^^^
|
||
|
|
||
|
Clang's refactoring engine supports several different refactoring rules:
|
||
|
|
||
|
- ``SourceChangeRefactoringRule`` produces source replacements that are applied
|
||
|
to the source files. Subclasses that choose to implement this rule have to
|
||
|
implement the ``createSourceReplacements`` member function. This type of
|
||
|
rule is typically used to implement local refactorings that transform the
|
||
|
source in one translation unit only.
|
||
|
|
||
|
- ``FindSymbolOccurrencesRefactoringRule`` produces a "partial" refactoring
|
||
|
result: a set of occurrences that refer to a particular symbol. This type
|
||
|
of rule is typically used to implement an interactive renaming action that
|
||
|
allows users to specify which occurrences should be renamed during the
|
||
|
refactoring. Subclasses that choose to implement this rule have to implement
|
||
|
the ``findSymbolOccurrences`` member function.
|
||
|
|
||
|
The following set of quick checks might help if you are unsure about the type
|
||
|
of rule you should use:
|
||
|
|
||
|
#. If you would like to transform the source in one translation unit and if
|
||
|
you don't need any cross-TU information, then the
|
||
|
``SourceChangeRefactoringRule`` should work for you.
|
||
|
|
||
|
#. If you would like to implement a rename-like operation with potential
|
||
|
interactive components, then ``FindSymbolOccurrencesRefactoringRule`` might
|
||
|
work for you.
|
||
|
|
||
|
How to Create a Rule
|
||
|
^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
Once you determine which type of rule is suitable for your needs you can
|
||
|
implement the refactoring by subclassing the rule and implementing its
|
||
|
interface. The subclass should have a constructor that takes the inputs that
|
||
|
are needed to perform the refactoring. For example, if you want to implement a
|
||
|
rule that simply deletes a selection, you should create a subclass of
|
||
|
``SourceChangeRefactoringRule`` with a constructor that accepts the selection
|
||
|
range:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
class DeleteSelectedRange final : public SourceChangeRefactoringRule {
|
||
|
public:
|
||
|
DeleteSelection(SourceRange Selection) : Selection(Selection) {}
|
||
|
|
||
|
Expected<AtomicChanges>
|
||
|
createSourceReplacements(RefactoringRuleContext &Context) override {
|
||
|
AtomicChange Replacement(Context.getSources(), Selection.getBegin());
|
||
|
Replacement.replace(Context.getSource,
|
||
|
CharSourceRange::getCharRange(Selection), "");
|
||
|
return { Replacement };
|
||
|
}
|
||
|
private:
|
||
|
SourceRange Selection;
|
||
|
};
|
||
|
|
||
|
The rule's subclass can then be added to the list of refactoring action's
|
||
|
rules for a particular action using the ``createRefactoringActionRule``
|
||
|
function. For example, the class that's shown above can be added to the
|
||
|
list of action rules using the following code:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
RefactoringActionRules Rules;
|
||
|
Rules.push_back(
|
||
|
createRefactoringActionRule<DeleteSelectedRange>(
|
||
|
SourceRangeSelectionRequirement())
|
||
|
);
|
||
|
|
||
|
The ``createRefactoringActionRule`` function takes in a list of refactoring
|
||
|
action rule requirement values. These values describe the initiation
|
||
|
requirements that have to be satisfied by the refactoring engine before the
|
||
|
provided action rule can be constructed and invoked. The next section
|
||
|
describes how these requirements are evaluated and lists all the possible
|
||
|
requirements 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 the
|
||
|
``RefactoringActionRuleRequirement`` class. The type must define an
|
||
|
``evaluate`` member function that returns a value of type ``Expected<...>``.
|
||
|
When a requirement value is used as an argument to
|
||
|
``createRefactoringActionRule``, that value is evaluated during the initiation
|
||
|
of the action rule. The evaluated result is then passed to the rule's
|
||
|
constructor unless the evaluation produced an error. For example, the
|
||
|
``DeleteSelectedRange`` sample rule that's defined in the previous section
|
||
|
will be evaluated using the following steps:
|
||
|
|
||
|
#. ``SourceRangeSelectionRequirement``'s ``evaluate`` member function will be
|
||
|
called first. It will return an ``Expected<SourceRange>``.
|
||
|
|
||
|
#. If the return value is an error the initiation will fail and the error
|
||
|
will be reported to the client. Note that the client may not report the
|
||
|
error to the user.
|
||
|
|
||
|
#. Otherwise the source range return value will be used to construct the
|
||
|
``DeleteSelectedRange`` rule. The rule will then be invoked as the initiation
|
||
|
succeeded (all requirements were evaluated successfully).
|
||
|
|
||
|
The same series of steps applies to any refactoring rule. Firstly, the engine
|
||
|
will evaluate all of the requirements. Then it will check if these requirements
|
||
|
are satisfied (they should not produce an error). Then it will construct the
|
||
|
rule and invoke it.
|
||
|
|
||
|
The separation of requirements, their evaluation and the invocation of the
|
||
|
refactoring 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 interface
|
||
|
that allows users to input these options without ever invoking the
|
||
|
action.
|
||
|
|
||
|
Selection Requirements
|
||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
The refactoring rule requirements that require some form of source selection
|
||
|
are listed below:
|
||
|
|
||
|
- ``SourceRangeSelectionRequirement`` evaluates to a source range when the
|
||
|
action is invoked with some sort of selection. This requirement should be
|
||
|
satisfied when a refactoring is initiated in an editor, even when the user
|
||
|
has not selected anything (the range will contain the cursor's location in
|
||
|
that case).
|
||
|
|
||
|
.. FIXME: Future selection requirements
|
||
|
|
||
|
.. FIXME: Maybe mention custom selection requirements?
|
||
|
|
||
|
Other Requirements
|
||
|
^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
There are several other requirements types that can be used when creating
|
||
|
a refactoring rule:
|
||
|
|
||
|
- The ``RefactoringOptionsRequirement`` requirement is an abstract class that
|
||
|
should be subclassed by requirements working with options. The more
|
||
|
concrete ``OptionRequirement`` requirement is a simple implementation of the
|
||
|
aforementioned class that returns the value of the specified option when
|
||
|
it's evaluated. The next section talks more about refactoring options and
|
||
|
how they can be used when creating a rule.
|
||
|
|
||
|
Refactoring Options
|
||
|
-------------------
|
||
|
|
||
|
Refactoring options are values that affect a refactoring operation and are
|
||
|
specified either using command-line options or another client-specific
|
||
|
mechanism. Options should be created using a class that derives either from
|
||
|
the ``OptionalRequiredOption`` or ``RequiredRefactoringOption``. The following
|
||
|
example shows how one can created a required string option that corresponds to
|
||
|
the ``-new-name`` command-line option in clang-refactor:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
class NewNameOption : public RequiredRefactoringOption<std::string> {
|
||
|
public:
|
||
|
StringRef getName() const override { return "new-name"; }
|
||
|
StringRef getDescription() const override {
|
||
|
return "The new name to change the symbol to";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
The option that's shown in the example above can then be used to create
|
||
|
a requirement for a refactoring rule using a requirement like
|
||
|
``OptionRequirement``:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
createRefactoringActionRule<RenameOccurrences>(
|
||
|
...,
|
||
|
OptionRequirement<NewNameOption>())
|
||
|
);
|
||
|
|
||
|
.. FIXME: Editor Bindings section
|