首页
/ Flutter 仓库中 flutter_test 的 Data-Driven Fixes:`fix_data` 目录结构与 `dart fix` 规则编写与维护指南

Flutter 仓库中 flutter_test 的 Data-Driven Fixes:`fix_data` 目录结构与 `dart fix` 规则编写与维护指南

2026-09-07 22:45:05作者:彭桢灵Jeremy

在 Flutter 主仓库中,packages/flutter_test 包通过 fix_data/README.md 定义了一整套基于 YAML 的“数据驱动修复”(Data-Driven Fixes)规则,用于在 flutter_test 公开 API 演进时帮助下游用户自动化迁移测试代码。本文以该文档为主线,结合仓库内真实规则文件、模板文件与黄金测试(golden test)用例,系统讲解规则文件的组织约定、编写方法、测试手段以及对外部 CI 的影响,读完即可掌握在 Flutter 仓库中新增或维护一条 dart fix 修复规则的完整流程。

一、fix_data 目录在 flutter_test 中的作用

1. 目录到底存储了什么

fix_data 目录下的所有 .yaml 文件,定义的是 Flutter 官方文档称之为 dart fix 框架(Data-Driven Fixes)的重构规则,专供 flutter_test 使用。这些规则描述的并非“怎么改代码”,而是描述 flutter_test 公共 API 发生了什么样的变化;具体的客户端代码改写动作由 Dart 工具链(analysis server 与 dart fix 命令)根据数据自行推导完成。这与仓库文档 Data-driven-Fixes.md 中“high-level:描述 API 如何变化,而非描述客户端需要做什么”的设计理念一致。

数据最终被两个工具消费:

  • analysis server:在 IDE 中提供快速修复(quick fixes / code actions);
  • dart fix 命令行:对文件或目录中的代码批量执行修改。

因此,本目录的维护质量直接决定了 flutter_test 用户在新版本升级时能否被自动引导迁移。

2. 目录实际布局一览

当前 fix_data 下的实际结构为:

packages/flutter_test/lib/fix_data/
├── README.md
├── template.yaml
└── fix_flutter_test/
    ├── fix_animation_sheet_builder.yaml
    ├── fix_binding/
    │   ├── fix_automated_test_widgets_flutter_binding.yaml
    │   ├── fix_live_test_widgets_flutter_binding.yaml
    │   └── fix_test_widgets_flutter_binding.yaml
    ├── fix_matchers.yaml
    ├── fix_semantics_controller.yaml
    └── fix_widget_tester.yaml

fix_flutter_test 这一层对应 flutter_test 库本身;其内部再按“类(class)”或“源文件”进行拆分,例如 binding.dart 中同时定义多个 Binding 类,就由 fix_binding/ 子目录收纳。对应地,仓库中每一份 YAML 都可通过 element.uris 字段(如 flutter_test.dart)与 element(类、方法、函数)明确指定修复目标,确保规则落在正确 API 上。

二、文件组织约定:单类一文件、每文件不超过 50 条

原文档给出了三条硬性约定,直接决定了 fix_data 的可维护性:

  1. 单文件规则条数上限为 50。理由是为了更好的可维护性。统计方式很简单:在某 YAML 文件中搜索 title: 出现的次数即为该文件包含的修复规则数(每条 transform 恰好一个 title)。因此文件超过 50 条时,应把其中的规则拆出去。
  2. 按类(class)拆分规则。不要跨类堆叠,拆分的单位是“类”。
  3. 每份新 YAML 应对应单一类,文件命名为 fix_<class>.yaml(例如针对 AnimationSheetBuilderfix_animation_sheet_builder.yaml)。为了让同一源文件下的相关类彼此归组,使用 fix_<filename> 文件夹收纳各单类的规则文件——这正是 fix_binding/ 子目录存放三种 *WidgetsFlutterBinding 规则的原因。

实际写规则时,template.yaml(根目录 template.yaml)是唯一被官方认可的起点:新增规则文件应当template.yaml 复制一份,再按需填写。

三、从 template.yaml 起步:新增规则文件的编写规范

template.yaml 是每条新规则的“空模板”,其核心骨架如下(原文保留):

# For details regarding the *Flutter Fix* feature, see
# https://flutter.dev/to/flutter-fix

# Please add new fixes to the top of the file, separated by one blank line
# from other fixes. In a comment, include a link to the PR where the change
# requiring the fix was made.

# Every fix must be tested. See the
# flutter/packages/flutter_test/lib/fix_data/README.md file for instructions
# on testing these data driven fixes.

# For documentation about this file format, see
# https://dart.dev/go/data-driven-fixes.

# * Fixes in this file are [for CLASS] from the <XXX> library. *

# Uncomment version & transforms, and follow with fixes.
# version: 1
# transforms:

