# 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. ## `ChangeBuilder` The class used to create a `SourceChange` is `ChangeBuilder`, defined in `package:analyzer_plugin/utilities/change_builder/change_builder_core.dart`. You can create a `ChangeBuilder` with the following: ```dart ChangeBuilder changeBuilder = new ChangeBuilder(session: session); ``` The constructor requires 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 `addDartFileEdit`, as illustrated by the following: ```dart await changeBuilder.addDartFileEdit(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 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.writeOverride( typeProvider.objectType.getMethod('toString').type); }); }); ``` ## Utility methods As was mentioned briefly above, many of these classes provide utility methods. For the best UX, it's important to use these utility method when possible. They often do more than just write some simple text; they can take care of other details, such as adding required imports, and adherence to style preferences that the user has expressed (typically by enabling lints). ## 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 `ChangeBuilder`. 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 the edits for the 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 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 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`.