ARTICLE DETAIL

资讯详情

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

在 Dart SDK 中实现一个新 Lint:从诊断定义到注册、测试与发布的完整指南

在 Dart SDK 中实现一个新 Lint:从诊断定义到注册、测试与发布的完整指南 编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载导读本文基于 Dart SDK 仓库pkg/analysis_server/.agents/skills/implement-a-lint/SKILL.md这份官方技能文档系统讲解在 Dart SDK 中新增一条 lint 规则的完整流程如何定义诊断代码messages.yaml、如何用AnalysisRuleAPI 编写 AST 访问逻辑、如何注册规则、记录修复状态、编写反射式测试并通过全套验证命令。读者按本文操作即可从零实现、注册并发布一条可在dart analyze中生效的官方 lint。文中所有步骤均以仓库内真实参考实现unnecessary_primary_constructor_body为范本并给出对应源码位置保证每一步都可对照、可验证。一、开工前必读参考实现与文件布局1. 官方技能文档与参考实现编写新 lint 前请先完整阅读技能文档 implement-a-lint/SKILL.md它把整个过程拆成 7 个可执行的检查清单步骤。文档指定了两个“金标准”参考文件参考实现pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart参考测试pkg/linter/test/rules/unnecessary_primary_constructor_body_test.dart这两份文件分别展示了“一条完整 lint 的最小实现”和“配套的完整测试套件”后续所有步骤都可以直接对照它们进行。2. 文件布局与命名约定实现一条新 lint 时需要新建或修改以下文件路径均以仓库根目录为基准文件路径操作说明pkg/linter/lib/src/rules/rule_name.dart新建lint 主实现包含AnalysisRule子类与 AST 访问器逻辑pkg/linter/messages.yaml修改按字母序新增诊断条目包含 problem/correction 消息与文档pkg/linter/lib/src/rules.dart修改导入并按字母序注册 lint 类pkg/linter/test/rules/rule_name_test.dart新建新 lint 的反射式测试pkg/linter/test/rules/all.dart修改导入并调用测试文件的 main 入口pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml修改记录该诊断是否应提供或已有修复其中rule_name即 lint 的名字例如参考实现中的unnecessary_primary_constructor_body。二、Step 1定义诊断代码messages.yaml1. 单诊断代码模板除非用户明确要求不同上下文使用不同诊断代码否则一条规则只应有一个诊断代码。将以下 YAML 结构按字母序插入 pkg/linter/messages.yamlruleName: type: lint parameters: none problemMessage: Diagnostic message shown to users correctionMessage: Suggestion on how to fix the warning state: experimental: Dart SDK major.minor version categories: [style] hasPublishedDocs: false documentation: |- #### Description The analyzer produces this diagnostic when brief description of when its produced #### Example The following code produces this diagnostic because reason: dart // Bad code here #### Common fixes If condition, then fix: dart // Good code here deprecatedDetails: |- brief styling guide snippet containing BAD and GOOD examples2. 字段要点与严格校验规则deprecatedDetails必须包含生成器脚本会对该属性做严格校验即使新 lint 也必须提供。其中通常放一个“BAD / GOOD”风格示例帮助开发者快速理解该写什么、不该写什么。例如参考实现中就是BADclass C() { this; }GOODclass C();文档代码块不做校验documentation中的代码片段不会被脚本验证因此不要在其中使用范围标记如[!、!]或任何%指令。hasPublishedDocs必须为false新 lint 必须先写false待文档发布到官网后才改为true。参考实现unnecessaryPrimaryConstructorBody当前为true见 messages.yaml因为它已随 Dart 3.13 发布可作为“发布后”状态的对照。categories只允许以下取值binarySize、brevity、documentationCommentMaintenance、effectiveDart、errorProne、flutter、languageFeatureUsage、memoryLeaks、nonPerformant、pub、publicInterface、style、unintentional、unusedCode、web。开发者会通过这些分类发现新 lint请谨慎选择。参考实现选择了[brevity, style]。3. 多诊断代码MultiAnalysisRule场景若用户明确要求不同上下文产生不同诊断代码则对每个代码重复上述模板并按字母序排列。此时每个条目都必须有sharedName:且取值相同为该 lint 规则的名字每个条目的ruleName形如ruleName_context只有一个诊断代码通常取字典序第一个带documentation:和deprecatedDetails:这些内容会被具有相同sharedName的代码共享。4. 运行生成器编辑完messages.yaml后运行以下命令更新生成的常量文件lint_names.g.dart、diagnostic.g.dart等dart run pkg/linter/tool/generate_lints.dart dart run pkg/analyzer/tool/messages/generate.dart从源码看generate_lints.dart 会遍历messagesRuleInfo中所有条目为每个 lint 名生成LintNames.rule_name常量全部为 snake_case并写入pkg/linter/lib/src/lint_names.g.dart。这也是实现类中name: LintNames.rule_name能直接引用的原因。5. 进入下一步前的验证必须先通过文档一致性测试再进入 Step 2dart test pkg/analyzer/test/verify_diagnostics_test.dart该测试会校验documentation中的示例与修复代码是否正确。若不过修改后再跑若期间又改了messages.yaml必须重新运行生成器再测试。三、Step 2实现 lintAnalysisRule 与 AST Visitor1. 实验特性前置检查如果 lint 依赖的语言实验特性默认未开启请确认 pkg/analyzer_testing/lib/experiments/experiments.dart 中experimentsForTests返回的实验列表已包含该特性若没有需补上否则测试环境无法启用对应语法。另外新 lint 的since属性应使用下一个 Dart SDK 版本参考实现unnecessary_primary_constructor_body使用的是3.13.0对应 unnecessary_primary_constructor_body.dart。2. 实现模板在pkg/linter/lib/src/rules/rule_name.dart中新建实现复制以下模板// Copyright (c) year, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import package:analyzer/analysis_rule/analysis_rule.dart; import package:analyzer/analysis_rule/rule_context.dart; import package:analyzer/analysis_rule/rule_visitor_registry.dart; import package:analyzer/dart/ast/ast.dart; import package:analyzer/dart/ast/visitor.dart; import ../analyzer.dart; import ../diagnostic.dart as diag; const _desc rbrief one-line description of the rule; /// comment including the criteria given in the prompt for when a diagnostic should be generated class RuleName extends AnalysisRule { new() : super( name: LintNames.rule_name, description: _desc, state: RuleState.experimental(since: .new(sdk major version, sdk minor version, 0)), ); override DiagnosticCode get diagnosticCode diag.ruleName; override void registerNodeProcessors( RuleVisitorRegistry registry, RuleContext context, ) { var visitor _Visitor(this, context); // register the AST nodes the rule needs to visit using one of the add methods } } class _Visitor(final AnalysisRule rule, final RuleContext context) extends SimpleAstVisitorvoid { // visit methods }3. 必须遵守的约束注册与 visit 一一对应每个注册的节点类型都必须有对应的visitNodeClass方法反之亦然。二者不匹配时分析器会在注册/访问阶段报错。报告诊断用reportAtToken或reportAtNode优先使用rule.reportAtToken(token)把诊断定位到单个 token 上错误提示更精准。参考实现中多余的主构造函数体被报告在this关键字上rule.reportAtToken(node.thisKeyword)。4. 多诊断代码实现如果 lint 产生多个诊断代码实现类应改为继承MultiAnalysisRule继承MultiAnalysisRule而非AnalysisRule覆写复数版diagnosticCodes返回代码列表而非单数版diagnosticCodeoverride ListDiagnosticCode get diagnosticCodes [ diag.ruleName_contextOne, diag.ruleName_contextTwo, ];报告时显式指定诊断代码rule.reportAtToken(token, diagnosticCode: diag.ruleName_contextOne);5. 常用 Analyzer API剥括号用node.unParenthesized直接取得表达式目标无需手工递归遍历ParenthesizedExpression。元素模型用node.declaredFragment?.element获取声明节点对应的Element。实验/特性开关用context.isFeatureEnabled(Feature.feature_name)按条件启用或检查特性。6. AST 勘探工具不确定某语法对应哪些 AST 节点类型时可用工具打印/转储给定文件的 AST。技能文档提到pkg/linter/tool/spelunk.dart但在当前仓库中该工具实际位于 pkg/analyzer_testing/tool/spelunk.dart其核心逻辑在 pkg/analyzer_testing/lib/src/spelunker.dart用法为把 Dart 文件路径作为参数传入即可输出该文件的 AST 结构据此确定需要注册/访问的节点类型。7. 进入下一步前的验证dart analyze必须零诊断如有问题先修复再继续dart analyze pkg/linter/lib/src/rules/rule_name.dart四、Step 3注册 lint实现完成后修改 pkg/linter/lib/src/rules.dart导入规则文件并在链式注册中按字母序添加一行..registerLintRule(RuleName())参考实现注册在 rules.dart..registerLintRule(UnnecessaryPrimaryConstructorBody())其导入语句位于 rules.dart前后按unnecessary_parenthesis/unnecessary_raw_strings的字母序夹在中间。注册顺序决定规则在 lint 集合中的排列与生效方式务必保持全文件有序。五、Step 4记录 lint 修复状态编辑 pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml按字母序新增一条表明该 lint 需要被评估以确定是否应提供修复rule_name: status: needsEvaluation该文件是 analysis_server 判断“诊断是否附带修复建议”的依据。注意参考实现目前已评估完毕状态为hasFix见 error_fix_status.yaml新 lint 的初始状态则统一填写needsEvaluation由后续实现快速修复quick fix后再更新。六、Step 5编写测试1. 反射式测试模板在pkg/linter/test/rules/rule_name_test.dart新建测试套件复制以下模板// Copyright (c) year, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import package:test_reflective_loader/test_reflective_loader.dart; import ../rule_test_support.dart; void main() { defineReflectiveSuite(() { defineReflectiveTests(RuleNameTest); }); } reflectiveTest class RuleNameTest extends LintRuleTest { override String get lintRule LintNames.rule_name; // test methods }2. 断言 API 与覆盖要求期望产生诊断时用assertDiagnosticsFromMarkup期望不产生时用assertNoDiagnostics不要使用assertDiagnostics。若实现中存在“必须满足某条件才产生诊断”的逻辑则必须为每个条件至少补一个“条件为假时不产生诊断”的测试。参考测试 unnecessary_primary_constructor_body_test.dart 就是这一要求的范例空块体this {}、空分号体this;均应报[!this!]带文档注释、带初始化列表this : assert(i 0)、带注解deprecated、带非空函数体的场景均应assertNoDiagnostics。3. 实验特性与测试语言版本若 lint 依赖语言实验特性则“实验特性未开启时”的行为测试必须在测试代码中包含language override 注释版本取 pkg/analyzer/lib/src/dart/analysis/experiments.g.dart 中experimentalReleaseVersion键给出的版本即实验引入前的版本若该值为null则使用当前 Dart SDK 版本。4. Mock SDK 注意点Linter 测试运行在轻量级 mock SDKpkg/analyzer/lib/src/test_utilities/mock_sdk.dart之上而不是真实磁盘 SDK。若测试引用了标准库 API如Iterable却因 API 不存在而失败应换用其他 API或扩展 mock SDK 补上该 API。5. 注册测试入口在 pkg/linter/test/rules/all.dart 中按字母序导入新测试文件并在main()中调用其main()。参考实现对应 all.dart 的导入与 all.dart 的调用。七、Step 6运行测试先直接运行单文件测试dart test pkg/linter/test/rules/rule_name_test.dart失败则修复实现或测试视情况而定。单测通过后运行三组回归测试dart run pkg/linter/test/all.dart dart run pkg/analyzer/test/test_all.dart dart run pkg/analysis_server/test/test_all.dart这三组通常都能直接通过但偶尔会失败——此时修复失败项并重复执行以上命令直到全部通过。八、Step 7静态分析与格式化对所有新建或修改的文件不含生成文件执行分析确保零诊断dart analyze file path若报出诊断可先尝试自动修复dart fix file path然后重新dart analyze仍有诊断就手工修复反复直到干净。最后对所有新建/修改文件生成文件除外执行格式化dart format file path九、参考实现深度剖析unnecessary_primary_constructor_body以技能文档指定的参考实现为例完整走查一条 lint 的实现内核见 pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart规则声明第 16-27 行_desc一句话描述规则UnnecessaryPrimaryConstructorBody extends AnalysisRule构造时传入name: LintNames.unnecessary_primary_constructor_body、state: .stable(since: .new(3, 13, 0))该规则已稳定发布新规则用experimental。诊断代码映射第 26-27 行覆写diagnosticCode返回diag.unnecessaryPrimaryConstructorBody来自pkg/linter/lib/src/diagnostic.g.dart由生成器根据 messages.yaml 生成。节点注册第 29-36 行在registerNodeProcessors中通过registry.addPrimaryConstructorBody(this, visitor)注册对PrimaryConstructorBody节点的访问。该方法在 rule_visitor_registry.g.dart 中有声明——该文件是RuleVisitorRegistry的生成实现为每种 AST 节点提供addNodeClass(rule, visitor)注册入口。访问逻辑第 39-53 行_Visitor extends SimpleAstVisitorvoid实现visitPrimaryConstructorBody若节点带元数据、文档注释或初始化列表直接返回不报若函数体为EmptyFunctionBody即this;或为空块BlockFunctionBody即this {}则在thistoken 处报告诊断。配套测试unnecessary_primary_constructor_body_test.dart6 个用例覆盖“报与不报”两侧其中assertDiagnosticsFromMarkup用[!this!]精确标记期望的诊断位置assertNoDiagnostics验证带注释/初始化/注解/非空体时不报。诊断消息messages.yamlproblemMessage: Unnecessary primary constructor body.、correctionMessage: Try removing the body.、categories: [brevity, style]、hasPublishedDocs: true并提供完整documentation含 Description / Example / Common fixes与 BAD/GOOD 版deprecatedDetails。十、常见问题与注意事项生成文件不要手改lint_names.g.dart、diagnostic.g.dart等均为生成产物头部明确标注 “THIS FILE IS GENERATED. DO NOT EDIT.”见 generate_lints.dart只允许通过修改messages.yaml后运行生成器更新。改了 messages.yaml 必须重跑两个生成器且重新跑verify_diagnostics_test.dart否则测试会用旧常量导致失败。deprecatedDetails与hasPublishedDocs的取值受生成器严格校验前者必须存在后者新规则固定false。注册与 visit 对称性漏注册节点或漏写 visit 方法都会导致运行期错误务必逐个核对。测试命名空间all.dart中每个测试文件都以独立别名导入并调用main()防止顶层符号冲突。spelunk 工具位置技能文档写的是pkg/linter/tool/spelunk.dart当前仓库实际路径为pkg/analyzer_testing/tool/spelunk.dart使用时以实际路径为准。按上述 7 步走完一条新 lint 即可完成“定义 → 实现 → 注册 → 记录修复状态 → 测试 → 全量回归 → 格式化”的完整闭环其行为与dart analyze、analysis_server 的诊断提示完全打通。若需发布到官网只需在文档上线后将hasPublishedDocs改为true并将error_fix_status.yaml中的状态按实际修复支持情况更新即可。赞分享编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载相关推荐Clippy 新 Lint 开发完全指南从零编写、测试并注册一个 Clippy LintClippy 新 Lint 开发完全指南从零编写、测试并注册一个 Clippy Lint 本篇指南以 Clippy 官方开发文档 book/src/devel静态分析代码质量开发工具Clippy 新 Lint 的定义与注册从命名到接入 Lint Pass 的完整指南Clippy 新 Lint 的定义与注册从命名到接入 Lint Pass 的完整指南 本文以 Clippy 开发者文档 defining_lints.md h静态分析代码质量开发工具在 DataHub GMS 中新增 GraphQL 端点从 Schema 定义到 Resolver 注册与测试的完整开发指南在 DataHub GMS 中新增 GraphQL 端点从 Schema 定义到 Resolver 注册与测试的完整开发指南 导读 本文以 DataHub 开数据目录数据治理数据血缘后端前端数据工程数据集成创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表