目录

自定义静态分析

静态分析让你的代码问题能在运行前被发现,它在防止问题产生和代码风格指南的遵循上很有帮助。

在分析器的帮助下,你可以发现简单的拼写错误。例如,可能在 if 语句中不小心多打了一个分号:

void increment() {
  if (count < 10) ;
  count++;
}

如果配置得当,分析器会指出这个分号的位置并输出如下警告:

info - example.dart:9:19 - Unnecessary empty statement. Try removing the empty statement or restructuring the code. - empty_statements

分析器也能帮你找出更多细节问题。例如,也许你忘记关闭一个 sink 了:

var controller = StreamController<String>();
info - Close instances of `dart.core.Sink`. - close_sinks

在 Dart 生态系统中,Dart 分析服务和其他相关工具使用了 analyzer 来提供静态分析。

你可以自定义静态分析以寻找各种潜在的问题,包括在 Dart 编程语言规范 中规定的错误和警告。你同样能通过配置 linter ——分析器的一个插件,来确保你的代码遵循 Dart 代码风格指南高效 Dart 中其他建议的准则。诸如 Dart 编译器 (dart compile)dart analyze, flutter analyze, 和 JetBrains IDEs 等 Dart 工具都会使用 analyzer package 来评估你的代码。

这篇文档解释了如何通过使用分析配置文件,或在 Dart 源代码中添加注释来自定义分析器的行为。如果你想在工具中添加静态分析规则,请参考 analyzer package 的文档和 Analysis Server API 规范

分析配置文件

将分析配置文件 analysis_options.yaml 放在包的根目录,即和 pubspec 文件同样的目录下。

这是一个分析配置文件的示例:

include: package:lints/recommended.yaml

