使用Clang作为库 —— 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成员函数。

如果你不确定应该使用哪种规则,下面的快速检查可能会有所帮助:

  1. 如果您想在一个翻译单元中转换源代码,并且不需要任何跨 TU 的信息,那么SourceChangeRefactoringRule应该适合您。
  2. 如果您希望实现具有潜在交互组件的类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简单规则将使用以下步骤进行评估:

  1. 首先调用SourceRangeSelectionRequirementevaluate成员函数。它将返回一个Expected<SourceRange>
  2. 如果返回值是一个error,则启动将失败,并将 error 报告给 client。注意,client 可能不会向用户报告 error。
  3. 否则,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应该使用从OptionalRefactoringOptionRequiredRefactoringOption派生的类来创建。下面的例子展示了一个如何创建一个所需字符串选项,对应于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>())
);
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值