|
| 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 |
0 commit comments