解读模板中的工程约束:

  • 新增规则必须放在文件顶部,且与其他规则之间用空行分隔——这与单一文件阅读、review 的便利性有关;
  • 每条规则上方须以注释形式附带引发该 API 变更的 PR 链接(仓库内真实文件均遵循此约定);
  • 每个 fix 都必须有测试
  • version: 1transforms: 在模板中默认注释,新增文件时取消注释即可启用。

模板内容对应 Data-driven-Fixes.md 中定义的顶层数据结构:数据文件本质上是若干 transform 的列表,每个 transform 描述对一个 element(可在库外引用的顶层声明或成员,如类、扩展、方法、字段)所做的一到多步细粒度 change

四、真实规则拆解:rename、参数增删与条件化迁移

1. 方法/函数重命名(rename)

最简单也最常见的规则是重命名。以 fix_matchers.yaml 为例,它把函数 containsSemantics 改名为 isSemantics

version: 1
transforms:
  # Changes made in https://github.com/flutter/flutter/pull/180538
  - title: 'Rename from containsSemantics to isSemantics'
    date: 2026-01-05
    element:
      uris: ['flutter_test.dart']
      function: 'containsSemantics'
    changes:
      - kind: 'rename'
        newName: 'isSemantics'

要点解析:

  • element.uris 指向修复目标所在库(此处统一为 flutter_test.dart 出口库);
  • element.function 指明目标符号;对类成员方法则改用 method + inClass(见下例);
  • changes[].kind: 'rename' + newName 指示工具将旧符号引用替换为新符号。

2. 方法改名 + 参数级增删组合

单个 API 变更往往需要多个细粒度 change 组合描述。以 fix_animation_sheet_builder.yaml 为例,它同时完成了三件事:

version: 1
transforms:
  # Changes made in https://github.com/flutter/flutter/pull/83337
  # The related deprecation for `sheetSize` doesn't have a fix because there
  # isn't an alternative API, it's being removed completely.
  - title: 'Migrate to collate'
    date: 2023-03-29
    element:
      uris: [ 'flutter_test.dart' ]
      method: 'display'
      inClass: 'AnimationSheetBuilder'
    changes:
      - kind: 'rename'
        newName: 'collate'
      - kind: 'removeParameter'
        name: 'key'
      - kind: 'addParameter'
        index: 0
        name: 'cellsPerRow'
        style: 'required_positional'
        argumentValue:
          expression: '1'

这条规则体现的能力包括:

  • method + inClass:把目标精确定位到 AnimationSheetBuilder.display 方法;
  • 三步级联 change:先 renamecollate,再删除命名参数 key,最后在位置 index: 0 新增一个 required_positional 参数 cellsPerRow,且给调用点自动填入表达式 1
  • 对不可修复项的处理:注释明确指出 sheetSize 属性的废弃没有对应 fix,因为它没有替代 API,属于完全移除——并非所有破坏性变更都能自动化,规则作者需要区分“可迁移”与“直接删除”两种情况。

3. 条件化迁移:oneOf + variables 的复杂场景

当修复动作依赖调用点的现有参数组合时,需要条件逻辑。在 fix_widget_tester.yaml 中,testWidgetsinitialTimeout 参数被迁移为 timeout,规则用 oneOf 枚举两种情况:

version: 1
transforms:
  # Changes made in https://github.com/flutter/flutter/pull/89952
  - title: "Migrate to timeout"
    date: 2023-03-30
    element:
      uris: [ 'flutter_test.dart' ]
      function: 'testWidgets'
    oneOf:
      - if: "initialTimeout != '' && timeout == ''"
        changes:
          - kind: 'addParameter'
            index: 3
            name: 'timeout'
            style: optional_named
            argumentValue:
              expression: 'Timeout({% initialTimeout %})'
              requiredIf: "initialTimeout != '' && timeout == ''"
          - kind: 'removeParameter'
            name: 'initialTimeout'
      - if: "initialTimeout != '' && timeout != ''"
        changes:
          - kind: 'removeParameter'
            name: 'initialTimeout'
    variables:
      initialTimeout:
        kind: 'fragment'
        value: 'arguments[initialTimeout]'
      timeout:
        kind: 'fragment'
        value: 'arguments[timeout]'

这里的关键设计是:

  • variables把调用点参数捕获为可复用的片段(arguments[initialTimeout]),value 用于读取实际传入的表达式;
  • oneOf 分支依据模板中 if 条件判断用户代码形态:若只传了 initialTimeout,则把它包进 Timeout({% initialTimeout %}) 作为 timeout 传入并删除旧参数;若两者都已存在,则仅删除 initialTimeout,保留用户手写的 timeout
  • 占位符语法 {% initialTimeout %} 表示把捕获到的原始表达式原样插入新参数位置。

