| Index: pkg/analyzer_plugin/doc/tutorial/creating_edits.md
|
| diff --git a/pkg/analyzer_plugin/doc/tutorial/creating_edits.md b/pkg/analyzer_plugin/doc/tutorial/creating_edits.md
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9fc69088817130338ab157e8175e59d0f4b670bc
|
| --- /dev/null
|
| +++ b/pkg/analyzer_plugin/doc/tutorial/creating_edits.md
|
| @@ -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`.
|
|
|