Dart Analysis Server 重构(Refactor)设计指南:三种类型、语义保持准则与代码生成规范
2026/9/24 16:27:33 网站建设 项目流程
  • 编程语言
  • 编译器
  • 语言运行时
  • 标准库
  • 开发工具

【免费下载链接】sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.

项目地址:https://gitcode.com/gh_mirrors/sdk1/sdk
点击查看免费下载

本文档是 Dart Analysis Server 内部关于"重构(Refactor)"功能的设计说明(原文见 pkg/analysis_server/doc/design/features/refactors.md),它界定了 analysis_server 中所有代码修改类操作的分类方式、语义保持的取舍原则以及生成代码的工程规范。阅读本文后,你将理解编辑器里"灯泡"(quick fix / assist / refactor)背后的统一架构:Fix、Assist、Global Refactor 三类操作的差异与 UX 影响,服务端在何种情况下允许改变代码语义,以及生成代码时应遵循的风格与"不完整代码"处理策略,并能结合仓库源码定位到具体的实现位置。

从协议入口看 Refactor 的定位

Refactor 是用户可以从编辑器中选择执行、从而修改代码的动作。在 Analysis Server 中,这类能力通过两套协议暴露:

  • LSP 协议:通过textDocument/codeActions请求提供(对应 LSP 3.17 规范,原文以链接形式引用);
  • Legacy 协议:通过edit.getAssistsedit.getAvailableRefactoringsedit.getFixesedit.getRefactoring四个请求提供。

在 LSP 实现侧,可以找到这些请求对应的处理链,例如 pkg/analysis_server/lib/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart 中即围绕textDocument/codeAction请求组织 code action 的产出与优先级。

虽然用户倾向于把所有重构都看成"同一件事",但服务器内部将其划分为三类。这种划分部分源于实现层面的考量,更重要的是因为三类操作在**用户体验(UX)**上有着不同的含义与影响。

划分依据:两个特征维度

三类重构的分类基于重构的两个特征:

编辑创建时间(edit creation time)

  • Eager Refactor(即时重构):在 code action 返回之前就完成代码变更的计算。如果无法计算出代码编辑,则该 code action 根本不会返回。这样能保证:只要用户选中该操作,编辑就能成功应用。
  • Lazy Refactor(惰性重构):返回 code action 时并不计算具体的代码编辑。通常只做一组最小化的检查,确保仅在重构"有意义"时才显示该操作,但用户选中后仍可能失败。此类失败场景下,服务器会显示一条消息向用户解释失败原因。

可用性(availability)

  • 部分重构仅在存在诊断(diagnostic)指示问题时才可用
  • 另一些重构只要编辑器选区位于恰当的 token 上就始终可用(无论是否有诊断)。

三种重构类型

Fixes(修复)

Fixes 是用于解决代码中由诊断所指示问题的代码变更,始终以 eager 方式计算

Fixes 可以被触发用于:

  • 修复单个文件中单个位置上的单个诊断;
  • 修复单个文件中某个诊断报告的所有位置;
  • 修复工作区所有文件中全部诊断的所有位置(通过dart fix以及基于 LSP 的 IDE)。

在仓库中,Fix 的实现集中在 pkg/analysis_server/lib/src/services/correction/fix.dart(及其fix_internal.dart),具体到每个诊断的修复产出于 pkg/analysis_server/lib/src/services/correction/dart/ 目录下——该目录包含数百个以行为命名的修复生产者,例如remove_unused_import.dart(移除未使用导入)、add_null_check.dart(添加空检查)、convert_to_switch_expression.dart(转换为 switch 表达式)等。跨文件的批量修复由 pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart 负责调度,这正是dart fix命令在命令行侧的对应实现。

Assists(辅助操作)

Assists 是即使没有诊断也始终可用的代码变更,始终以 eager 方式计算

Assists 只能在单个文件的单个位置触发。