analyzer:
  exclude: [build/**]
  language:
    strict-casts: true
    strict-raw-types: true

linter:
  rules:
    - cancel_subscriptions

该示例说明了一些最常用的顶级配置入口:

如果分析器在 package 的根目录下无法找到一个分析配置文件,它将会往下查找整个目录树。如果还是没有可用的配置文件,分析器则默认使用标准检查规则。

对于如下所示的一个大型项目的目录结构而言:

project root contains analysis_options.yaml (#1) and 3 packages, one of which (my_package) contains an analysis_options.yaml file (#2).

分析器使用 #1 文件来分析 my_other_packagemy_other_other_package 中的代码,使用 #2 文件来分析 my_package 中的代码。

启用更严格的类型检查

如果你想要比 Dart 类型系统 所要求的更加严格的类型检查,考虑开启 strict-castsstrict-inference,和 strict-raw-types 语言模式:

analyzer:
  language:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true

你可以单独或一起使用这些模式,他们默认都为 false 状态。

strict-casts: <bool>
设为 true 可确保类型推理引擎不再将 dynamic 进行隐式类型转换。下方的 Dart 代码在 List<String> 参数上传递了一个 jsonDecode 方法的返回值,实际是将返回的 dynamic 做了隐式向下转换,在运行时可能导致错误。该模式会报告此类潜在的错误,要求你添加一个显示的类型转换或者调整你的代码。

void foo(List<String> lines) {
  ...
}

void bar(String jsonText) {
  foo(jsonDecode(jsonText)); // Implicit cast
}
error - The argument type 'dynamic' can't be assigned to the parameter type 'List<String>'. - argument_type_not_assignable

strict-inference: <bool>
设为 true 可确保当类型推理引擎无法确定静态类型时,不再选择dynamic 类型。下方合法 Dart 代码创建了一个类型参数无法被推断的 Map,在该模式下会触发推断失败的 hint 提示:

final lines = {}; // Inference failure
lines['Dart'] = 10000;
lines['C++'] = 'one thousand';
lines['Go'] = 2000;
print('Lines: ${lines.values.reduce((a, b) => a + b)}'); // Runtime error
info - The type argument(s) of 'Map' can't be inferred - inference_failure_on_collection_literal

strict-raw-types: <bool>
设为 true 可确保当类型推理引擎,由于省略类型参数而无法确定静态类型时,不再选择dynamic 类型。下方合法 Dart 代码中有一个原始类型的 List 变量,导致在该模式下触发了原始类型 hint 提示。

List numbers = [1, 2, 3]; // List with raw type
for (final n in numbers) {
  print(n.length); // Runtime error
}
info - The generic type 'List<dynamic>' should have explicit type arguments but doesn't - strict_raw_type

启用和停用 linter 规则

analyzer 包同样提供一个代码 linter,并包含一份广泛多样的 linter 规则。提示规则之间往往是无关联性的,各种规则之间不必彼此遵守。例如,有些规则更合适支持库,而另一些则是为 Flutter 应用设计的。注意,linter 规则可能会触发误报,静态分析则不会。

启用 Dart 团队推荐的 linter 规则

Dart 团队在 lints package 中提供了 2 个推荐的 linter 规则集合:

核心规则
帮助确认可能导致在运行或使用 Dart 代码时引发问题的关键事项。所有类型的代码都应该符合这些 linter 规则。被上传至 pub.dev 的 package,会有一个部分基于通过这些规则的情况而生成的 package 评分

推荐规则
帮助确认其他可能导致运行或使用 Dart 代码时引发问题的事项,并强制使用单一、惯用的代码风格和代码格式化。作为一个核心规则的超集,我们推荐所有的 Dart 代码使用它。

lints package 作为 dev dependency 添加,来启用任意 lints 集合。

$ dart pub add --dev lints

然后编辑 analysis_options.yaml 文件来引入你想要的规则集合:

include: package:lints/<RULE_SET>.yaml

例如,你可以像这样引入推荐规则的集合:

include: package:lints/recommended.yaml

启用单条规则

在分析配置文件中添加顶层 key linter: 来启用单条规则,紧跟着用 rules: 作为二级 key。在后续行中,以短横杠为前缀(YAML 列表的语法),指定你想要添加的规则。例如:

linter:
  rules:
    - always_declare_return_types
    - cancel_subscriptions
    - close_sinks
    - comment_references
    - one_member_abstracts
    - only_throw_errors
    - package_api_docs
    - prefer_final_in_for_each
    - prefer_single_quotes

停用单条规则

如果你引入了一个分析配置文件(比如 lints 中的某一个),你可能会想要停用其中的一部分。停用单条规则和启用单条规则是类似的,但要求使用键值对而不是列表来作为 rules: 的值。因此每一行应该包括规则的名字,后面跟上 : false 或者 : true

这里是一个分析配置文件的示例,其中使用了来自 lints 的所有推荐规则,除了 avoid_shadowing_type_parameters 被单独停用。这里同样单独启用了 await_only_futures 这条 lint。

include: package:lints/recommended.yaml

linter:
  rules:
    avoid_shadowing_type_parameters: false
    await_only_futures: true

从 analysis 中排除代码

有时候,部分代码可能允许包含分析出的警告和提示。例如,你也许依赖于某个不属于你 package 所生成的代码,这些代码可以正常运行,但是在静态检查中会产生警告。或者某个 linter 规则可能会出现你想关掉的误报。

有几种方法可以从 analysis 中排除代码:

  • 从 analysis 中排除整个文件。

  • 在单个文件中停止特定的非错误规则的生效。

  • 在单个文件的某几行中,停止特定的非错误规则的生效。

你同样可以对所有文件 停用特定的规则,或者 改变规则的警告等级

排除文件

使用分析器选项 exclude: 在静态分析中排除文件。你可以列出单独的文件,或者用 glob 语法:

analyzer:
  exclude:
    - lib/client.dart
    - lib/server/*.g.dart
    - test/_data/**

对单个文件忽略规则

通过在文件中添加 ignore_for_file 注释,使得特定的非错误规则对该文件忽略。

// ignore_for_file: unused_local_variable

该操作对整个文件都生效,不论代码是在注释之前还是之后,对于生成的代码尤其有用。

使用逗号分隔的列表,可以忽略多条规则:

// ignore_for_file: unused_local_variable, duplicate_ignore, dead_code

添加 type=lint 以忽略所有的 linter 规则:

// ignore_for_file: type=lint

对一行代码忽略规则

通过在单行代码的上方添加 ignore 注释,使得特定的非错误规则对该行代码忽略。这是一个忽略代码导致运行时错误的例子,你可能会在一个开发语言的测试上使用。

// ignore: invalid_assignment
int x = '';

使用逗号分隔的列表,可以忽略多条规则:

// ignore: invalid_assignment, const_initialized_with_non_constant_value
const x = y;

或者,将需要忽略的规则追加到相应的行后:

int x = ''; // ignore: invalid_assignment

自定义 analysis 规则

每个 分析器错误码linter 规则 都有一个默认的警告等级。你可以使用分析配置文件来改变单个规则的警告等级,或者总是忽略某些规则。

分析器支持三种警告等级:

info
消息,不会造成 analysis 验证失败。例如:dead_code

warning
警告,一般不会造成 analysis 验证失败,除非分析器被配置了对待警告与错误一致。例如:invalid_null_aware_operator

error
错误,会造成 analysis 验证失败。例如:invalid_assignment

忽略规则

你可以通过使用 errors: 字段,来忽略特定的 分析器错误码 and linter 规则。列出规则,在后面加上 : ignore。例如下方的分析配置文件,指示分析器工具忽略了 TODO 规则:

analyzer:
  errors:
    todo: ignore

修改规则的警告等级

你可以全局修改单个规则的警告等级。这项技术对于常规的 analysis 问题和 lints 问题都有效。例如下方的分析配置文件,指示分析器工具把无效的赋值配置为警告,把缺少返回值配置为错误,而对于无法执行到的代码,仅提供并非警告和错误的信息通知。

analyzer:
  errors:
    invalid_assignment: warning
    missing_return: error
    dead_code: info

更多资源

你还可以通过以下资源来深入了解 Dart 的静态分析: