Clang 的重构引擎
本文为译文,点击 此处查看原文。
本文档描述了 Clang 的重构引擎(refactoring engine)
的设计,并提供了两个示例,展示了如何使用重构API中的各种原语实现不同的重构操作(refactoring actions)
。LibTooling 库提供了在开发一个重构操作时使用的其他几个APIs。
重构引擎可用于实现本地重构,它使用编辑器或 IDE 中的一个selection
来启动。您可以结合 AST matchers 和重构引擎来实现重构,这些重构不能很好地用于源文件 selection 和/或必须查询某些特定节点的 AST。
我们假设有关于 Clang AST 的基本知识,如果您想了解更多关于 AST 结构的信息,请参阅 Clang AST的介绍。
1. 介绍
Clang 的重构引擎定义了一组重构操作,这些操作实现了许多不同的源文件转换。clang-refactor
命令行工具可用于执行这些重构。某些重构在文本编辑器和 IDEs 等其他 clients 中也可用。
一个重构操作是一个类,它定义了一系列相关的重构操作(规则)。这些规则被分组在一个公共的保护伞下 —— 一个clang-refactor
命令。除了规则之外,重构操作还向clang-refactor
提供操作的命令名(command name)和描述(description)。每个操作必须实现RefactoringAction
接口。下面是一个local-rename
操作的大纲:
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 {
...
}
};
2. 重构操作规则(Refactoring Action Rules)
一个单独的重构操作负责创建一组分组重构操作规则,规则表示一次重构运算(refactoring operation)。尽管一个操作中的规则可能有许多不同的实现,但它们应该努力产生类似的结果。用户应该很容易确定哪个重构操作生成了结果,而不管使用了哪个重构操作规则。
操作(actions)和规则(rules)之间的区别允许创建操作,这些操作定义了一组产生类似结果的不同规则。例如,“添加缺少的 switch cases”
重构操作通常一次向一个switch
添加缺少的cases
。但是,对在一个特定enum
上 operate 的所有switches
进行重构可能很有用,因为可以在添加新的enum
常量之后自动更新所有switches
。为此,我们可以创建两个不同的规则,它们将使用一个clang-refactor
子命令。第一条规则将描述用户选择单个switch
时启动的一个本地operation
。第二条规则将描述一个跨翻译单元工作的全局operation
,当用户向clang-refactor
提供enum
的名称时(或者用户可以选择enum
声明)将启动该operation。然后,clang-refactor
工具将分析传递给refactoring action
的选择(selection)和其他选项(options),并为给定的选择和其他选项选择最合适的规则。
3. 规则类型
Clang的重构引擎支持几种不同的重构规则:
SourceChangeRefactoringRule
生成应用于源文件的源代码替换。选择实现此规则的子类必须实现createSourceReplacements
成员函数。这种类型的规则通常用于实现只在一个翻译单元中转换源代码的本地重构。FindSymbolOccurrencesRefactoringRule
生成一个“部分”重构结果:一组引用特定符号的事件(occurrences)。这种类型的规则通常用于实现交互式renaming action
,该 action 允许用户指定在重构期间应该重命名哪些事件。选择实现此规则的子类必须实现findSymbolOccurrences
成员函数。
如果你不确定应该使用哪种规则,下面的快速检查可能会有所帮助:
- 如果您想在一个翻译单元中转换源代码,并且不需要任何跨 TU 的信息,那么
SourceChangeRefactoringRule
应该适合您。 - 如果您希望实现具有潜在交互组件的类rename operation,那么
FindSymbolOccurrencesRefactoringRule
可能适合您。
4. 如何创建规则
一旦确定哪种类型的规则适合您的需求,就可以通过子类化此规则并实现其接口来实现重构。子类应该有一个构造函数,它接受执行重构所需的输入。例如,如果你想实现一个简单地删除selection
的规则,你应该创建一个SourceChangeRefactoringRule
的子类,它的构造函数接受selection range
:
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;
};
然后,可以使用createRefactoringActionRule
函数将规则的子类添加到refactoring action
的规则列表中,用于一个特定 action。例如,上面所示的类可以使用以下代码添加到 action 规则列表中:
RefactoringActionRules Rules;
Rules.push_back(
createRefactoringActionRule<DeleteSelectedRange>(
SourceRangeSelectionRequirement())
);
createRefactoringActionRule
函数接受 refactoring action规则需求
值的列表。这些值描述了在构造和调用所提供的 action 规则之前,重构引擎必须满足的初始化需求。下一节将描述如何评估这些需求,并列出所有可用于构建一个refactoring action
规则的可能需求。
5. 重构操作规则需求(Refactoring Action Rule Requirements)
重构操作规则需求是一个值,其类型派生自RefactoringActionRuleRequirement
类。该类型必须定义一个evaluate
成员函数,该函数返回一个类型为 Expected<…>
的值。当将一个需求值用作createRefactoringActionRule
的参数时,该值将在 action 规则启动期间被评估。然后将评估结果传递给规则的构造函数,除非评估产生错误。例如,上一节定义的DeleteSelectedRange
简单规则将使用以下步骤进行评估:
- 首先调用
SourceRangeSelectionRequirement
的evaluate
成员函数。它将返回一个Expected<SourceRange>
。 - 如果返回值是一个
error
,则启动将失败,并将 error 报告给 client。注意,client 可能不会向用户报告 error。 - 否则,
source range
返回值将用于构造DeleteSelectedRange
规则。然后,在启动成功时调用规则(所有需求都已成功评估)。
同样的步骤也适用于任何重构规则。首先,引擎将评估所有的需求。然后它将检查这些需求是否满足(它们不应该产生错误)。然后它将构造规则并调用它。
将需求、它们的评估和refactoring action
规则的调用分离开来,使得重构 clients 可以:
- 禁用不支持需求的 refactoring action 规则。
- 收集一组选项,并定义一个命令行/可视接口,允许用户在不调用 action 的情况下输入这些选项。
6. 选择需求(Selection Requirements)
需要某种形式的源代码选择(source selection)的重构规则要求如下:
- 当使用某种类型的 selection 来调用 action 时,
SourceRangeSelectionRequirement
评估结果为一个source range
。当在编辑器中启动重构时,应该满足这个需求,即使用户没有选择任何东西(在这种情况下,range 将包含cursor
的位置)。
7. 其他需求
在创建一个重构规则时,还可以使用其他几种需求类型:
RefactoringOptionsRequirement
需求是一个抽象类,应该由使用options
的需求子类化。更具体的OptionRequirement
需求是上述类的一个简单实现,它在评估时返回指定option
的值。下一节将更多地讨论重构选项(refactoring options)以及如何在创建一个规则时使用它们。
8. 重构选项(Refactoring Options)
重构选项是影响重构操作的值,可以使用命令行选项或其他特定于客户端的机制来指定。Options
应该使用从OptionalRefactoringOption
或RequiredRefactoringOption
派生的类来创建。下面的例子展示了一个如何创建一个所需字符串选项
,对应于clang-refactor
中的-new-name
命令行选项:
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";
}
};
在上面的例子中显示的option
可以用来为一个重构规则创建一个需求,使用像OptionRequirement
这样的一个需求:
createRefactoringActionRule<RenameOccurrences>(
...,
OptionRequirement<NewNameOption>())
);