Change-Id: If52b7557251fbe022ec873f003fafecf035a370a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429700 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
6.6 KiB
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:
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dart/error/lint_codes.dart';
import 'package:analyzer/src/lint/linter.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(
NodeLintRegistry registry, LinterContext 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 extendAnalysisRule. -
static const LintCode _codeandLintCode get diagnosticCode- The rule class must implementLintCode get diagnosticCode, for infrastructure to be able to register the diagnostic code that the rule can report.A
LintCodeis the template for each diagnostic that is to be reported. It contains the diagnostic name, problem message, and optionally the correction message. We instantiate aLintCodeas 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 implementList<LintCode> get diagnosticCodesinstead ofLintCode get diagnosticCode. The rule can then declare the differentLintCodes in multiple static fields, which are referenced in thediagnosticCodesgetter. -
MyRule()- The rule class must have a constructor that callssuper(), 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 aLinterContextobject (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
NodeLintRegistry. Each 'visit' method found onSimpleAstVisitorhas a corresponding 'add' method in theNodeLintRegistryclass.
The visitor class
The visitor class contains the code that examines syntax nodes and reports
diagnostics. See the API documentation for the
SimpleAstVisitor class to find the various 'visit' methods available for
implementation. Let's look at a quick example:
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/src/lint/linter.dart';
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
final LinterContext context;
_Visitor(this.rule, this.context);
@override
void visitAwaitExpression(AwaitExpression node) {
if (context.isInLibDir) {
rule.reportLint(node);
}
}
}
Let's look at each declaration individually:
class _Visitor extends SimpleAstVisitor<void>- Each visitor must extendSimpleAstVisitor. 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 onSimpleAstVisitoris not important, as 'visit' return values are not used, sovoidis appropriate.final LintRule rule- The rule is the object to which we can report diagnostics (lints or warnings). Several methods are provided, all starting withreportLint. The different methods allow for different ranges of text to be highlighted.final LinterContext context- The LinterContext object provides various information about the library being analyzed. In this example, we make use of aisInLibDirutility._Visitor(...)- Often the constructor just initializes the LintRule and LinterContext fields. Other information can be initialized as well.void visitAwaitExpression(AwaitExpression node)- The main component of the_Visitorclass is the 'visit' methods. In this case,visitAwaitExpressionis 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.
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:
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.