mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
Initial documentation for the plugin package
R=devoncarew@google.com Review-Url: https://codereview.chromium.org/2973753003 .
This commit is contained in:
parent
6323bedc58
commit
0484f28335
7 changed files with 595 additions and 0 deletions
|
@ -4,6 +4,18 @@ A framework for building plugins for the analysis server.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
**Note:** The plugin support is not currently available for general use.
|
||||||
|
|
||||||
|
Plugins are written in Dart and are run in the same VM as the analysis server.
|
||||||
|
The analysis server runs each plugin in a separate isolate and communicates with
|
||||||
|
the plugin using a [plugin API][pluginapi]. This API is similar to the API used
|
||||||
|
by the analysis server to communicate with clients.
|
||||||
|
|
||||||
|
Plugins are automatically discovered and run by the analysis server.
|
||||||
|
|
||||||
|
This package contains support code to make it easier to write a plugin. There is
|
||||||
|
a [tutorial][tutorial] describing how to use the support in this package.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
Post issues and feature requests on the [issue tracker][issues].
|
Post issues and feature requests on the [issue tracker][issues].
|
||||||
|
@ -19,3 +31,4 @@ See the [LICENSE] file.
|
||||||
[LICENSE]: https://github.com/dart-lang/sdk/blob/master/pkg/analyzer/LICENSE
|
[LICENSE]: https://github.com/dart-lang/sdk/blob/master/pkg/analyzer/LICENSE
|
||||||
[list]: https://groups.google.com/a/dartlang.org/forum/#!forum/analyzer-discuss
|
[list]: https://groups.google.com/a/dartlang.org/forum/#!forum/analyzer-discuss
|
||||||
[pluginapi]: https://htmlpreview.github.io/?https://github.com/dart-lang/sdk/blob/master/pkg/analyzer_plugin/doc/api.html
|
[pluginapi]: https://htmlpreview.github.io/?https://github.com/dart-lang/sdk/blob/master/pkg/analyzer_plugin/doc/api.html
|
||||||
|
[tutorial]: doc/tutorial/tutorial.md
|
||||||
|
|
106
pkg/analyzer_plugin/doc/tutorial/assists.md
Normal file
106
pkg/analyzer_plugin/doc/tutorial/assists.md
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Providing Quick Assists
|
||||||
|
|
||||||
|
A quick assist is used by clients to provide a set of possible changes to code
|
||||||
|
that are based on the structure of the code. Quick assists are intended to help
|
||||||
|
users safely make local changes to code when those changes do not require any
|
||||||
|
user interaction. (Modifications that require interaction with users or that
|
||||||
|
touch multiple files are usually implemented as refactorings.)
|
||||||
|
|
||||||
|
For example, if the user has a function whose body consists of a single return
|
||||||
|
statement in a block, server will provide an assist to convert the function body
|
||||||
|
from a block to an expression (`=>`).
|
||||||
|
|
||||||
|
Assists have a priority associated with them. The priority allows the client to
|
||||||
|
display the assists that are most likely to be of use closer to the top of the
|
||||||
|
list when there are multiple assists available.
|
||||||
|
|
||||||
|
## Implementation details
|
||||||
|
|
||||||
|
When appropriate, the analysis server will send your plugin an `edit.getAssists`
|
||||||
|
request. The request includes the `file`, `offset` and `length` associated with
|
||||||
|
the selected region of code.
|
||||||
|
|
||||||
|
When an `edit.getAssists` request is received, the method `handleEditGetAssists`
|
||||||
|
will be invoked. This method is responsible for returning a response that
|
||||||
|
contains the available assists.
|
||||||
|
|
||||||
|
The easiest way to implement this method is by adding the classes `AssistsMixin`
|
||||||
|
and `DartAssistsMixin` (from `package:analyzer_plugin/plugin/assist_mixin.dart`)
|
||||||
|
to the list of mixins for your subclass of `ServerPlugin`. This will leave you
|
||||||
|
with one abstract method that you need to implement: `getAssistContributors`.
|
||||||
|
That method is responsible for returning a list of `AssistContributor`s. It is
|
||||||
|
the assist contributors that produce the actual assists. (Most plugins will only
|
||||||
|
need a single assist contributor.)
|
||||||
|
|
||||||
|
To write an assist contributor, create a class that implements
|
||||||
|
`AssistContributor`. The interface defines a single method named
|
||||||
|
`computeAssists`. The method has two arguments: an `AssistRequest` that
|
||||||
|
describes the location at which assists were requested and an `AssistCollector`
|
||||||
|
through which assists are to be added.
|
||||||
|
|
||||||
|
The class `AssistContributorMixin` defines a support method that makes it easier
|
||||||
|
to implement `computeAssists`.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Start by creating a class that implements `AssistContributor` and that mixes in
|
||||||
|
the class `AssistContributorMixin`, then implement the method `computeAssists`.
|
||||||
|
This method is typically implemented as a sequence of invocations of methods
|
||||||
|
that check to see whether a given assist is appropriate in the context of the
|
||||||
|
request
|
||||||
|
|
||||||
|
To learn about the support available for creating the edits, see
|
||||||
|
[Creating Edits][creatingEdits].
|
||||||
|
|
||||||
|
For example, your contributor might look something like the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyAssistContributor extends Object
|
||||||
|
with AssistContributorMixin
|
||||||
|
implements AssistContributor {
|
||||||
|
static AssistKind wrapInIf =
|
||||||
|
new AssistKind('wrapInIf', 100, "Wrap in an 'if' statement");
|
||||||
|
|
||||||
|
DartAssistRequest request;
|
||||||
|
|
||||||
|
AssistCollector collector;
|
||||||
|
|
||||||
|
AnalysisSession get session => request.result.session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void computeAssists(DartAssistRequest request, AssistCollector collector) {
|
||||||
|
this.request = request;
|
||||||
|
this.collector = collector;
|
||||||
|
_wrapInIf();
|
||||||
|
_wrapInWhile();
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void _wrapInIf() {
|
||||||
|
ChangeBuilder builder = new DartChangeBuilder(session);
|
||||||
|
// TODO Build the edit to wrap the selection in a 'if' statement.
|
||||||
|
addAssist(wrapInIf, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _wrapInWhile() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Given a contributor like the one above, you can implement your plugin similar to
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyPlugin extends ServerPlugin with AssistsMixin, DartAssistsMixin {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<AssistContributor> getAssistContributors(
|
||||||
|
covariant AnalysisDriver driver) {
|
||||||
|
return <AssistContributor>[new MyAssistContributor()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[creatingEdits]: creating_edits.md
|
81
pkg/analyzer_plugin/doc/tutorial/completion.md
Normal file
81
pkg/analyzer_plugin/doc/tutorial/completion.md
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# Providing Code Completions
|
||||||
|
|
||||||
|
A code completion is used by clients to provide a set of possible completions to
|
||||||
|
partially entered code. Completions are intended to address two use cases: to
|
||||||
|
help users enter code with less effort and to help users discover the behavior
|
||||||
|
of an object.
|
||||||
|
|
||||||
|
For example, if the user has typed `o.toSt` and then requested completions, one
|
||||||
|
suggestion might be `toString`.
|
||||||
|
|
||||||
|
That said, the completion suggestions that your plugin returns should include
|
||||||
|
all of the options that would be valid if the partial identifier did not exist.
|
||||||
|
The reason is that most clients are implemented such that they send a single
|
||||||
|
request for completions when the dialog with the user begins and cannot send any
|
||||||
|
subsequent requests. If the user presses the backspace key during the dialog the
|
||||||
|
client needs to have already received the expanded list of options that now
|
||||||
|
match the prefix (or all options if the prefix has completely been deleted).
|
||||||
|
Clients will filter the list of suggestions displayed as appropriate.
|
||||||
|
|
||||||
|
Hence, in the example above, plugins should return suggestions as if the user
|
||||||
|
had requested completions after typing `o.`;
|
||||||
|
|
||||||
|
## Implementation details
|
||||||
|
|
||||||
|
When appropriate, the analysis server will send your plugin a
|
||||||
|
`completion.getSuggestions` request. The request includes the `file` and
|
||||||
|
`offset` at which completions are being requested.
|
||||||
|
|
||||||
|
When a `completion.getSuggestions` request is received, the method
|
||||||
|
`handleCompletionGetSuggestions` will be invoked. This method is responsible for
|
||||||
|
returning a response that contains the available suggestions.
|
||||||
|
|
||||||
|
The easiest way to implement this method is by adding the classes
|
||||||
|
`CompletionMixin` and `DartCompletionMixin` (from
|
||||||
|
`package:analyzer_plugin/plugin/completion_mixin.dart`) to the list of mixins
|
||||||
|
for your subclass of `ServerPlugin`. This will leave you with one abstract
|
||||||
|
method that you need to implement: `getCompletionContributors`. That method is
|
||||||
|
responsible for returning a list of `CompletionContributor`s. It is the
|
||||||
|
completion contributors that produce the actual completion suggestions. (Most
|
||||||
|
plugins will only need a single completion contributor.)
|
||||||
|
|
||||||
|
To write a completion contributor, create a class that implements
|
||||||
|
`CompletionContributor`. The interface defines a single method named
|
||||||
|
`computeSuggestions`. The method has two arguments: a `CompletionRequest` that
|
||||||
|
describes the where completions are being requested and a `CompletionCollector`
|
||||||
|
through which suggestions are to be added.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Start by creating a class that implements `CompletionContributor`, then
|
||||||
|
implement the method `computeSuggestions`. Your contributor should invoke the
|
||||||
|
method `checkAborted`, defined on the `CompletionRequest` object, before
|
||||||
|
starting any slow work. This allows the computation of completion suggestions
|
||||||
|
to be preempted if the client no longer needs the results.
|
||||||
|
|
||||||
|
For example, your contributor might look something like the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyCompletionContributor implements CompletionContributor {
|
||||||
|
@override
|
||||||
|
Future<Null> computeSuggestions(covariant DartCompletionRequest request,
|
||||||
|
CompletionCollector collector) async {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Given a contributor like the one above, you can implement your plugin similar to
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyPlugin extends ServerPlugin with CompletionMixin, DartCompletionMixin {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<CompletionContributor> getCompletionContributors(
|
||||||
|
covariant AnalysisDriverGeneric driver) {
|
||||||
|
return <CompletionContributor>[new MyCompletionContributor()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
205
pkg/analyzer_plugin/doc/tutorial/creating_edits.md
Normal file
205
pkg/analyzer_plugin/doc/tutorial/creating_edits.md
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
# Creating `SourceChange`s
|
||||||
|
|
||||||
|
Several of the response objects take a `SourceChange` (specifically, assists,
|
||||||
|
fixes, and refactorings). Because `SourceChange` is a structured object that
|
||||||
|
can be difficult to create correctly, this package provides a set of utility
|
||||||
|
classes to help you build those structures.
|
||||||
|
|
||||||
|
Using these classes will not only simplify the work you need to do to implement
|
||||||
|
your plugin, but will ensure a consistent user experience in terms of the code
|
||||||
|
being generated by the analysis server.
|
||||||
|
|
||||||
|
## `DartChangeBuilder`
|
||||||
|
|
||||||
|
The class used to create a `SourceChange` is `DartChangeBuilder`, defined in
|
||||||
|
`package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart`.
|
||||||
|
You can create a `DartChangeBuilder` with the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
DartChangeBuilder changeBuilder = new DartChangeBuilder(session);
|
||||||
|
```
|
||||||
|
|
||||||
|
The constructor required an instance of the class `AnalysisSession`. How you get
|
||||||
|
the correct instance depends on where the constructor is being invoked.
|
||||||
|
|
||||||
|
A `SourceChange` can contain edits that are to be applied to multiple files. The
|
||||||
|
edits for a single file are created by invoking the method `addFileEdit`, as
|
||||||
|
illustrated by the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
changeBuilder.addFileEdit(path, (DartFileEditBuilder fileEditBuilder) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
where the `path` is the path to the file to which the edits will be applied.
|
||||||
|
|
||||||
|
## `DartFileEditBuilder`
|
||||||
|
|
||||||
|
The class `DartFileEditBuilder` defines methods for creating three kinds of
|
||||||
|
edits: deletions, insertions, and replacements.
|
||||||
|
|
||||||
|
For deletions, you pass in the range of code to be deleted as a `SourceRange`.
|
||||||
|
In addition to the constructor for `SourceRange`, there are a set of functions
|
||||||
|
defined in `package:analyzer_plugin/utilities/range_factory.dart` that can be
|
||||||
|
used to build a `SourceRange` from tokens, AST nodes, and elements.
|
||||||
|
|
||||||
|
For example, if you need to remove the text in a given `range`, you could write:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
fileEditBuilder.addDeletion(range);
|
||||||
|
```
|
||||||
|
|
||||||
|
In the case of insertions and replacements, there are two styles of method. The
|
||||||
|
first takes the string that is to be inserted; the second takes a closure in
|
||||||
|
which the string can be composed. Insertions take the offset of the insertion,
|
||||||
|
while replacements take a `SourceRange` indicating the location of the text to
|
||||||
|
be replaced.
|
||||||
|
|
||||||
|
For example, if you need to insert `text` at offset `offset`, you could write
|
||||||
|
|
||||||
|
```dart
|
||||||
|
fileEditBuilder.addSimpleInsertion(offset, text);
|
||||||
|
```
|
||||||
|
|
||||||
|
The forms that take a closure are useful primarily because they give you access
|
||||||
|
to a `DartEditBuilder`, which is described below.
|
||||||
|
|
||||||
|
For example, to replace a given `range` of text with some yet to be constructed
|
||||||
|
text, you could write:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, `DartFileEditBuilder` has some methods that allow you to build some
|
||||||
|
common sets of edits more easily. For example, `importLibraries` allows you to
|
||||||
|
pass in the `Source`s for one or more libraries and will create one or more
|
||||||
|
edits to insert `import` directives in the correct locations.
|
||||||
|
|
||||||
|
## `DartEditBuilder`
|
||||||
|
|
||||||
|
A `DartEditBuilder` allows you to compose source code by writing the individual
|
||||||
|
pieces, much like a `StringSink`. It also provides additional methods to compose
|
||||||
|
more complex code. For example, if you need to write a type annotation, the
|
||||||
|
method `writeType` will handle writing all of the type arguments and will add
|
||||||
|
import directives as needed. There are also methods to write class declarations
|
||||||
|
and to write various members within a class.
|
||||||
|
|
||||||
|
For example, if you're implementing a quick assist to insert a template for a
|
||||||
|
class declaration, the code to create the insertion edit could look like the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
String className = 'NewClass';
|
||||||
|
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) {
|
||||||
|
editBuilder.writeClassDeclaration(className, memberWriter: () {
|
||||||
|
editBuilder.writeConstructorDeclaration(className);
|
||||||
|
editBuilder.writeOverrideOfInheritedMember(
|
||||||
|
typeProvider.objectType.getMethod('toString'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linked Edits
|
||||||
|
|
||||||
|
Many clients support a style of editing in which multiple regions of text can be
|
||||||
|
edited simultaneously. Server refers to these as "linked" edit groups. Many
|
||||||
|
clients also support having multiple groups associated with the edits in a file
|
||||||
|
and allow users to tab from one group to the next. Essentially, these edit
|
||||||
|
groups mark placeholders for text that users might want to change after the
|
||||||
|
edits are applied.
|
||||||
|
|
||||||
|
The class `DartEditBuilder` provides support for creating linked edits through
|
||||||
|
the method `addLinkedEdit`. As with the insertion and replacement methods
|
||||||
|
provided by `DartFileEditBuilder` (see above), there are both a "simple" and a
|
||||||
|
closure-based version of this method.
|
||||||
|
|
||||||
|
For example, if you're implementing a quick assist to insert a for loop, you
|
||||||
|
should add the places where the loop variable name appears to a linked edit
|
||||||
|
group. You should also add the name of the list being iterated over to a
|
||||||
|
different group. The code to create the insertion edit could look like the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) {
|
||||||
|
String listName = 'list';
|
||||||
|
String listGroup = 'list_variable';
|
||||||
|
String variableName = 'i';
|
||||||
|
String variableGroup = 'loop_variable';
|
||||||
|
|
||||||
|
editBuilder.write('for (int ');
|
||||||
|
editBuilder.addSimpleLinkedEdit(variableGroup, variableName);
|
||||||
|
editBuilder.write(' = 0; ');
|
||||||
|
editBuilder.addSimpleLinkedEdit(variableGroup, variableName);
|
||||||
|
editBuilder.write(' < ');
|
||||||
|
editBuilder.addSimpleLinkedEdit(listGroup, listName);
|
||||||
|
editBuilder.write('.length; ');
|
||||||
|
editBuilder.addSimpleLinkedEdit(variableGroup, variableName);
|
||||||
|
editBuilder.write('++) {}');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
One of the advantages of the closure-based form of `addLinkedEdit` is that you
|
||||||
|
can specify suggested replacements for the values of each group. You do that by
|
||||||
|
invoking either `addSuggestion` or `addSuggestions`. In the example above, you
|
||||||
|
might choose to suggest `j` and `k` as other likely loop variable names. You
|
||||||
|
could do that by replacing one of the places where the variable name is written
|
||||||
|
with code like the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
editBuilder.addLinkedEdit(variableGroup, (LinkedEditBuilder linkedEditBuilder) {
|
||||||
|
linkedEditBuilder.write(variableName);
|
||||||
|
linkedEditBuilder.addSuggestions(['j', 'k']);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
A more interesting use of this feature would be to find the names of all of the
|
||||||
|
list-valued variables within scope and suggest those names as alternatives for
|
||||||
|
the name of the list.
|
||||||
|
|
||||||
|
That said, most of the methods on `DartEditBuilder` that help you generate Dart
|
||||||
|
code take one or more optional arguments that allow you to create linked edit
|
||||||
|
groups for appropriate pieces of text and even to specify the suggestions for
|
||||||
|
those groups.
|
||||||
|
|
||||||
|
## Post-edit Selection
|
||||||
|
|
||||||
|
A `SourceChange` also allows you to specify where the cursor should be placed
|
||||||
|
after the edits are applied. There are two ways to specify this.
|
||||||
|
|
||||||
|
The first is by invoking the method `setSelection` on a `DartChangeBuilder`.
|
||||||
|
The method takes a `Position`, which encapsulates an offset in a particular
|
||||||
|
file. This can be difficult to get right because the offset is required to be
|
||||||
|
the offset *after* all of the edits for that file have been applied.
|
||||||
|
|
||||||
|
The second, and easier, way is by invoking the method `selectHere` on a
|
||||||
|
`DartEditBuilder`. This method does not require any arguments; it computes the
|
||||||
|
offset for the position based on the edits that have previously been created.
|
||||||
|
It does require that all of the edits that apply to text before the desired
|
||||||
|
cursor location have been created before the method is invoked.
|
||||||
|
|
||||||
|
For example, if you're implementing a quick assist to insert a to-do comment at
|
||||||
|
the cursor location, the code to create the insertion edit could look like the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
fileEditBuilder.addReplacement(range, (DartEditBuilder editBuilder) {
|
||||||
|
editBuilder.write('/* TODO ');
|
||||||
|
editBuilder.selectHere();
|
||||||
|
editBuilder.write(' */');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will cause the cursor to be placed between the two spaces inside the
|
||||||
|
comment.
|
||||||
|
|
||||||
|
## Non-Dart Files
|
||||||
|
|
||||||
|
All of the classes above are subclasses of more general classes (just drop the
|
||||||
|
prefix "Dart" from the subclass names). If you are editing files that do not
|
||||||
|
contain Dart code, the more general classes might be a better choice. These
|
||||||
|
classes are defined in
|
||||||
|
`package:analyzer_plugin/utilities/change_builder/change_builder_core.dart`.
|
121
pkg/analyzer_plugin/doc/tutorial/fixes.md
Normal file
121
pkg/analyzer_plugin/doc/tutorial/fixes.md
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
# Providing Quick Fixes
|
||||||
|
|
||||||
|
A quick fix is used by clients to provide a set of possible changes to code that
|
||||||
|
are based on diagnostics reported against the code. Quick fixes are intended to
|
||||||
|
help users resolve the issue being reported.
|
||||||
|
|
||||||
|
If your plugin generates any diagnostics then you should consider providing
|
||||||
|
support for automatically fixing those diagnostics. There is often more than one
|
||||||
|
potential way of fixing a given problem, so it is possible for your plugin to
|
||||||
|
provide multiple fixes for a single problem.
|
||||||
|
|
||||||
|
For example, if an undefined identifier is used in the code, you might return
|
||||||
|
a fix to create an appropriate definition for the identifier. If there is a
|
||||||
|
similar identifier that is already defined, you might also return a second fix
|
||||||
|
to replace the undefined identifier with the defined identifier.
|
||||||
|
|
||||||
|
The latter example illustrates that fixes can be conditionally returned. You
|
||||||
|
will produce a better UX if only those fixes that actually make sense in the
|
||||||
|
given context are returned. If a lot of work is required to determine which
|
||||||
|
fixes make sense, it is possible to improve performance by generating different
|
||||||
|
diagnostics for the same issue, depending on the context in which the issue
|
||||||
|
occurs.
|
||||||
|
|
||||||
|
In addition, fixes have a priority associated with them. The priority allows the
|
||||||
|
client to display the fixes that are most likely to be of use closer to the top
|
||||||
|
of the list when there are multiple fixes available.
|
||||||
|
|
||||||
|
## Implementation details
|
||||||
|
|
||||||
|
When appropriate, the analysis server will send your plugin an `edit.getFixes`
|
||||||
|
request. The request includes the `file` and `offset` associated with the
|
||||||
|
diagnostics for which fixes should be generated. Fixes are typically produced
|
||||||
|
for all of the diagnostics on a given line of code. Your plugin should only
|
||||||
|
return fixes associated with the errors that it produced earlier.
|
||||||
|
|
||||||
|
When an `edit.getFixes` request is received, the method `handleEditGetFixes`
|
||||||
|
will be invoked. This method is responsible for returning a response that
|
||||||
|
contains the available fixes.
|
||||||
|
|
||||||
|
The easiest way to implement this method is by adding the classes `FixesMixin`
|
||||||
|
and `DartFixesMixin` (from `package:analyzer_plugin/plugin/fix_mixin.dart`) to
|
||||||
|
the list of mixins for your subclass of `ServerPlugin`. This will leave you with
|
||||||
|
one abstract method that you need to implement: `getFixContributors`. That
|
||||||
|
method is responsible for returning a list of `FixContributor`s. It is the fix
|
||||||
|
contributors that produce the actual fixes. (Most plugins will only need a
|
||||||
|
single fix contributor.)
|
||||||
|
|
||||||
|
To write a fix contributor, create a class that implements `FixContributor`. The
|
||||||
|
interface defines a single method named `computeFixes`. The method has two
|
||||||
|
arguments: a `FixesRequest` that describes the errors that should be fixed and a
|
||||||
|
`FixCollector` through which fixes are to be added. (If you use the mixins above
|
||||||
|
then the list of errors available through the request object will only include
|
||||||
|
the errors for which fixes should be returned.)
|
||||||
|
|
||||||
|
The class `FixContributorMixin` defines a simple implementation of this method
|
||||||
|
that captures the two arguments in fields, iterates through the errors, and
|
||||||
|
invokes a method named `computeFixesForError` for each of the errors for which
|
||||||
|
fixes are to be computed.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Start by creating a class that implements `FixContributor` and that mixes in the
|
||||||
|
class `FixContributorMixin`, then implement the method `computeFixesForError`.
|
||||||
|
This method is typically implemented by a series of `if` statements that test
|
||||||
|
the error code and invoke individual methods that compute the actual fixes to be
|
||||||
|
proposed. (In addition to keeping the method `computeFixesForError` shorter,
|
||||||
|
this also allows some fixes to be used for multiple error codes.)
|
||||||
|
|
||||||
|
To learn about the support available for creating the edits, see
|
||||||
|
[Creating Edits][creatingEdits].
|
||||||
|
|
||||||
|
For example, your contributor might look something like the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyFixContributor extends Object
|
||||||
|
with FixContributorMixin
|
||||||
|
implements FixContributor {
|
||||||
|
static FixKind defineComponent =
|
||||||
|
new FixKind('defineComponent', 100, "Define a component named {0}");
|
||||||
|
|
||||||
|
AnalysisSession get session => request.result.session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void computeFixesForError(AnalysisError error) {
|
||||||
|
ErrorCode code = error.errorCode;
|
||||||
|
if (code == MyErrorCode.undefinedComponent) {
|
||||||
|
_defineComponent(error);
|
||||||
|
_useExistingComponent(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _defineComponent(AnalysisError error) {
|
||||||
|
// TODO Get the name from the source code.
|
||||||
|
String componentName = null;
|
||||||
|
ChangeBuilder builder = new DartChangeBuilder(session);
|
||||||
|
// TODO Build the edit to insert the definition of the component.
|
||||||
|
addFix(error, defineComponent, builder, args: [componentName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _useExistingComponent(AnalysisError error) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Given a contributor like the one above, you can implement your plugin similar to
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyPlugin extends ServerPlugin with FixesMixin, DartFixesMixin {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<FixContributor> getFixContributors(
|
||||||
|
covariant AnalysisDriverGeneric driver) {
|
||||||
|
return <FixContributor>[new MyFixContributor()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[creatingEdits]: creating_edits.md
|
39
pkg/analyzer_plugin/doc/tutorial/getting_started.md
Normal file
39
pkg/analyzer_plugin/doc/tutorial/getting_started.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Creating a Minimal Plugin
|
||||||
|
|
||||||
|
To implement a plugin, start by creating a simple package and create a class
|
||||||
|
that is a subclass of `ServerPlugin`. This class will need to implement a
|
||||||
|
constructor, three getters, and two methods. The getters provide some basic
|
||||||
|
information about your plugin: the name and version, both of which are included
|
||||||
|
in error messages if there is a problem encountered, and a list of glob patterns
|
||||||
|
for the files that the plugin cares about. The methods ...
|
||||||
|
|
||||||
|
Here's an example of what a minimal plugin might look like.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyPlugin extends ServerPlugin {
|
||||||
|
MyPlugin(ResourceProvider provider) : super(provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get fileGlobsToAnalyze => <String>['**/*.dart'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'My fantastic plugin';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get version => '1.0.0';
|
||||||
|
|
||||||
|
@override
|
||||||
|
AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) {
|
||||||
|
// TODO: implement createAnalysisDriver
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendNotificationsForSubscriptions(
|
||||||
|
Map<String, List<AnalysisService>> subscriptions) {
|
||||||
|
// TODO: implement sendNotificationsForSubscriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
30
pkg/analyzer_plugin/doc/tutorial/tutorial.md
Normal file
30
pkg/analyzer_plugin/doc/tutorial/tutorial.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Building a Plugin
|
||||||
|
|
||||||
|
This is the introduction page to a set of pages that describe how to implement a
|
||||||
|
plugin. You should probably read the [Getting Started][gettingStarted] page
|
||||||
|
first, but there is no specific order for the remaining pages.
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
The following is a list of the pages available in this tutorial.
|
||||||
|
|
||||||
|
[Getting Started][gettingStarted] -
|
||||||
|
How to write a minimal plugin.
|
||||||
|
|
||||||
|
[Creating Edits][creatingEdits] -
|
||||||
|
How to compose the edits used in assists, fixes, and refactorings.
|
||||||
|
|
||||||
|
[Providing Quick Assists][assists] -
|
||||||
|
How to provide quick assists.
|
||||||
|
|
||||||
|
[Providing Quick Fixes][fixes] -
|
||||||
|
How to provide quick fixes associated with errors.
|
||||||
|
|
||||||
|
[Providing Code Completions][completion] -
|
||||||
|
How to provide code completion suggestions.
|
||||||
|
|
||||||
|
[assists]: assists.md
|
||||||
|
[completion]: completion.md
|
||||||
|
[creatingEdits]: creating_edits.md
|
||||||
|
[fixes]: fixes.md
|
||||||
|
[gettingStarted]: getting_started.md
|
Loading…
Reference in a new issue