一、为啥要搞团队专属的Dart lint规范

做开发的都知道,代码写得好不好,除了功能对不对,还有个很重要的点是“风格统一”。尤其是团队人多的时候,你写的变量名用驼峰,我用下划线,他还加个拼音,新人接手项目光认变量名就得花半天;再比如有人喜欢把一行代码写得老长,有人每个条件判断都加注释,这些乱七八糟的风格凑一起,代码库就跟“百家饭”似的,维护起来巨费劲。

之前大家可能用过Dart自带的dart analyze,它能查语法错误、简单的风格问题,但自带的规则太通用了,满足不了团队的特殊要求。比如我们团队要求所有和支付相关的函数必须加注释说明风险等级,或者所有自定义组件的构造函数必须传key,自带规则就管不了。这时候就需要定制专属的lint规则,还要自动检查,不然靠人review太费时间,还容易漏。

二、用custom_lint搞定制规则的核心步骤

custom_lint是专门给Dart/Flutter做定制lint的工具,相当于给我们开了个“规则自定义权限”,想查啥就查啥。整个流程分三步:先搭基础项目,再写具体规则,最后测试规则。

2.1 先搭custom_lint的基础项目

首先得准备两个项目:一个是“规则项目”(用来写我们的定制规则),一个是“测试项目”(用来验证规则好不好用)。

先建规则项目,用下面的命令:

# 新建一个叫team_lint的规则项目,选package类型
dart create -t package team_lint
cd team_lint

然后给这个项目加custom_lint的依赖,打开pubspec.yaml,把依赖改成下面这样:

name: team_lint
description: 团队专属的Dart lint规则
version: 1.0.0

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  analyzer: ^6.0.0 # 用来分析代码结构的工具
  custom_lint: ^0.5.0 # 核心的定制lint工具
  source_span: ^1.10.0 # 用来定位代码问题的位置

dev_dependencies:
  test: ^1.24.0

改完后运行dart pub get装依赖。

接下来要把规则项目变成custom_lint能识别的插件,打开lib/team_lint.dart,改成:

// 告诉custom_lint,这个项目的规则都从这里加载
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'src/rules/payment_function_comment.dart'; // 后面要写的规则文件

PluginBase createPlugin() => _TeamLintPlugin();

class _TeamLintPlugin extends PluginBase {
  @override
  List<LintRule> getLintRules(CustomLintConfigs configs) {
    // 把我们写的规则加进来,后面加新规则就往这个列表里添
    return [
      PaymentFunctionCommentRule(),
    ];
  }
}

然后在lib/src下新建rules文件夹,再建payment_function_comment.dart,这就是我们具体规则的文件。

2.2 写第一个定制规则:支付函数必须加风险注释

我们团队的要求是:所有函数名以“pay”开头的(比如payOrder、payWithAlipay),必须加文档注释,而且注释里必须有“风险等级:低/中/高”的字样,不然就报警告。

先写这个规则的代码,打开lib/src/rules/payment_function_comment.dart:

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

// 规则必须继承自LintRule
class PaymentFunctionCommentRule extends LintRule {
  // 规则的唯一ID,必须唯一,用来区分不同规则
  static const String ruleId = 'payment_function_comment';

  // 规则的配置:比如错误等级、显示的标题、详细说明
  static const LintCode code = LintCode(
    ruleId,
    '支付函数必须添加风险等级注释', // 显示给开发者的错误标题
    problemMessage: '函数名以"pay"开头的支付函数,必须添加包含"风险等级:X"的文档注释', // 详细错误说明
    correctionMessage: '请在函数上方添加文档注释,格式为/// 支付XX:风险等级:X', // 给开发者的修正提示
  );

  // 初始化规则
  PaymentFunctionCommentRule() : super(code: code);

  // 核心逻辑:遍历代码,找符合条件的函数
  @override
  void run(
    CustomLintResolver resolver,
    ChangeReporter reporter,
    CustomLintContext context,
  ) {
    // 注册回调:当解析完一个函数声明时,执行检查逻辑
    context.registry.addFunctionDeclaration((node) {
      // 1. 先判断函数名是不是以pay开头(忽略大小写,比如PayOrder也能匹配)
      final functionName = node.name.lexeme.toLowerCase();
      if (!functionName.startsWith('pay')) return;

      // 2. 再判断有没有文档注释,以及注释里有没有“风险等级:”
      final docComment = node.documentationComment;
      if (docComment == null) {
        // 没有注释,直接报错误
        _reportError(reporter, node);
        return;
      }
      // 把所有注释拼起来,检查有没有“风险等级:”
      final commentText = docComment.tokens.join(' ');
      if (!commentText.contains('风险等级:')) {
        // 有注释但没风险等级,报错误
        _reportError(reporter, node);
      }
    });
  }

  // 封装报错误的逻辑,避免重复代码
  void _reportError(ChangeReporter reporter, FunctionDeclaration node) {
    reporter.createChangeBuilder(
      message: code.problemMessage,
      priority: LintCodePriority.error,
    ).addDartFileEdit((builder) {
      builder.addSimpleInsertion(
        node.offset, // 错误位置的起始位置
        '/// 支付${node.name.lexeme}:风险等级:\n', // 给开发者的自动补全提示
      );
    });
  }
}

这个规则的逻辑很简单:先找所有函数,过滤出名字以pay开头的,然后检查有没有注释、注释里有没有要求的内容,不符合就报错,还能自动补个模板注释。

2.3 测试规则好不好用

