Clang 的重构引擎¶
本文档描述了 Clang 重构引擎的设计,并提供了一些示例,展示如何使用重构 API 中的各种原语来实现不同的重构操作。 LibTooling 库提供了一些其他 API,这些 API 在开发重构操作时使用。
重构引擎可用于实现本地重构,这些重构是使用编辑器或 IDE 中的选择来启动的。您可以将 AST 匹配器 和重构引擎结合起来,以实现不太适合源代码选择或需要查询 AST 以获取某些特定节点的重构。
我们假设您了解 Clang AST 的基本知识。请参阅 Clang AST 简介,如果您想详细了解 AST 的结构。
简介¶
Clang 的重构引擎定义了一组重构操作,这些操作实现了多种不同的源代码转换。 clang-refactor
命令行工具可用于执行这些重构。某些重构在其他客户端(如文本编辑器和 IDE)中也可用。
重构操作是一个类,它定义了一个相关重构操作(规则)列表。这些规则在同一个“保护伞”下分组 - 一个单一的 clang-refactor
命令。除了规则之外,重构操作还向 clang-refactor
提供操作的命令名称和描述。每个操作都必须实现 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 {
...
}
};
重构操作规则¶
单个重构操作负责创建一组分组的重构操作规则,这些规则代表一个重构操作。虽然一个操作中的规则可能有多种不同的实现,但它们应该努力产生类似的结果。用户应该很容易识别出哪个重构操作产生了结果,无论使用了哪个重构操作规则。
操作和规则之间的区别使我们可以创建定义一组不同规则的操作,这些规则会产生类似的结果。例如,“添加缺少的 switch case”重构操作通常一次向一个 switch 添加缺少的 case。但是,拥有一种对所有对特定枚举进行操作的 switch 起作用的重构可能很有用,因为在添加新的枚举常量后,可以自动更新所有这些常量。为此,我们可以创建两个不同的规则,它们将使用一个 clang-refactor
子命令。第一个规则将描述在用户选择单个 switch 时启动的本地操作。第二个规则将描述在用户向 clang-refactor 提供枚举名称(或用户选择枚举声明)时启动的全局操作。clang-refactor 工具将分析选择和其他传递给重构操作的选项,并为给定的选择和其他选项选择最合适的规则。
规则类型¶
Clang 的重构引擎支持多种不同的重构规则
SourceChangeRefactoringRule
生成将应用于源文件的源代码替换。选择实现此规则的子类必须实现createSourceReplacements
成员函数。这种类型的规则通常用于实现仅转换一个翻译单元中的源代码的本地重构。FindSymbolOccurrencesRefactoringRule
生成“部分”重构结果:一组引用特定符号的出现。这种类型的规则通常用于实现一个交互式重命名操作,允许用户指定在重构期间应该重命名的出现。选择实现此规则的子类必须实现findSymbolOccurrences
成员函数。
以下一组快速检查可能有助于您确定应该使用哪种类型的规则
如果您想转换一个翻译单元中的源代码,并且不需要任何跨翻译单元的信息,那么
SourceChangeRefactoringRule
应该适合您。如果您想实现类似重命名的操作,并且具有潜在的交互组件,那么
FindSymbolOccurrencesRefactoringRule
可能适合您。
如何创建规则¶
确定适合您需求的规则类型后,您可以通过子类化规则并实现其接口来实现重构。子类应该有一个构造函数,它接受执行重构所需的输入。例如,如果您想实现一个简单地删除选择的规则,那么您应该创建一个 SourceChangeRefactoringRule
的子类,并使用一个接受选择范围的构造函数
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
函数将规则的子类添加到特定操作的重构操作规则列表中。例如,上面显示的类可以使用以下代码添加到操作规则列表中
RefactoringActionRules Rules;
Rules.push_back(
createRefactoringActionRule<DeleteSelectedRange>(
SourceRangeSelectionRequirement())
);
createRefactoringActionRule
函数接受一个重构操作规则需求值列表。这些值描述了重构引擎在构建和调用提供的操作规则之前必须满足的启动需求。下一节将描述如何评估这些需求,并列出可用于构建重构操作规则的所有可能需求。
重构操作规则需求¶
重构操作规则需求是一个值,其类型派生自 RefactoringActionRuleRequirement
类。该类型必须定义一个 evaluate
成员函数,该函数返回类型为 Expected<...>
的值。当需求值用作 createRefactoringActionRule
的参数时,该值将在启动操作规则时进行评估。然后,将评估结果传递给规则的构造函数,除非评估产生了错误。例如,上一节中定义的 DeleteSelectedRange
示例规则将使用以下步骤进行评估
SourceRangeSelectionRequirement
的evaluate
成员函数将首先被调用。它将返回一个Expected<SourceRange>
。如果返回值是一个错误,则启动将失败,并且错误将报告给客户端。请注意,客户端可能不会将错误报告给用户。
否则,将使用返回的源代码范围来构造
DeleteSelectedRange
规则。然后,将调用该规则,因为启动成功(所有需求都已成功评估)。
相同的步骤系列适用于任何重构规则。首先,引擎将评估所有需求。然后,它将检查这些需求是否满足(它们不应该产生错误)。然后,它将构建该规则并调用它。
需求、它们的评估和重构操作规则的调用之间的分离允许重构客户端
禁用需求不受支持的重构操作规则。
收集选项集,并定义一个命令行/可视化界面,允许用户输入这些选项,而无需调用操作。
选择需求¶
下面列出了需要某种形式的源代码选择的重构规则需求
SourceRangeSelectionRequirement
在操作使用某种形式的选择调用时评估为源代码范围。当在编辑器中启动重构时,应该满足此需求,即使用户没有选择任何内容(在这种情况下,该范围将包含光标的位置)。
其他需求¶
创建重构规则时,可以使用其他几种需求类型
RefactoringOptionsRequirement
需求是一个抽象类,应该由使用选项的需求子类化。更具体的OptionRequirement
需求是上述类的简单实现,它在评估时返回指定选项的值。下一节将详细介绍重构选项以及如何在创建规则时使用它们。
重构选项¶
重构选项是影响重构操作的值,它们是使用命令行选项或其他特定于客户端的机制指定的。应该使用派生自 OptionalRequiredOption
或 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";
}
};
然后,可以使用上面示例中显示的选项使用类似 OptionRequirement
的需求为重构规则创建需求
createRefactoringActionRule<RenameOccurrences>(
...,
OptionRequirement<NewNameOption>())
);