pkg/analyzer_plugin/doc/tutorial/fixes.md
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.
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 FixContributors. 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 mix in the class DartFixesMixin, then the list of errors available
through the request object will only include the errors for which fixes should
be returned and the request will be an instance of DartFixesRequest, which
also has analysis results.
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.
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.
For example, your contributor might look something like the following:
class MyFixContributor extends Object
with FixContributorMixin
implements FixContributor {
static FixKind defineComponent =
FixKind('defineComponent', 100, "Define a component named {0}");
AnalysisSession get session => request.result.session;
@override
Future<void> computeFixesForError(AnalysisError error) async {
ErrorCode code = error.errorCode;
if (code == MyErrorCode.undefinedComponent) {
await _defineComponent(error);
await _useExistingComponent(error);
}
}
Future<void> _defineComponent(AnalysisError error) async {
// TODO Get the name from the source code.
String componentName = null;
ChangeBuilder builder = ChangeBuilder(session: session);
await changeBuilder.addDartFileEdit(path,
(DartFileEditBuilder fileEditBuilder) {
// TODO Build the edit to insert the definition of the component.
});
addFix(error, defineComponent, builder, args: [componentName]);
}
Future<void> _useExistingComponent(AnalysisError error) async {
// ...
}
}
Given a contributor like the one above, you can implement your plugin similar to the following:
class MyPlugin extends ServerPlugin with FixesMixin, DartFixesMixin {
// ...
@override
List<FixContributor> getFixContributors(String path) {
return <FixContributor>[MyFixContributor()];
}
}