Closes https://github.com/dart-lang/sdk/pull/63006 GitOrigin-RevId: bb93c8af4d9d3f440ad12e66f5dfa58591943c41 Change-Id: I7a9c2e71bc90e22ea10e7a43a5cddda4ff91cb9d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491521 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
7.5 KiB
Writing rules
This package gives analyzer plugin authors the ability to write static rules for Dart 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 Dart syntax tree nodes that the visitor class needs to visit. Let's see an example:
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 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 astatic constfield to ensure a single instance exists — this is a functional requirement, not just convention. If multiple instances of the same code exist, the analysis server cannot properly match them, and users will be unable to suppress the diagnostic using// ignore:comments. Alternatively, the class can implement==andhashCode, but using astatic constfield is simpler and preferred.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 aRuleContextobject (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 onSimpleAstVisitorhas a corresponding 'add' method in theRuleVisitorRegistryclass.
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/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 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 AnalysisRule rule- The rule is the object to which we can report diagnostics (lints or warnings). Several methods are provided, all starting withreportAt. 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 aisInLibDirutility._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_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
String get name => 'Simple 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.