|
| 1 | +# Writing Rules |
| 2 | + |
| 3 | +This package gives analyzer plugin authors the ability to write static rules |
| 4 | +for source code. This document describes briefly how to write such a rule, and |
| 5 | +how to register it in an analyzer plugin. |
| 6 | + |
| 7 | +## Declaring an analysis rule |
| 8 | + |
| 9 | +Every analysis rule is declared in two parts: a rule class that extends |
| 10 | +`AnalysisRule`, and a visitor class that extends `SimpleAstVisitor`. |
| 11 | + |
| 12 | +### The rule class |
| 13 | + |
| 14 | +The rule class contains some general information about the rule, like its name |
| 15 | +and the diagnostic or diagnostics that the rule reports. It also registers the |
| 16 | +various syntax tree nodes that the visitor class needs to visit. Let's see an |
| 17 | +example: |
| 18 | + |
| 19 | +```dart |
| 20 | +class MyRule extends AnalysisRule { |
| 21 | + static const LintCode _code = LintCode( |
| 22 | + 'my_rule', |
| 23 | + 'No await expressions', |
| 24 | + correctionMessage: "Try removing 'await'.", |
| 25 | + ); |
| 26 | +
|
| 27 | + MyRule() |
| 28 | + : super( |
| 29 | + name: LintNames.prefer_void_to_null, |
| 30 | + description: 'A longer description of the rule.', |
| 31 | + ); |
| 32 | +
|
| 33 | + @override |
| 34 | + LintCode get lintCode => _code; |
| 35 | +
|
| 36 | + @override |
| 37 | + void registerNodeProcessors( |
| 38 | + NodeLintRegistry registry, LinterContext context) { |
| 39 | + var visitor = _Visitor(this, context); |
| 40 | + registry.addAwaitExpression(this, visitor); |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +Let's look at each declaration individually: |
| 46 | + |
| 47 | +* `class MyRule extends AnalysisRule` - The rule class must extend |
| 48 | + `AnalysisRule`. |
| 49 | + |
| 50 | +* `static const LintCode _code` and `LintCode get lintCode` - Each rule class |
| 51 | + must implement either `LintCode get lintCode` or `List<LintCode> get |
| 52 | + lintCodes`, depending on whether there is only one diagnostic that it can |
| 53 | + report, or multiple. |
| 54 | + |
| 55 | + A `LintCode` is the template for each diagnostic that is to be reported. It |
| 56 | + contains the diagnostic name, problem message, and optionally the correction |
| 57 | + message. We instantiate a `LintCode` as a static field so that it can also be |
| 58 | + made const. If the rule needs to report more than one `LintCode`, with |
| 59 | + different problem messages, then multiple static fields can be declared. |
| 60 | + |
| 61 | +* `MyRule()` - The rule class must have a constructor that calls `super()`, |
| 62 | + passing along the name of the rule, and a description. Typically this |
| 63 | + constructor has zero parameters. |
| 64 | + |
| 65 | +* `void registerNodeProcessors(...)` - An analysis rule uses a visitor to walk |
| 66 | + a [Dart syntax tree][] (we see how the visitor is defined in "The visitor |
| 67 | + class," below). This visitor is typically named `_Visitor`. This visitor |
| 68 | + class must be instantiated once in this method. Typically, the instance of |
| 69 | + the rule class (`this`) and a `LinterContext` object (described below) are |
| 70 | + passed to the visitor constructor. |
| 71 | + |
| 72 | + In order for such a visitor's various 'visit' methods to be called, we need |
| 73 | + to register them, in a `NodeLintRegistry`. Each 'visit' method found on |
| 74 | + `SimpleAstVisitor` has a corresponding 'add' method in the `NodeLintRegistry` |
| 75 | + class. |
| 76 | + |
| 77 | +[Dart syntax tree]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/doc/tutorial/ast.md |
| 78 | + |
| 79 | +### The visitor class |
| 80 | + |
| 81 | +The visitor class contains the code that examines syntax nodes and reports |
| 82 | +diagnostics. See the [API documentation][SimpleAstVisitor docs] for the |
| 83 | +`SimpleAstVisitor` class to find the various 'visit' methods available for |
| 84 | +implementation. Let's look at a quick example: |
| 85 | + |
| 86 | +[SimpleAstVisitor docs]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/lib/dart/ast/visitor.dart#L1841 |
| 87 | + |
| 88 | +```dart |
| 89 | +class _Visitor extends SimpleAstVisitor<void> { |
| 90 | + final LintRule rule; |
| 91 | +
|
| 92 | + final LinterContext context; |
| 93 | +
|
| 94 | + _Visitor(this.rule, this.context); |
| 95 | +
|
| 96 | + @override |
| 97 | + void visitAwaitExpression(AwaitExpression node) { |
| 98 | + if (context.isInLibDir) { |
| 99 | + rule.reportLint(node); |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +Let's look at each declaration individually: |
| 106 | + |
| 107 | +* `class _Visitor extends SimpleAstVisitor<void>` - Each visitor must extend |
| 108 | + `SimpleAstVisitor`. While the analyzer package provides other Dart syntax |
| 109 | + tree visitors, using one directly in a rule can result in poor performance |
| 110 | + and unexpected behavior. The type argument on `SimpleAstVisitor` is not |
| 111 | + important, as 'visit' return values are not used, so `void` is appropriate. |
| 112 | +* `final LintRule rule` - The rule is the object to which we can report |
| 113 | + diagnostics (lints or warnings). Several methods are provided, all starting |
| 114 | + with `reportLint`. The different methods allow for different ranges of text |
| 115 | + to be highlighted. |
| 116 | +* `final LinterContext context` - The LinterContext object provides various |
| 117 | + information about the library being analyzed. In this example, we make use of |
| 118 | + a `isInLibDir` utility. |
| 119 | +* `_Visitor(...)` - Often the constructor just initializes the LintRule and |
| 120 | + LinterContext fields. Other information can be initialized as well. |
| 121 | +* `void visitAwaitExpression(AwaitExpression node)` - The main component of the |
| 122 | + `_Visitor` class is the 'visit' methods. In this case, `visitAwaitExpression` |
| 123 | + is invoked for each 'await expression' found in the source code under |
| 124 | + analysis. Typically, a 'visit' method like this is where we perform some |
| 125 | + analysis and maybe report lint(s) or warning(s). |
| 126 | + |
| 127 | +## Registering an analysis rule |
| 128 | + |
| 129 | +In order for an analysis rule to be used in an analyzer plugin, it must be |
| 130 | +registered. Register an instance of an analysis rule inside a plugin's |
| 131 | +`register` method: |
| 132 | + |
| 133 | +```dart |
| 134 | +class SimplePlugin extends Plugin { |
| 135 | + @override |
| 136 | + void register(PluginRegistry registry) { |
| 137 | + registry.registerWarningRule(MyRule()); |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +Here, the instance of MyRule is registered as a "warning rule," so that it is |
| 143 | +enabled by default. To register an analysis rule as a "lint rule," such that it |
| 144 | +must be specifically enabled from analysis options, use `registerLintRule` |
| 145 | +instead. |
| 146 | + |
| 147 | +TODO(srawlins): Write up and link documentation for this Plugin subclass. |
0 commit comments