Assist 的统一入口位于 pkg/analysis_server/lib/src/services/correction/assist.dart(以及assist_internal.dart)。由于不依赖诊断,Assists 的可用性完全取决于编辑器选区所在的代码上下文,例如将if-else转换为条件表达式、提取局部变量等操作在无错误代码上同样可以被触发。

Global Refactors(全局重构)

Global Refactors 是可能涉及多个库、甚至跨多个 package 变更的代码重构(当这些 package 同时打开在 IDE 工作区中时)。Global Refactors始终以 lazy 方式计算

Global Refactors 只能在单个文件的单个位置触发。

全局重构的实现框架位于 pkg/analysis_server/lib/src/services/refactoring/framework/,其中:

  • refactoring_processor.dart 负责重构的整体执行流程;
  • refactoring_producer.dart 定义重构生产者;
  • refactoring_context.dart 封装触发重构时的上下文信息。

具体的全局重构(如移动顶层声明到新文件move_top_level_to_file.dart、重命名相关的add_import_prefix.dart/remove_import_prefix.dart等)则放在 pkg/analysis_server/lib/src/services/refactoring/ 目录下。

术语提醒:在很多语境下(例如 issue tracker 中),术语 "refactor" 有时泛指任意类型的重构,有时又特指 global refactor。本文档统一使用更长的名字以保持清晰。

语义保持(Preserving Semantics)

对于"重构是否应该改变代码语义",文档明确表示没有一刀切的禁令——有些重构正是因为改变了语义才有价值。甚至可以论证大多数 Fix 都在改变语义:把代码从"无法编译"变为"可编译"本身就是语义变化。本节讨论的是一组判断准则,用于决定何时应当保持语义、何时允许改变语义。

用户期望(User Expectations)

首先要问的问题:用户在多大程度上会合理地期望语义被保持?

  • 合理假设保持语义的例子:把switch 语句转换为 switch 表达式的重构,用户会期望它保持 switch 的语义。
  • 合理期望语义发生变化的例子:把方法标记为async并把返回类型改为Future的重构,用户能预期到这会改变代码语义。

微妙 vs. 明显的变化(Subtle vs. Obvious Changes)

如果某个重构将改变代码语义,那么这种改变应当对用户显而易见。语义变化越微妙,就越不适合让语义发生改变。例如:

  • 合适:把方法转换为async的 assist——虽然改变了语义,但因为返回类型被修改、新增了关键字,改变非常容易看见(对应实现见 pkg/analysis_server/lib/src/services/correction/dart/add_async.dart)。
  • 不合适:一种会改变查找作用域、使得某些标识符在没有任何提示的情况下被解析到不同目标的修改——这种变化过于微妙。

此外,作用范围也影响判断:

  • 若 Fix 只在单个位置应用,语义变化通常更明显;
  • 若 Fix 在大型代码库中批量应用,语义变化可能很容易被忽略,因为受影响的文件可能根本没有被打开(用户看不到变化)。

产出损坏代码(Producing Broken Code)

几乎不存在(如果有的话)合理的理由让重构产出无法编译的代码。但存在两个已知例外:

  1. 作用于已损坏代码的重构:某些重构会作用在本来就无法编译的代码上,此时结果仍然损坏是合理的——只要没有"坏得更厉害"。但通常不应由重构引入新的诊断到代码中。
  2. 客户端明确告知且用户确认继续:如果客户端允许服务器向用户通报当前状况,并且用户明确表示希望继续,那么继续执行重构是有意义的。

生成代码(Generated Code)

大多数重构在运行时会生成代码。本节描述生成代码时应遵循的工程实践,而遵循这些实践的最佳方式之一,就是使用DartFileEditBuilderDartEditBuilder类中定义的工具方法来生成代码。

