30a9f13794
Change-Id: I2c50f36111cc319a85ad1a09369bcbdda14416d8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419762 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
120 lines
5.2 KiB
Markdown
120 lines
5.2 KiB
Markdown
# Writing assists
|
|
|
|
This package gives analyzer plugin authors the ability to write "quick assists,"
|
|
which can make local changes to source code, like small refactorings, from a
|
|
developer's IDE. This document describes briefly how to write such an assist,
|
|
and how to register it in an analyzer plugin.
|
|
|
|
## The ResolvedCorrectionProducer class
|
|
|
|
A quick assist is specified by subclassing the ResolvedCorrectionProducer class.
|
|
Here is our example:
|
|
|
|
```dart
|
|
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
|
import 'package:analyzer_plugin/utilities/assist/assist.dart';
|
|
import 'package:analyzer_plugin/utilities/range_factory.dart';
|
|
|
|
class RemoveAwait extends ResolvedCorrectionProducer {
|
|
static const _removeAwaitKind = AssistKind(
|
|
'dart.assist.removeAwait', 30 /* default */, "Remove the 'await' keyword");
|
|
|
|
RemoveAwait({required super.context});
|
|
|
|
@override
|
|
CorrectionApplicability get applicability =>
|
|
CorrectionApplicability.singleLocation;
|
|
|
|
@override
|
|
AssistKind get assistKind => _removeAwaitKind;
|
|
|
|
@override
|
|
Future<void> compute(ChangeBuilder builder) async {
|
|
var awaitExpression = node;
|
|
if (awaitExpression is AwaitExpression) {
|
|
var awaitToken = awaitExpression.awaitKeyword;
|
|
await builder.addDartFileEdit(file, (builder) {
|
|
builder.addDeletion(range.startStart(awaitToken, awaitToken.next!));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Let's look at each declaration individually:
|
|
|
|
* `class RemoveAwait` - A quick assist is a class that extends
|
|
`ResolvedCorrectionProducer`. The name of the base class indicates that an
|
|
instance of this class can produce "corrections" (a set of edits) for a
|
|
resolved library.
|
|
* `static const _removeAwaitKind = AssistKind(...)` - Each quick assist must
|
|
have an associated `AssistKind` which has a unique `id`
|
|
(`'dart.assist.removeAwait'`), a priority (`DartFixKindPriority.standard` is a
|
|
fine default), and a message which is displayed in the IDE
|
|
(`"Remove the 'await' keyword"`).
|
|
* `RemoveAwait({required super.context});` - A standard constructor that accepts
|
|
a `CorrectionProducerContext` and passes it up to the super-constructor.
|
|
* `CorrectionApplicability get applicability =>` - the applicability field
|
|
describes how widely an assist can be applied safely and sensibly. Currently,
|
|
only `CorrectionApplicability.singleLocation` should be used.
|
|
* `AssistKind get assistKind => _removeAwaitKind;` - each instance of this class
|
|
can refer to the static field for it's `assistKind`.
|
|
* `Future<void> compute(ChangeBuilder builder)` - This method is called when the
|
|
client has requested quick assists at a specific location, and we want a
|
|
possible correction from this correction producer. This is the code that
|
|
looks at the `node` field and surrounding code and determines what correction
|
|
to offer, if any.
|
|
|
|
* `await builder.addDartFileEdit(...)` - Once we have determined that we want
|
|
to offer an assist, we call this method, and specify code deletions,
|
|
insertions, and/or replacements inside the callback function. If there are
|
|
cases where this correction producer will not offer any quick assists (such
|
|
as the source code having certain properties), then those cases should be
|
|
checked so that we don't call this method in such cases.
|
|
* `builder.addDeletion(...)` - For this assist (removing an `await` keyword),
|
|
we can use `addDeletion` to specify a range of source code text to delete.
|
|
The `DartFileEditBuilder` class has many utilities for adding various edits.
|
|
|
|
Writing a quick assist can be non-trivial, even for changes which are
|
|
conceptually simple. It may be helpful to see examples that are similar to a
|
|
desired assist. See the [assists that are offered by Dart Analysis
|
|
Server][existing-assists] for hundreds of examples.
|
|
|
|
Instances of the correction producer class are short-lived, and they can contain
|
|
state related to the source-code-under-analysis. Indeed, the
|
|
`CorrectionProducerContext`, which is passed into the constructor, and available
|
|
as a field in the super-class, contains information specific to the
|
|
code-under-analysis.
|
|
|
|
## Registering a quick assist
|
|
|
|
In order for a quick assist to be used in an analyzer plugin, it must be
|
|
registered. Register the quick assist's constructor inside a plugin's
|
|
`register` method:
|
|
|
|
```dart
|
|
import 'package:analysis_server_plugin/plugin.dart';
|
|
import 'package:analysis_server_plugin/registry.dart';
|
|
|
|
final plugin = SimplePlugin();
|
|
|
|
class SimplePlugin extends Plugin {
|
|
@override
|
|
void register(PluginRegistry registry) {
|
|
registry.registerAssist(RemoveAwait.new);
|
|
}
|
|
}
|
|
```
|
|
|
|
Instances of correction producers contain state related to the specific
|
|
source-code-under-analysis, which is why the constructor is given here,
|
|
instead of a long-lived instance.
|
|
|
|
See [writing a plugin][] for information about the `Plugin` class.
|
|
|
|
[writing rules]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
|
|
[existing-assists]: https://github.com/dart-lang/sdk/tree/main/pkg/analysis_server/lib/src/services/correction/dart
|
|
[writing a plugin]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
|