Add documentation about creating an assist

This was copied from quick fixes and then modified, so please also look
for places that I missed updating the text.

Change-Id: Ib67865232e9af2194999553a8007911f1e691fae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251140
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/doc/tutorial/quick_assist.md b/pkg/analysis_server/doc/tutorial/quick_assist.md
new file mode 100644
index 0000000..b42dab9
--- /dev/null
+++ b/pkg/analysis_server/doc/tutorial/quick_assist.md
@@ -0,0 +1,337 @@
+# Writing a quick assist
+
+This document describes what a quick assist is and outlines the basic steps for
+writing a quick assist.
+
+## Overview
+
+A quick assist is an automated code edit that is both local in scope and doesn't
+require any user input. By 'local in scope' we mean that the assist only needs
+information from the local library (the library in which it is invoked) and will
+only make changes to the local library. If a code edit requires user input,
+might depend on knowledge from outside the local library, or could make changes
+outside the local library, then it should probably be implemented using a
+refactoring.
+
+Unlike quick fixes, quick assists are displayed even when there are no
+diagnostics being reported against the code. They are not intended to fix
+problems that are being reported, but are used to perform common and
+straightforward code transformations.
+
+In this document we'll use a simple example of writing an assist to convert a
+decimal representation of an integer into a hexadecimal representation. While
+this wouldn't be a good assist to offer, it's simple enough that the details of
+the implementation won't mask the general process used to implement an assist.
+
+## Design considerations
+
+Because quick assists are computed on demand, they need to be computed quickly.
+(Some clients request quick assists every time the user repositions the cursor.)
+That places a performance requirement on quick assists, one that requires that
+the code to compute a quick assist can't perform any potentially lengthy
+computations such as searching all of the user's code or accessing the network.
+That, in turn, generally means that assists can only support localized changes.
+They can add or remove text in the local library, but generally can't do more
+than that.
+
+Unlike quick fixes, there is no signal to indicate which assists might apply at
+a given location in the code. That means that we have to test every assist to
+see whether it's appropriate, which puts a practical limit on the number of
+assists that we can implement. (Even if each assist only takes 100 milliseconds
+to determine whether it applies, if we have 100 assists it will take 10 seconds
+to return the list of assists to the user, which is too slow.) That means that
+we need to be discerning about which assists are implemented and work to make
+assists return quickly if they are not appropriate.
+
+## Describing the assist
+
+Each assist has an instance of the class `AssistKind` associated with it. The
+existing assists for Dart are defined in the class `DartAssistKind`. An
+assist kind has an identifier, a priority, and a message.
+
+The identifier is used by some LSP-based clients to provide user-defined
+shortcuts. It's a hierarchical dot-separated identifier and should follow the
+pattern seen in the existing assist kinds.
+
+The priority is used to order the list of assists when presented to the user.
+The larger the value the closer to the top of the list it will appear. If you're
+implementing an assist for Dart files, you should use one of the constants
+defined in `DartAssistKindPriority` (typically
+`DartAssistKindPriority.DEFAULT`), or add a new constant if there's a need for
+it.
+
+The message is what will be displayed to the user by the client. This should be
+a sentence fragment (no terminating period) that would be appropriate as a label
+in a menu or on a button. It should describe the change that will be made to the
+user's code.
+
+Create a static field whose value is an `AssistKind` describing the assist
+you're implementing. For our example you might define a new constant in
+`DartAssistKind` like this:
+
+```dart
+static const CONVERT_TO_HEX = AssistKind(
+  'dart.assist.convert.toHex',
+  DartAssistKindPriority.DEFAULT,
+  "Convert to hexadecimal",
+);
+```
+
+### Implementing the assist, part 1
+
+To implement the assist you'll create a subclass of `CorrectionProducer`. Most
+of the existing correction producers are in the directory
+`analysis_server/lib/src/services/correction/dart`, so we'll start by creating
+a file named `convert_to_hex.dart` in that directory that contains the following
+(with the year updated appropriately):
+
+```dart
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/assist/assist.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class ConvertToHex extends CorrectionProducer {
+  @override
+  AssistKind get assistKind => DartAssistKind.CONVERT_TO_HEX;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+  }
+}
+```
+
+The `compute` method is where the assist will be built. We'll come back to it in
+"Implementing the assist, part 2".
+
+The `assistKind` getter is how you associate the assist kind we created earlier
+with the assist produced by the `compute` method.
+
+There's another getter you might need to override. The message associated with
+the assist kind is actually a template that can be filled in at runtime. The
+placeholders in the message are denoted by integers inside curly braces (such as
+`{0}`). The integers are indexes into a list of replacement values (hence, zero
+based), and the getter `assistArguments` returns the list of replacement values.
+The message we used above doesn't have any placeholders, but if we'd written the
+message as `"Convert to '{0}'"`, then we could return the replacement values by
+implementing:
+
+```dart
+String _hexRepresentation = '';
+
+@override
+List<Object> get assistArguments => [_hexRepresentation];
+```
+
+and assigning the replacement string to the field inside the `compute` method.
+
+If you don't implement this getter, then the inherited getter will return an
+empty list. The number of elements in the list must match the largest index used
+in the message. If it doesn't, an exception will be thrown at runtime.
+
+### Testing the assist
+
+Before we look at implementing the `compute` method, we should probably write
+some tests. Even if you don't normally use a test-driven approach to coding, we
+recommend it in this case because writing the tests can help you think of corner
+cases that the implementation will need to handle. The corresponding tests are
+in the directory `analysis_server/test/src/services/correction/assist`, so we'll
+create a file named `convert_to_hex_test.dart` that contains the following:
+
+```dart
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analyzer_plugin/utilities/assist/assist.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'assist_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConvertToHexTest);
+  });
+}
+
+@reflectiveTest
+class ConvertToHexTest extends AssistProcessorTest {
+  @override
+  AssistKind get kind => DartAssistKind.CONVERT_TO_HEX;
+}
+```
+
+This getter tells the test framework to expect an assist of the returned kind.
+
+The test can then be written in a method that looks something like this:
+
+```dart
+Future<void> test_positive() async {
+  await resolveTestCode('''
+var c = /*caret*/42;
+''');
+  await assertHasAssist('''
+var c = 0x2a;
+''');
+}
+```
+
+The test framework will look for the marker `/*caret*/`, remember it's offset,
+create a file containing the first piece of code with the marker removed, run
+the assist over the code, use our correction producer to build an edit, apply
+the edit to the file, and textually compare the results with the second piece of
+code.
+
+### Registering the assist
+
+Before we can run the test, we need to register the correction producer so that
+it can be run.
+
+The list of assists is computed by the `AssistProcessor`, which has two static
+lists named `generators` and `multiGenerators` that contain the correction
+producers used to compute the assists.
+
+Actually, the lists contain functions used to create the producers. We do that
+so that producers can't accidentally carry state over from one use to the next.
+These functions are usually a tear-off of the correction producer's constructor.
+
+The last step is to add your correction producer to the list named `generators`.
+We'll talk about the other list in "Multi-assist producers".
+
+At this point you should be able to run the test and see it failing.
+
+### Implementing the assist, part 2
+
+We're now at a point where we can finish the implementation of the assist by
+implementing the `compute` method.
+
+The correction producer has access to most of the information you should need in
+order to write the assist. The change builder passed to `compute` is how you
+construct the edit that will be sent back to the client.
+
+The first step in the implementation of any assist is to find the location in
+the AST where the cursor is positioned and verify that all the conditions on
+which the assist is predicated are valid. For example, for our assist we'll need
+to ensure that the cursor is on an integer literal, that the literal consists of
+decimal digits, and that the value is a valid integer.
+
+Finding the AST node is easy because it's done for you by the assist processor
+before `compute` is invoked. All you have to do is use the getter `node` to find
+the node at the cursor.
+
+```dart
+@override
+Future<void> compute(ChangeBuilder builder) async {
+  var node = this.node;
+}
+```
+
+Then we need to verify that this node is an integer literal:
+
+```dart
+@override
+Future<void> compute(ChangeBuilder builder) async {
+  var node = this.node;
+  if (node is! IntegerLiteral) {
+    return;
+  }
+}
+```
+
+If it isn't, then we'll return. Because we haven't used the builder to create a
+assist, returning now means that no assist from this producer will be sent to
+the client.
+
+We'll also check that the integer has the right form and is valid:
+
+```dart
+@override
+Future<void> compute(ChangeBuilder builder) async {
+  var node = this.node;
+  if (node is! IntegerLiteral) {
+    return;
+  }
+  var value = node.value;
+  if (value == null) {
+    return;
+  }
+  if (node.literal.lexeme.contains(RegExp('[^0-9]'))) {
+    return;
+  }
+}
+```
+
+After all those checks we now know that we have a decimal integer that we can
+convert. Note that we check for a `null` value before checking for non-decimal
+digits because it's a faster check and we want the assist to fail as quickly as
+possible.
+
+We're now ready to create the edit. To do that we're going to use the
+`ChangeBuilder` passed to the `compute` method. In the example below we'll
+introduce a couple of the methods on `ChangeBuilder`, but for more information
+you can read [Creating `SourceChange`s](https://github.com/dart-lang/sdk/blob/main/pkg/analyzer_plugin/doc/tutorial/creating_edits.md).
+
+```dart
+@override
+Future<void> compute(ChangeBuilder builder) async {
+  var node = this.node;
+  if (node is! IntegerLiteral) {
+    return;
+  }
+  var value = node.value;
+  if (value == null) {
+    return;
+  }
+  if (node.literal.lexeme.contains(RegExp('[^0-9]'))) {
+    return;
+  }
+  await builder.addDartFileEdit(file, (builder) {
+    var hexDigits = value.toRadixString(16);
+    builder.addSimpleReplacement(range.node(node), '0x$hexDigits');
+  });
+}
+```
+
+We're using `addDartFileEdit` to create an edit in a `.dart` file. In this case
+the edit is simple: we're just replacing one representation of the integer
+literal with a representation of the same value in a different base. The getter
+`range` returns a `RangeFactory`, a utility class with lots of methods to make
+it easier to create `SourceRange`s.
+
+We're missing several test cases. Minimally we should test that the assist works
+correctly with negative values (it doesn't), and that it will not produce an
+edit if the integer literal isn't valid. We'll leave adding such tests as an
+exercise for the reader.
+
+In this example we're just making a simple replacement, so we're avoiding any
+need to worry about formatting. As a general principle we don't attempt to
+format the code after it's been modified, but we do make an effort to leave the
+code in a reasonably readable state. There's a getter (`eol`) that you can use
+to get the end-of-line marker that should be used in the file, and there's
+another getter (`utils`) that will return an object with several utility methods
+that help with things like getting the right indentation for nested code.
+
+### Multi-assist producers
+
+We skipped over the list named `multiGenerators` earlier, promising that we'd
+return to it later. You'll probably never have a need for it, but in case you do
+this section will hopefully tell you what you need to know.
+
+There's a subclass of `CorrectionProducer` named `MultiCorrectionProducer` and
+this list is how you register one of them. That class exists for rare cases
+where you need to use a single correction producer to produce multiple assists.
+This is generally only needed when you can't know in advance the maximum number
+of assists that might need to be produced. For example, there's a set of assists
+to wrap a Flutter `Widget` in another widget, but the set of widgets that can
+wrap a given widget depends on the widget being wrapped.
+
+If you are able to enumerate the possible assists ahead of time, then you're
+probably better off to create one subclass of `CorrectionProducer` for each of
+the assists.