ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Dart Analysis Server 重构(Refactor)设计指南:三种类型、语义保持准则与代码生成规范

Dart Analysis Server 重构(Refactor)设计指南:三种类型、语义保持准则与代码生成规范 编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe 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.getAssists、edit.getAvailableRefactorings、edit.getFixes、edit.getRefactoring四个请求提供。在 LSP 实现侧可以找到这些请求对应的处理链例如 pkg/analysis_server/lib/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart 中即围绕textDocument/codeAction请求组织 code action 的产出与优先级。虽然用户倾向于把所有重构都看成同一件事但服务器内部将其划分为三类。这种划分部分源于实现层面的考量更重要的是因为三类操作在**用户体验UX**上有着不同的含义与影响。划分依据两个特征维度三类重构的分类基于重构的两个特征编辑创建时间edit creation timeEager 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几乎不存在如果有的话合理的理由让重构产出无法编译的代码。但存在两个已知例外作用于已损坏代码的重构某些重构会作用在本来就无法编译的代码上此时结果仍然损坏是合理的——只要没有坏得更厉害。但通常不应由重构引入新的诊断到代码中。客户端明确告知且用户确认继续如果客户端允许服务器向用户通报当前状况并且用户明确表示希望继续那么继续执行重构是有意义的。生成代码Generated Code大多数重构在运行时会生成代码。本节描述生成代码时应遵循的工程实践而遵循这些实践的最佳方式之一就是使用DartFileEditBuilder和DartEditBuilder类中定义的工具方法来生成代码。这两个类位于 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的判定逻辑提供addLinkedEdit、canWriteType、createLinkedEditBuilder等能力支持生成带关联编辑linked edit的代码片段。这从源码层面印证了文档生成的代码应尽量遵循已启用 lint 所强制执行的风格这一要求。风格Style生成的代码应尽可能接近用户可能亲手写出的代码。例如如果认为用户更可能写带块体的方法而不是表达式体方法那么生成方法时应产出块体同样如果认为大多数用户在返回Future的方法上会使用async生成时应包含该修饰符。生成的代码应尽可能遵循已启用 lint 所强制执行的风格。例如若启用了强制统一字符串字面量定界符的 lint则生成的所有字符串字面量都应使用该定界符。生成的代码应在格式上接近 formatter 的输出。服务器会合理尝试生成带恰当缩进和 token 间空格的代码但不需要包含自动换行、刻意添加或省略的逗号等。一旦未来获得对文件局部区域运行 formatter 的能力就应该对生成代码使用 formatter。不完整代码Incomplete Code有时生成不完整代码是必要的。例如根据一个示例调用生成方法时服务器可以推断出参数的数量与类型、甚至返回类型但无法知道方法体该如何实现。生成不完整代码时应尽量让代码不完整这一事实显而易见有两种做法生成带编译错误的代码在生成代码中附带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都能快速定位其定位与应遵守的规则。赞分享编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载相关推荐PhoneInfoga 实战指南scan 命令行扫描与 Web 服务部署PhoneInfoga 实战指南scan 命令行扫描与 Web 服务部署 PhoneInfoga 是一个面向电话号码的信息收集OSINT框架其核心使用方编程语言编译器语言运行时标准库开发工具OBS Studio 代码风格规范深度解读clang-format 强制规则、各语言守则与架构设计准则OBS Studio 代码风格规范深度解读clang format 强制规则、各语言守则与架构设计准则 OBS Studio 的 CODESTYLE.md h音视频直播屏幕录制桌面应用视频Dart Analysis Server 的 Sort Members 命令成员排序规则与源码实现深度解析Dart Analysis Server 的 Sort Members 命令成员排序规则与源码实现深度解析 Sort Members排序成员是 Dart编程语言编译器语言运行时标准库开发工具上一篇GetQzonehistory5分钟快速导出QQ空间历史说说完整指南下一篇终极指南如何用ChoEazyCopy图形化工具简化Windows文件复制备份创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表