Skip to content

Commit bfaf70f

Browse files
srawlinsCommit Queue
authored andcommitted
DAS plugins: Document writing quick fixes
Change-Id: I38dc64d8ff9f59dd9e45d30715a08233f4891b74 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410563 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 2048190 commit bfaf70f

File tree

3 files changed

+157
-6
lines changed

3 files changed

+157
-6
lines changed

pkg/analysis_server_plugin/doc/writing_a_plugin.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Here we have a class, `SimplePlugin`, which extends the `Plugin` class from the
4646
`analysis_server_plugin` package. This class has one method that we override:
4747
`register`. In the `register` method, we can register analysis rules and quick
4848
fixes (CorrectionProducers). See details in the [writing rules][] doc, and the
49-
writing quick fixes doc (TODO).
49+
[writing fixes][] doc.
5050

5151
Additionally, we provide a top-level variable in this file called `plugin`,
5252
which is an instance of our `SimplePlugin` class. When a running instance of
@@ -64,4 +64,5 @@ plugin isolate has crashed, the "plugins" screen will display the crash.
6464
can help in debugging plugin code.
6565

6666
[writing rules]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
67+
[writing fixes]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_fixes.md
6768
[analyzer diagnostics pages]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server/doc/tutorial/instrumentation.md#open-the-analyzer-diagnostics-pages
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Writing fixes
2+
3+
This package gives analyzer plugin authors the ability to write "quick fixes"
4+
targeted at specific diagnostics, that can fix source code from a developer's
5+
IDE. This document describes briefly how to write such a fix, and how to
6+
register it in an analyzer plugin.
7+
8+
## The analysis rule
9+
10+
A quick fix must be associated with a diagnostic in order to be presented to a
11+
devloper in their IDE. (There is a separate feature calls "assists" which do
12+
not need to be associated with diagnostics; analyzer plugins can't currently
13+
offer assists.)
14+
15+
For this guide, we will be using the "MyRule" rule from the [writing rules][]
16+
guide.
17+
18+
19+
## The ResolvedCorrectionProducer class
20+
21+
A quick fix is specified by subclassing the ResolvedCorrectionProducer class.
22+
Here is our example:
23+
24+
```dart
25+
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
26+
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart';
27+
import 'package:analyzer/dart/ast/ast.dart';
28+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
29+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
30+
import 'package:analyzer_plugin/utilities/range_factory.dart';
31+
32+
class RemoveAwait extends ResolvedCorrectionProducer {
33+
static const _removeAwaitKind = FixKind(
34+
'dart.fix.moveBelowEnclosingTestCall',
35+
DartFixKindPriority.standard,
36+
"Move below the enclosing 'test' call");
37+
38+
RemoveAwait({required super.context});
39+
40+
@override
41+
CorrectionApplicability get applicability =>
42+
CorrectionApplicability.singleLocation;
43+
44+
@override
45+
FixKind get fixKind => _removeAwaitKind;
46+
47+
@override
48+
Future<void> compute(ChangeBuilder builder) async {
49+
var awaitExpression = node;
50+
if (awaitExpression is AwaitExpression) {
51+
var awaitToken = awaitExpression.awaitKeyword;
52+
await builder.addDartFileEdit(file, (builder) {
53+
builder.addDeletion(range.startStart(awaitToken, awaitToken.next!));
54+
});
55+
}
56+
}
57+
}
58+
```
59+
60+
Let's look at each declaration individually:
61+
62+
* `class RemoveAwait` - A quick fix is a class that extends
63+
`ResolvedCorrectionProducer`. The name of the base class indicates that an
64+
instance of this class can produce "corrections" (a set of edits) for a
65+
resolved library.
66+
* `static const _removeAwaitKind = FixKind(...)` - Each quick fix must have an
67+
associated `FixKind` which has a unique `id`
68+
(`'dart.fix.moveBelowEnclosingTestCall'`), a priority
69+
(`DartFixKindPriority.standard` is a fine default), and a message which is
70+
displayed in the IDE (`"Move below the enclosing 'test' call"`).
71+
* `RemoveAwait({required super.context});` - A standard constructor that
72+
accepts a `CorrectionProducerContext` and passes it up to the
73+
super-constructor.
74+
* `CorrectionApplicability get applicability =>` - the applicability field
75+
describes how widely a fix can be applied safely and sensibly. Currently,
76+
fixes registered in plugins cannot be applied in bulk, so only
77+
`CorrectionApplicability.singleLocation` and
78+
`CorrectionApplicability.acrossSingleFile` should be used.
79+
* `FixKind get fixKind => _removeAwaitKind;` - each instance of this class can
80+
refer to the static field for it's `fixKind`.
81+
* `Future<void> compute(ChangeBuilder builder)` - This method is called when an
82+
associated diagnostic (lint or warning) has been reported, and we want a
83+
possible correction from this correction producer. This is the code that
84+
looks at the error node (the `node` field) and surrounding code and
85+
determines what correction to offer, if any.
86+
87+
* `await builder.addDartFileEdit(...)` - Once we have determined that we want
88+
to offer a fix, we call this method, and specify code deletions,
89+
insertions, and/or replacements inside the callback function. If there are
90+
cases where this correction producer will not offer any quick fixes (such
91+
as the source code having certain properties), then those cases should be
92+
checked so that we don't call this method in such cases.
93+
* `builder.addDeletion(...)` - For this fix (removing an `await` keyword), we
94+
can use `addDeletion` to specify a range of source code text to delete. The
95+
`DartFileEditBuilder` class has many utilities for adding various edits.
96+
97+
Writing a quick fix can be non-trivial, even for changes which are
98+
conceptually simple. It may be helpful to see examples that are similar to a
99+
desired fix. See the [fixes that are offered by Dart Analysis
100+
Server][existing-fixes] for hundreds of examples.
101+
102+
Instances of the correction producer class are short-lived, and they can
103+
contain state related to the specific reported diagnostic and
104+
code-under-analysis. Indeed, the `CorrectionProducerContext`, which is passed
105+
into the constructor, and available as a field in the super-class, contains
106+
information specific to the code-under-analysis and the diagnostic.
107+
108+
## Registering a quick fix
109+
110+
In order for a quick fix to be used in an analyzer plugin, it must be
111+
registered. Register the quick fix's constructor inside a plugin's
112+
`register` method:
113+
114+
```dart
115+
import 'package:analysis_server_plugin/plugin.dart';
116+
import 'package:analysis_server_plugin/registry.dart';
117+
118+
final plugin = SimplePlugin();
119+
120+
class SimplePlugin extends Plugin {
121+
@override
122+
void register(PluginRegistry registry) {
123+
registry.registerWarningRule(MyRule());
124+
registry.registerFixForRule(MyRule.code, RemoveAwait.new);
125+
}
126+
}
127+
```
128+
129+
Here, the warning (`MyRule.code`) is associated with the fix (the constructor
130+
for the`RemoveAwait` class), in `registry.registerFixForRule`. Instances of
131+
correction producers contain state related to the specific reported diagnostic
132+
and the code-under-analysis, which is why the constructor is given here,
133+
instead of a long-lived instance.
134+
135+
See [writing a plugin][] for information about the `Plugin` class.
136+
137+
[writing rules]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
138+
[existing-fixes]: https://github.com/dart-lang/sdk/tree/main/pkg/analysis_server/lib/src/services/correction/dart
139+
[writing a plugin]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md