这两个类位于 pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart(实现位于 pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart)。从实现可以看到,DartEditBuilderImpl会依据CodeStyleOptions(由启用的 lint 推导)决定生成代码的细节,例如:

  • 是否在dynamic类型处写明类型注解(对应always_specify_types等 lint,见shouldWriteDynamicNonReturnTypes/shouldWriteDynamicReturnTypes的判定逻辑);
  • 提供addLinkedEditcanWriteTypecreateLinkedEditBuilder等能力,支持生成带关联编辑(linked edit)的代码片段。

这从源码层面印证了文档"生成的代码应尽量遵循已启用 lint 所强制执行的风格"这一要求。

风格(Style)

  • 生成的代码应尽可能接近用户可能亲手写出的代码。例如:如果认为用户更可能写带块体的方法而不是表达式体方法,那么生成方法时应产出块体;同样,如果认为大多数用户在返回Future的方法上会使用async,生成时应包含该修饰符。
  • 生成的代码应尽可能遵循已启用 lint 所强制执行的风格。例如:若启用了强制统一字符串字面量定界符的 lint,则生成的所有字符串字面量都应使用该定界符。
  • 生成的代码应在格式上接近 formatter 的输出。服务器会合理尝试生成带恰当缩进和 token 间空格的代码,但不需要包含自动换行、刻意添加或省略的逗号等。一旦未来获得对文件局部区域运行 formatter 的能力,就应该对生成代码使用 formatter。

不完整代码(Incomplete Code)

有时生成不完整代码是必要的。例如:根据一个示例调用生成方法时,服务器可以推断出参数的数量与类型、甚至返回类型,但无法知道方法体该如何实现

生成不完整代码时,应尽量让"代码不完整"这一事实显而易见,有两种做法:

  1. 生成带编译错误的代码
  2. 在生成代码中附带TODO注释

例如:生成一个需要返回值的方法时,可以选择生成返回null的代码。但如果返回类型本身可空,就不会有任何信号提示该方法是不完整的——这种情况下,更好的选择是生成一个不返回值的方法(从而产生编译错误作为信号)。

一个"反例":当服务器生成对某个具体方法的 override 时,会添加对被覆盖方法的super 调用。如果原方法有返回值,则返回被覆盖方法的返回值——这样代码没有任何诊断,因此服务器会补上一个TODO注释。同时代码保持了语义等价。这看似违反了上面的建议,但严格来说这段代码并非不完整,只是通常不是用户真正想写的内容。这类"补齐 override"的逻辑可在 pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart 等生产者中查看其实现形态。

小结

Analysis Server 的重构体系可以用一张表概括:

维度FixesAssistsGlobal Refactors
是否依赖诊断依赖(由诊断触发)不依赖(选区在合适 token 上即可)不依赖(选区在合适 token 上即可)
编辑计算方式Eager(返回前算好)Eager(返回前算好)Lazy(选中后才计算,可能失败)
作用范围单位置 / 单文件全部位置 / 全工作区(dart fix单文件单位置多库、跨 package,但触发点仍是单文件单位置
语义保持允许改变(如修复编译错误),但变化需可感知默认保持,变化需明显由具体重构决定,需在 UX 上可感知

无论哪一类,其共同的工程底线是:尽量不产出比原来更坏的代码、不引入新的诊断;生成代码时贴近用户习惯、贴近 lint 与 formatter 的约束;必要的不完整代码必须给出显式信号(编译错误或 TODO 注释)。理解了这套分类与准则,无论是阅读 pkg/analysis_server/lib/src/services/correction/dart/ 下数百个修复/辅助生产者的源码,还是自己为 Analysis Server 或分析器插件编写新的 code action,都能快速定位其定位与应遵守的规则。

  • 编程语言
  • 编译器
  • 语言运行时
  • 标准库
  • 开发工具

【免费下载链接】sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.

项目地址:https://gitcode.com/gh_mirrors/sdk1/sdk
点击查看免费下载

相关推荐

上一篇:GetQzonehistory:5分钟快速导出QQ空间历史说说完整指南
下一篇:终极指南:如何用ChoEazyCopy图形化工具简化Windows文件复制备份

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询