Skip to content

Commit e508372

Browse files
srawlinsCommit Queue
authored andcommitted
DAS plugins: initial doc for writing rules
Change-Id: I50957b9ae9b19d8cfa5371f5398bc3a001df1661 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408940 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 51ea425 commit e508372

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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

Comments
 (0)