pkg/analysis_server_plugin/doc/writing_rules.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Writing Rules
1+
# Writing rules
22

33
This package gives analyzer plugin authors the ability to write static rules
44
for source code. This document describes briefly how to write such a rule, and
@@ -17,8 +17,12 @@ various syntax tree nodes that the visitor class needs to visit. Let's see an
1717
example:
1818

1919
```dart
20+
import 'package:analyzer/dart/ast/ast.dart';
21+
import 'package:analyzer/src/dart/error/lint_codes.dart';
22+
import 'package:analyzer/src/lint/linter.dart';
23+
2024
class MyRule extends AnalysisRule {
21-
static const LintCode _code = LintCode(
25+
static const LintCode code = LintCode(
2226
'my_rule',
2327
'No await expressions',
2428
correctionMessage: "Try removing 'await'.",
@@ -31,7 +35,7 @@ class MyRule extends AnalysisRule {
3135
);
3236
3337
@override
34-
LintCode get lintCode => _code;
38+
LintCode get lintCode => code;
3539
3640
@override
3741
void registerNodeProcessors(
@@ -86,6 +90,10 @@ implementation. Let's look at a quick example:
8690
[SimpleAstVisitor docs]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/lib/dart/ast/visitor.dart#L1841
8791

8892
```dart
93+
import 'package:analyzer/dart/ast/ast.dart';
94+
import 'package:analyzer/dart/ast/visitor.dart';
95+
import 'package:analyzer/src/lint/linter.dart';
96+
8997
class _Visitor extends SimpleAstVisitor<void> {
9098
final LintRule rule;
9199
@@ -138,6 +146,9 @@ registered. Register an instance of an analysis rule inside a plugin's
138146
`register` method:
139147

140148
```dart
149+
import 'package:analysis_server_plugin/plugin.dart';
150+
import 'package:analysis_server_plugin/registry.dart';
151+
141152
class SimplePlugin extends Plugin {
142153
@override
143154
void register(PluginRegistry registry) {
@@ -151,6 +162,6 @@ enabled by default. To register an analysis rule as a "lint rule," such that it
151162
must be specifically enabled from analysis options, use `registerLintRule`
152163
instead.
153164

154-
See [writing a plugin][] for for information about the `Plugin` class.
165+
See [writing a plugin][] for information about the `Plugin` class.
155166

156-
[writing a plugin]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
167+
[writing a plugin]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md

0 commit comments

Comments
 (0)