规则写完得验证,我们需要一个测试项目。回到规则项目的根目录,新建一个叫test_project的文件夹:

dart create test_project
cd test_project

然后打开test_project的pubspec.yaml,加规则项目的依赖(因为我们的规则项目还没发布到pub,所以用本地路径):

name: test_project
description: 测试定制lint规则的项目
version: 1.0.0

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  custom_lint: ^0.5.0
  # 引入本地的规则项目,路径要对
  team_lint:
    path: ../team_lint

改完运行dart pub get装依赖。

接下来打开test_project的lib/main.dart,写几个测试用的函数:

// 正确的函数:有注释,有风险等级
/// 支付订单:风险等级:中
void payOrder() {}

// 错误的函数:没有注释
void payWithAlipay() {}

// 错误的函数:有注释但没风险等级
/// 支付会员费
void payVip() {}

// 非支付函数:不检查
void getUserInfo() {}

然后运行custom_lint的检查命令:

dart run custom_lint

正常会输出两个错误,对应payWithAlipay和payVip这两个不符合要求的函数,说明规则生效了。

三、把规则集成到CI里,实现自动检查

规则写好、测试通过后,还得把它加到CI(持续集成)里,这样每次有人提交代码,CI就会自动跑检查,不符合要求的代码不让合并,彻底杜绝“百家饭”代码。

3.1 选CI工具:用GitHub Actions举例

现在大部分项目都用GitHub托管,所以我们用GitHub Actions来做CI,其他CI工具(比如GitLab CI、Jenkins)逻辑差不多,只是配置文件格式不一样。

先在项目根目录新建.github/workflows文件夹,再新建lint.yml文件,内容如下:

name: Dart Lint Check # CI任务的名字,GitHub上显示的
on:
  # 触发条件:每次有人提交代码到main分支,或者提PR到main分支时
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest # 用Ubuntu系统跑任务
    steps:
      # 第一步:拉取最新的代码
      - name: Checkout code
        uses: actions/checkout@v4

      # 第二步:安装Dart环境,指定版本和项目的SDK版本一致
      - name: Set up Dart
        uses: dart-lang/setup-dart@v1
        with:
          sdk: '3.0.0' # 要和项目的sdk版本一致

      # 第三步:安装项目依赖
      - name: Install dependencies
        run: dart pub get

      # 第四步:跑定制的lint检查
      - name: Run custom lint
        run: dart run custom_lint

这个配置的逻辑很简单:每次有代码提交或者PR,就拉代码、装依赖、跑lint检查,如果检查失败,CI就会标红,阻止代码合并。

3.2 注意事项:避免CI报错的坑

  1. 路径问题:如果规则项目和测试项目不在同一个仓库,不能用本地路径引入,得把规则项目发布到pub.dev,或者用git路径引入,比如:
dev_dependencies:
  team_lint:
    git:
      url: https://github.com/xxx/team_lint.git
      ref: main # 用main分支的代码
  1. 版本问题:所有依赖的版本(比如analyzer、custom_lint)要和项目的Dart SDK版本兼容,不然会报依赖冲突。
  2. 规则优先级:如果有多个规则,要设置优先级,比如把严重的错误设为error,警告设为warning,避免无关的警告干扰检查。

四、定制规则的应用场景、优缺点和注意事项

4.1 应用场景

除了我们举的支付函数加注释的例子,定制规则还能解决很多团队的特殊问题:

  • 所有自定义组件的构造函数必须传key(Flutter项目常用,避免组件状态混乱);
  • 所有接口请求的函数必须加超时时间的参数;
  • 所有枚举类必须有对应的字符串转枚举的方法;
  • 禁止用某些过时的函数(比如自己团队废弃的旧支付接口)。

4.2 优缺点

优点:

  • 统一代码风格:彻底解决团队代码风格不一致的问题,新人接手快,维护成本低;
  • 提前发现问题:能检查出静态分析工具查不到的问题,比如团队的特殊要求,减少review的压力;
  • 自动执行:集成到CI后,不用人盯着,代码提交就自动检查,避免漏查。

缺点:

  • 有学习成本:要写规则得懂analyzer的代码结构,刚开始写规则可能会觉得难;
  • 维护成本:规则多了之后,每次Dart SDK升级,可能要更新analyzer、custom_lint的版本,不然会报错;
  • 不能太复杂:规则太复杂的话,跑检查的时间会变长,影响CI的速度,所以规则要尽量简单。

4.3 注意事项

  • 规则不要太多:只加团队真正需要的规则,太多规则会让开发者觉得麻烦,反而产生抵触情绪;
  • 规则要灵活:可以给规则加配置,比如有些函数可以豁免检查(比如测试用的函数);
  • 及时更新:Dart SDK升级后,要及时更新规则的依赖,避免规则失效;
  • 给开发者反馈:规则报错时,要给明确的修正提示,比如我们写的自动补全注释,让开发者知道怎么改。

五、总结

搞团队专属的Dart lint规范,本质上是用工具代替人来管理代码质量,既统一了风格,又减少了维护成本。整个流程其实不复杂:先搭custom_lint的基础项目,再写符合团队要求的规则,测试通过后集成到CI里自动检查。刚开始可能会觉得写规则有点难,但只要多写几个例子,熟悉了analyzer的逻辑,就能轻松搞定。

最后要提醒的是,规则不是越多越好,要贴合团队的实际需求,比如小团队可能只需要几个核心规则,大团队可以多加点,但也要注意不要给开发者增加太多负担。只要规则合适,就能让团队的代码质量上一个台阶,大家写代码也更省心。