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`. |