4. 纯参数移除(无替代物)

当参数被彻底废弃且无等价物时,规则只需 removeParameterfix_binding/fix_test_widgets_flutter_binding.yaml 中就同时含两条此类规则:移除 TestWidgetsFlutterBinding.runTesttimeout 参数、移除 runAsyncadditionalTime 参数(后者同样因方法整体移除而无法提供 fix)。这类规则适用于“API 签名变窄”的纯破坏性变更。

五、规则必须被测试:test_fixes 目录与黄金文件

原文档强调:每个 fix 都必须有测试。验证规则的位置在 test_fixes 目录,其内部布局与 fix_data 一一对应:

packages/flutter_test/test_fixes/flutter_test/
├── animation_sheet_builder.dart
├── animation_sheet_builder.dart.expect
├── binding/
├── matchers.dart
├── matchers.dart.expect
├── semantics_controller.dart
├── semantics_controller.dart.expect
├── widget_tester.dart
└── widget_tester.dart.expect

1. 测试组织方式

每个被测对象由一对文件组成:

  • <name>.dart:代表修复前的“用户代码”,其中故意使用已被规则标记的旧 API。以 widget_tester.dart 为例,它在 main() 里构造了三种 testWidgets 调用形态,分别是“仅 timeout”“timeoutinitialTimeout 并存”“仅 initialTimeout”,并在注释中标明每条规则对应的 PR(https://github.com/flutter/flutter/pull/89952);
  • <name>.dart.expect:同名的 .expect 黄金文件,记录执行 dart fix 之后应得到的“期望输出”。

测试框架会把 dart fix 实际改写结果与 .expect 逐行比对,从而校验每一条 transform 的行为,覆盖前面提到的 rename、参数增删以及 oneOf 条件分支。

2. 本地运行命令

原文档给出了本地执行全部 fix 测试的命令,需在 test_fixes 目录下运行:

dart fix --compare-to-golden

即进入 packages/flutter_test/test_fixes 后执行上述命令,工具会遍历该目录下的测试用例,将 dart fix 的输出与对应 .expect 文件比较,任何不匹配都会以测试失败的形式暴露。

3. 何时运行测试

按仓库约定,测试应至少覆盖:

  • 新增一条 fix 之后(模板头部注释也明确要求 “Every fix must be tested”);
  • 对已存在的规则进行结构性调整之后;
  • 任何可能影响 transform 匹配逻辑的 YAML 改动之后。

六、结构性变更的外部影响与协同要求

原文档特别提醒:对 fix_data 目录的结构性修改(如移动文件、改名、改变目录层级)需要格外谨慎,因为这些测试不止在本仓库运行。

  1. dart-lang/sdk 仓库的 CI 也会执行这些测试:用于确保 Dart SDK 对 dart fix 文件格式的改动不会破坏 Flutter。也就是说,本目录同时是 dart fix 文件格式稳定性的“外部探针”。
  2. Flutter 侧的执行入口dev/bots/test.dart:Flutter 仓库自身通过该脚本统一调度这些 fix 测试。
  3. 协同约定:当修改可能影响外部 analyze_flutter_flutter.sh 脚本时,应尽可能先与相关方协调,避免破坏 dart-lang/sdk 对 Flutter 的格式回归测试链路。

换句话说,fix_datatest_fixes 处于 Flutter 与 Dart SDK 两个大型仓库 CI 的交汇点,任何目录级重构都应视为跨仓库兼容性改动来对待。

七、延伸阅读与实操路径

  • 关于 Data-Driven Fixes 的完整规范(数据文件结构、transform/element/change 参考、测试指引),参见仓库文档 Data-driven-Fixes.md(该文档明确说明:此特性面向 Flutter 官方包的维护者,暂不开放给其他插件作者)。
  • 需要新增一条规则时的推荐步骤小结:
    1. template.yaml 基础上复制出 fix_<class>.yaml(若同一 dart 源文件含多个类,则归入对应的 fix_<filename> 子目录);
    2. 取消注释 version: 1transforms:,将新规则写到文件顶部并隔开空行,注释中附带引入 API 变更的 PR 链接;
    3. element 精确定位目标 API,按需组合 renameaddParameterremoveParameter,复杂场景使用 oneOf + variables
    4. 确认单文件 title: 数量不超过 50 条;
    5. test_fixes 中补齐“用户代码样例”与对应的 .expect 黄金文件;
    6. 进入 packages/flutter_test/test_fixes 执行 dart fix --compare-to-golden 验证全部规则。

按照上述约定,任何一位 Flutter 框架贡献者都能让 flutter_test 的 API 演进以“代码提示 + 一键迁移”的形式平滑落到每一位使用者手中。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389