| # Writing rules |
| |
| This package gives analyzer plugin authors the ability to write static rules |
| for source code. This document describes briefly how to write such a rule, and |
| how to register it in an analyzer plugin. |
| |
| ## Declaring an analysis rule |
| |
| Every analysis rule is declared in two parts: a rule class that extends |
| `AnalysisRule`, and a visitor class that extends `SimpleAstVisitor`. |
| |
| ### The rule class |
| |
| The rule class contains some general information about the rule, like its name |
| and the diagnostic or diagnostics that the rule reports. It also registers the |
| various syntax tree nodes that the visitor class needs to visit. Let's see an |
| example: |
| |
| ```dart |
| 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/error/error.dart'; |
| |
| class MyRule extends AnalysisRule { |
| static const LintCode code = LintCode( |
| 'my_rule', |
| 'No await expressions', |
| correctionMessage: "Try removing 'await'.", |
| ); |
| |
| MyRule() |
| : super( |
| name: 'my_rule', |
| description: 'A longer description of the rule.', |
| ); |
| |
| @override |
| LintCode get diagnosticCode => code; |
| |
| @override |
| void registerNodeProcessors( |
| RuleVisitorRegistry registry, RuleContext context) { |
| var visitor = _Visitor(this, context); |
| registry.addAwaitExpression(this, visitor); |
| } |
| } |
| ``` |
| |
| Let's look at each declaration individually: |
| |
| * `class MyRule extends AnalysisRule` - The rule class must extend |
| `AnalysisRule`. |
| |
| * `static const LintCode _code` and `LintCode get diagnosticCode` - The rule class |
| must implement `LintCode get diagnosticCode`, for infrastructure to be able |
| to register the diagnostic code that the rule can report. |
| |
| A `LintCode` is the template for each diagnostic that is to be reported. It |
| contains the diagnostic name, problem message, and optionally the correction |
| message. We instantiate a `LintCode` as a static field so that it can also be |
| made const. |
| |
| Alternatively, if a rule can report several different diagnostic codes |
| (typically for differentiated messages), it can instead extend |
| `MultiAnalysisRule`, and then implement `List<LintCode> get diagnosticCodes` |
| instead of `LintCode get diagnosticCode`. The rule can then declare the |
| different `LintCode`s in multiple static fields, which are referenced in the |
| `diagnosticCodes` getter. |
| |
| * `MyRule()` - The rule class must have a constructor that calls `super()`, |
| passing along the name of the rule, and a description. Typically this |
| constructor has zero parameters. |
| |
| * `void registerNodeProcessors(...)` - An analysis rule uses a visitor to walk |
| a [Dart syntax tree][] (we see how the visitor is defined in "The visitor |
| class," below). This visitor is typically named `_Visitor`. This visitor |
| class must be instantiated once in this method. Typically, the instance of |
| the rule class (`this`) and a `RuleContext` object (described below) are |
| passed to the visitor constructor. |
| |
| In order for such a visitor's various 'visit' methods to be called, we need |
| to register them, in a `RuleVisitorRegistry`. Each 'visit' method found on |
| `SimpleAstVisitor` has a corresponding 'add' method in the |
| `RuleVisitorRegistry` class. |
| |
| [Dart syntax tree]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/doc/tutorial/ast.md |
| |
| ### The visitor class |
| |
| The visitor class contains the code that examines syntax nodes and reports |
| diagnostics. See the [API documentation][SimpleAstVisitor docs] for the |
| `SimpleAstVisitor` class to find the various 'visit' methods available for |
| implementation. Let's look at a quick example: |
| |
| [SimpleAstVisitor docs]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/lib/dart/ast/visitor.dart#L1841 |
| |
| ```dart |
| import 'package:analyzer/analysis_rule/analysis_rule.dart'; |
| import 'package:analyzer/analysis_rule/rule_context.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| |
| class _Visitor extends SimpleAstVisitor<void> { |
| final AnalysisRule rule; |
| |
| final RuleContext context; |
| |
| _Visitor(this.rule, this.context); |
| |
| @override |
| void visitAwaitExpression(AwaitExpression node) { |
| if (context.isInLibDir) { |
| rule.reportAtNode(node); |
| } |
| } |
| } |
| ``` |
| |
| Let's look at each declaration individually: |
| |
| * `class _Visitor extends SimpleAstVisitor<void>` - Each visitor must extend |
| `SimpleAstVisitor`. While the analyzer package provides other Dart syntax |
| tree visitors, using one directly in a rule can result in poor performance |
| and unexpected behavior. The type argument on `SimpleAstVisitor` is not |
| important, as 'visit' return values are not used, so `void` is appropriate. |
| * `final AnalysisRule rule` - The rule is the object to which we can report |
| diagnostics (lints or warnings). Several methods are provided, all starting |
| with `reportAt`. The different methods allow for different ranges of text to |
| be highlighted. |
| * `final RuleContext context` - The RuleContext object provides various |
| information about the library being analyzed. In this example, we make use of |
| a `isInLibDir` utility. |
| * `_Visitor(...)` - Often the constructor just initializes the AnalysisRule and |
| RuleContext fields. Other information can be initialized as well. |
| * `void visitAwaitExpression(AwaitExpression node)` - The main component of the |
| `_Visitor` class is the 'visit' methods. In this case, `visitAwaitExpression` |
| is invoked for each 'await expression' found in the source code under |
| analysis. Typically, a 'visit' method like this is where we perform some |
| analysis and maybe report lint(s) or warning(s). |
| |
| Some rules do not require complex logic in the visitor class, but rules may |
| also need to walk up or down the syntax tree, or examine properties of nodes |
| carefully and thoroughly. For many examples of analysis rules and their visitor |
| classes, see the [lint rules] that ship with the Dart Analysis Server. |
| |
| [lint rules]: https://github.com/dart-lang/sdk/tree/main/pkg/linter/lib/src/rules |
| |
| ## Registering an analysis rule |
| |
| In order for an analysis rule to be used in an analyzer plugin, it must be |
| registered. Register an instance of an analysis rule inside a plugin's |
| `register` method: |
| |
| ```dart |
| import 'package:analysis_server_plugin/plugin.dart'; |
| import 'package:analysis_server_plugin/registry.dart'; |
| |
| class SimplePlugin extends Plugin { |
| @override |
| void register(PluginRegistry registry) { |
| registry.registerWarningRule(MyRule()); |
| } |
| } |
| ``` |
| |
| Here, the instance of MyRule is registered as a "warning rule," so that it is |
| enabled by default. To register an analysis rule as a "lint rule," such that it |
| must be specifically enabled from analysis options, use `registerLintRule` |
| instead. |
| |
| See [writing a plugin][] for information about the `Plugin` class. |
| |
| ## Testing an analysis rule |
| |
| Writing tests for an analysis rule is very easy, and is documented at [testing_rules][]. |
| |
| [writing a plugin]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_a_plugin.md |
| [testing rules]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/testing_rules.md |