Version 2.17.0-125.0.dev

Merge commit '0921dceaef7f205bd24dc214abd2509ec95e0cdf' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/client_configuration.dart b/pkg/analysis_server/lib/src/lsp/client_configuration.dart
index 83b987b..c7497dd 100644
--- a/pkg/analysis_server/lib/src/lsp/client_configuration.dart
+++ b/pkg/analysis_server/lib/src/lsp/client_configuration.dart
@@ -165,6 +165,18 @@
       _fallback?.enableSdkFormatter ??
       true;
 
+  /// Whether to include Snippets in code completion results.
+  bool get enableSnippets =>
+      // TODO(dantup): Change this setting to `enableSnippets`
+      //    and default to `true`
+      //    and remove `initializeWithSnippetSupportAndPreviewFlag` from tests
+      //    once all snippets are implemented and VS Code has shipped a
+      //    version that maps `enableServerSnippets` to `enableSnippets` in
+      //    middleware to avoid dupes.
+      _settings['previewEnableSnippets'] as bool? ??
+      _fallback?.enableSnippets ??
+      false;
+
   /// The line length used when formatting documents.
   ///
   /// If null, the formatters default will be used.
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index 6338b96..a91f9fd 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -21,7 +21,9 @@
 import 'package:analysis_server/src/services/completion/yaml/fix_data_generator.dart';
 import 'package:analysis_server/src/services/completion/yaml/pubspec_generator.dart';
 import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
+import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
 import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart' as ast;
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
@@ -178,6 +180,31 @@
   String _createImportedSymbolKey(String name, Uri declaringUri) =>
       '$name/$declaringUri';
 
+  Future<List<CompletionItem>> _getDartSnippetItems({
+    required LspClientCapabilities clientCapabilities,
+    required ResolvedUnitResult unit,
+    required int offset,
+    required LineInfo lineInfo,
+  }) async {
+    final request = DartSnippetRequest(
+      unit: unit,
+      offset: offset,
+    );
+    final snippetManager = DartSnippetManager();
+    final snippets = await snippetManager.computeSnippets(request);
+
+    return snippets
+        .map((snippet) => snippetToCompletionItem(
+              server,
+              clientCapabilities,
+              unit.path,
+              lineInfo,
+              toPosition(lineInfo.getLocation(offset)),
+              snippet,
+            ))
+        .toList();
+  }
+
   Future<ErrorOr<CompletionList>> _getPluginResults(
     LspClientCapabilities capabilities,
     LineInfo lineInfo,
@@ -420,6 +447,18 @@
             });
           }
 
+          // Add in any snippets.
+          final snippetsEnabled =
+              server.clientConfiguration.forResource(unit.path).enableSnippets;
+          if (capabilities.completionSnippets && snippetsEnabled) {
+            results.addAll(await _getDartSnippetItems(
+              clientCapabilities: capabilities,
+              unit: unit,
+              offset: offset,
+              lineInfo: unit.lineInfo,
+            ));
+          }
+
           // Perform fuzzy matching based on the identifier in front of the caret to
           // reduce the size of the payload.
           final fuzzyPattern = completionRequest.targetPrefix;
@@ -435,6 +474,8 @@
               CompletionList(isIncomplete: false, items: matchingResults));
         } on AbortCompletion {
           return success(CompletionList(isIncomplete: false, items: []));
+        } on InconsistentAnalysisException {
+          return success(CompletionList(isIncomplete: false, items: []));
         }
       },
     );
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 817937e..fec47e8 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -22,6 +22,7 @@
 import 'package:analysis_server/src/lsp/source_edits.dart';
 import 'package:analysis_server/src/protocol_server.dart' as server
     hide AnalysisError;
+import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
 import 'package:analyzer/dart/analysis/results.dart' as server;
 import 'package:analyzer/error/error.dart' as server;
 import 'package:analyzer/source/line_info.dart' as server;
@@ -890,6 +891,35 @@
   );
 }
 
+/// Creates a SnippetTextEdit for a set of edits using Linked Edit Groups.
+///
+/// Edit groups offsets are based on the entire content being modified after all
+/// edits, so [editOffset] must to take into account both the offset of the edit
+/// _and_ any delta from edits prior to this one in the file.
+///
+/// [selectionOffset] is also absolute and assumes [edit.replacement] will be
+/// inserted at [editOffset].
+lsp.SnippetTextEdit snippetTextEditFromEditGroups(
+  String filePath,
+  server.LineInfo lineInfo,
+  server.SourceEdit edit, {
+  required List<server.LinkedEditGroup> editGroups,
+  required int editOffset,
+  required int? selectionOffset,
+}) {
+  return lsp.SnippetTextEdit(
+    insertTextFormat: lsp.InsertTextFormat.Snippet,
+    range: toRange(lineInfo, edit.offset, edit.length),
+    newText: buildSnippetStringForEditGroups(
+      edit.replacement,
+      filePath: filePath,
+      editGroups: editGroups,
+      editOffset: editOffset,
+      selectionOffset: selectionOffset,
+    ),
+  );
+}
+
 /// Creates a SnippetTextEdit for an edit with a selection placeholder.
 ///
 /// [selectionOffset] is relative to (and therefore must be within) the edit.
@@ -909,6 +939,76 @@
   );
 }
 
+lsp.CompletionItem snippetToCompletionItem(
+  lsp.LspAnalysisServer server,
+  LspClientCapabilities capabilities,
+  String file,
+  LineInfo lineInfo,
+  Position position,
+  Snippet snippet,
+) {
+  assert(capabilities.completionSnippets);
+
+  final formats = capabilities.completionDocumentationFormats;
+  final documentation = snippet.documentation;
+  final supportsAsIsInsertMode =
+      capabilities.completionInsertTextModes.contains(InsertTextMode.asIs);
+  final changes = snippet.change;
+
+  // We must only get one change for this file to be able to apply snippets.
+  final thisFilesChange = changes.edits.singleWhere((e) => e.file == file);
+  final otherFilesChanges = changes.edits.where((e) => e.file != file).toList();
+
+  // If this completion involves editing other files, we'll need to build
+  // a command that the client will call to apply those edits later, because
+  // LSP Completions can only provide simple edits for the current file.
+  Command? command;
+  if (otherFilesChanges.isNotEmpty) {
+    final workspaceEdit = createPlainWorkspaceEdit(server, otherFilesChanges);
+    command = Command(
+        title: 'Add import',
+        command: Commands.sendWorkspaceEdit,
+        arguments: [workspaceEdit]);
+  }
+
+  /// Convert the changes to TextEdits using snippet tokens for linked edit
+  /// groups.
+  final mainFileEdits = toSnippetTextEdits(
+    file,
+    thisFilesChange,
+    changes.linkedEditGroups,
+    lineInfo,
+    selectionOffset:
+        changes.selection?.file == file ? changes.selection?.offset : null,
+  );
+
+  // For LSP, we need to provide the main edit and other edits separately. The
+  // main edit must include the location that completion was invoked. If we
+  // fail to find exactly one, this is an error.
+  final mainEdit = mainFileEdits
+      .singleWhere((edit) => edit.range.start.line == position.line);
+  final nonMainEdits = mainFileEdits.where((edit) => edit != mainEdit).toList();
+
+  return lsp.CompletionItem(
+    label: snippet.label,
+    filterText: snippet.prefix,
+    kind: lsp.CompletionItemKind.Snippet,
+    command: command,
+    documentation: documentation != null
+        ? asStringOrMarkupContent(formats, documentation)
+        : null,
+    // Force snippets to be sorted at the bottom of the list.
+    // TODO(dantup): Consider if we can rank these better. Client-side
+    //   snippets have always been forced to the bottom partly because they
+    //   show up in more places than wanted.
+    sortText: 'zzz${snippet.prefix}',
+    insertTextFormat: lsp.InsertTextFormat.Snippet,
+    insertTextMode: supportsAsIsInsertMode ? InsertTextMode.asIs : null,
+    textEdit: Either2<TextEdit, InsertReplaceEdit>.t1(mainEdit),
+    additionalTextEdits: nonMainEdits,
+  );
+}
+
 lsp.CompletionItemKind? suggestionKindToCompletionItemKind(
   Set<lsp.CompletionItemKind> supportedCompletionKinds,
   server.CompletionSuggestionKind kind,
@@ -1372,6 +1472,36 @@
   );
 }
 
+List<lsp.SnippetTextEdit> toSnippetTextEdits(
+  String filePath,
+  server.SourceFileEdit change,
+  List<server.LinkedEditGroup> editGroups,
+  LineInfo lineInfo, {
+  required int? selectionOffset,
+}) {
+  final snippetEdits = <lsp.SnippetTextEdit>[];
+
+  // Edit groups offsets are based on after the edits are applied, so we
+  // must track the offset delta to ensure we track the offset within the
+  // edits. This also requires the edits are sorted earliest-to-latest.
+  var offsetDelta = 0;
+  change.edits.sortBy<num>((edit) => edit.offset);
+  for (final edit in change.edits) {
+    snippetEdits.add(snippetTextEditFromEditGroups(
+      filePath,
+      lineInfo,
+      edit,
+      editGroups: editGroups,
+      editOffset: edit.offset + offsetDelta,
+      selectionOffset: selectionOffset,
+    ));
+
+    offsetDelta += edit.replacement.length - edit.length;
+  }
+
+  return snippetEdits;
+}
+
 ErrorOr<server.SourceRange> toSourceRange(
     server.LineInfo lineInfo, Range range) {
   // If there is a range, convert to offsets because that's what
diff --git a/pkg/analysis_server/lib/src/lsp/snippets.dart b/pkg/analysis_server/lib/src/lsp/snippets.dart
index 6f59d8e..e88dcaa 100644
--- a/pkg/analysis_server/lib/src/lsp/snippets.dart
+++ b/pkg/analysis_server/lib/src/lsp/snippets.dart
@@ -4,32 +4,113 @@
 
 import 'dart:math' as math;
 
+import 'package:analysis_server/src/protocol_server.dart' as server
+    hide AnalysisError;
 import 'package:collection/collection.dart';
 
+/// Builds an LSP snippet string using the supplied edit groups.
+///
+/// [editGroups] are provided as absolute positions, where the edit will be
+/// made starting at [editOffset].
+///
+/// [selectionOffset] is also absolute and assumes [text] will be
+/// inserted at [editOffset].
+String buildSnippetStringForEditGroups(
+  String text, {
+  required String filePath,
+  required List<server.LinkedEditGroup> editGroups,
+  required int editOffset,
+  int? selectionOffset,
+}) =>
+    _buildSnippetString(
+      text,
+      filePath: filePath,
+      editGroups: editGroups,
+      editGroupsOffset: editOffset,
+      selectionOffset:
+          selectionOffset != null ? selectionOffset - editOffset : null,
+    );
+
 /// Builds an LSP snippet string with supplied ranges as tab stops.
 ///
 /// [tabStopOffsetLengthPairs] are relative to the supplied text.
 String buildSnippetStringWithTabStops(
   String text,
   List<int>? tabStopOffsetLengthPairs,
-) {
+) =>
+    _buildSnippetString(
+      text,
+      filePath: null,
+      tabStopOffsetLengthPairs: tabStopOffsetLengthPairs,
+    );
+
+/// Builds an LSP snippet string with supplied ranges as tab stops.
+///
+/// [tabStopOffsetLengthPairs] are relative to the supplied text.
+///
+/// [selectionOffset]/[selectionLength] form a tab stop that is always "number 0"
+/// which is the final tab stop.
+///
+/// [editGroups] are provided as absolute positions, where [text] is known to
+/// start at [editGroupsOffset] in the final document.
+String _buildSnippetString(
+  String text, {
+  required String? filePath,
+  List<int>? tabStopOffsetLengthPairs,
+  int? selectionOffset,
+  int? selectionLength,
+  List<server.LinkedEditGroup>? editGroups,
+  int editGroupsOffset = 0,
+}) {
   tabStopOffsetLengthPairs ??= const [];
+  editGroups ??= const [];
   assert(tabStopOffsetLengthPairs.length % 2 == 0);
 
-  // Convert selection/tab stops/edit groups all into a common format
-  // (`SnippetPlaceholder`) so they can be handled in a single pass through
+  /// Helper to create a [SnippetPlaceholder] for each position in a linked
+  /// edit group.
+  Iterable<SnippetPlaceholder> convertEditGroup(
+    int index,
+    server.LinkedEditGroup editGroup,
+  ) {
+    final validPositions = editGroup.positions.where((p) => p.file == filePath);
+    // Create a placeholder for each position in the group.
+    return validPositions.map(
+      (position) => SnippetPlaceholder(
+        // Make the position relative to the supplied text.
+        position.offset - editGroupsOffset,
+        editGroup.length,
+        suggestions: editGroup.suggestions
+            .map((suggestion) => suggestion.value)
+            .toList(),
+        // Use the index as an ID to keep all related positions together (so
+        // the remain "linked").
+        linkedGroupId: index,
+      ),
+    );
+  }
+
+  // Convert selection/tab stops/edit groups all into the same format
+  // (`_SnippetPlaceholder`) so they can be handled in a single pass through
   // the text.
   final placeholders = [
+    // Selection.
+    if (selectionOffset != null)
+      SnippetPlaceholder(selectionOffset, selectionLength ?? 0, isFinal: true),
+
     // Tab stops.
     for (var i = 0; i < tabStopOffsetLengthPairs.length - 1; i += 2)
       SnippetPlaceholder(
         tabStopOffsetLengthPairs[i],
         tabStopOffsetLengthPairs[i + 1],
-        // If there's only a single tab stop, mark
+        // If there's only a single tab stop (and no selection/editGroups), mark
         // it as the final stop so it exit "snippet mode" when tabbed to.
-        isFinal: tabStopOffsetLengthPairs.length == 2,
+        isFinal: selectionOffset == null &&
+            editGroups.isEmpty &&
+            tabStopOffsetLengthPairs.length == 2,
       ),
-    // TODO(dantup): Add edit group/selection support.
+
+    // Linked edit groups.
+    ...editGroups.expandIndexed(convertEditGroup),
   ];
 
   // Remove any groups outside of the range (it's possible the edit groups apply
diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart
index 3f8be5c..0e0b1c9 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist.dart
@@ -56,6 +56,11 @@
     30,
     'Assign value to new local variable',
   );
+  static const CONVERT_CLASS_TO_ENUM = AssistKind(
+    'dart.assist.convert.classToEnum',
+    30,
+    'Convert class to an enum',
+  );
   static const CONVERT_CLASS_TO_MIXIN = AssistKind(
     'dart.assist.convert.classToMixin',
     30,
diff --git a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
index 3cbb3f8..70835d5 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -12,6 +12,7 @@
 import 'package:analysis_server/src/services/correction/dart/add_type_annotation.dart';
 import 'package:analysis_server/src/services/correction/dart/assign_to_local_variable.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_add_all_to_spread.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_class_to_enum.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_class_to_mixin.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_conditional_expression_to_if_element.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_documentation_into_block.dart';
@@ -91,6 +92,7 @@
     AddTypeAnnotation.newInstanceBulkFixable,
     AssignToLocalVariable.newInstance,
     ConvertAddAllToSpread.newInstance,
+    ConvertClassToEnum.newInstance,
     ConvertClassToMixin.newInstance,
     ConvertConditionalExpressionToIfElement.newInstance,
     ConvertDocumentationIntoBlock.newInstance,
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_enum.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_enum.dart
new file mode 100644
index 0000000..978a6f5
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_enum.dart
@@ -0,0 +1,768 @@
+// 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:analysis_server/src/services/correction/util.dart';
+import 'package:analysis_server/src/utilities/extensions/range_factory.dart';
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/constant/value.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.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/change_builder/change_builder_dart.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+import 'package:collection/collection.dart';
+
+class ConvertClassToEnum extends CorrectionProducer {
+  @override
+  AssistKind get assistKind => DartAssistKind.CONVERT_CLASS_TO_ENUM;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    if (!libraryElement.featureSet.isEnabled(Feature.enhanced_enums)) {
+      // If the library doesn't support enhanced_enums then the class can't be
+      // converted.
+      return;
+    }
+    if (libraryElement.units.length > 1) {
+      // If the library has any part files, then the class can't be converted
+      // because we don't currently have a performant way to access the ASTs for
+      // the parts to check for invocations of the constructors or subclasses of
+      // the class.
+      return;
+    }
+    var node = this.node;
+    if (node is! SimpleIdentifier) {
+      return;
+    }
+    var parent = node.parent;
+    if (parent is ClassDeclaration && parent.name == node) {
+      var description = _EnumDescription.fromClass(parent);
+      if (description != null) {
+        await builder.addDartFileEdit(file, (builder) {
+          description.applyChanges(builder, utils);
+        });
+      }
+    }
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
+  static ConvertClassToEnum newInstance() => ConvertClassToEnum();
+}
+
+/// A superclass for the [_EnumVisitor] and [_NonEnumVisitor].
+class _BaseVisitor extends RecursiveAstVisitor<void> {
+  /// The element representing the enum declaration that's being visited.
+  final ClassElement classElement;
+
+  _BaseVisitor(this.classElement);
+
+  /// Return `true` if the given [node] is an invocation of a constructor from
+  /// the class being converted.
+  bool invokesEnumConstructor(InstanceCreationExpression node) {
+    var constructorElement = node.constructorName.staticElement;
+    return constructorElement?.enclosingElement == classElement;
+  }
+}
+
+/// An exception thrown by the visitors if a condition is found that prevents
+/// the class from being converted.
+class _CannotConvertException implements Exception {
+  final String message;
+
+  _CannotConvertException(this.message);
+}
+
+/// A representation of a static field in the class being converted that will be
+/// replaced by an enum constant.
+class _ConstantField extends _Field {
+  /// The element representing the constructor used to initialize the field.
+  ConstructorElement constructorElement;
+
+  /// The invocation of the constructor.
+  final InstanceCreationExpression instanceCreation;
+
+  /// The value of the index field.
+  final int indexValue;
+
+  _ConstantField(
+      FieldElement element,
+      VariableDeclaration declaration,
+      VariableDeclarationList declarationList,
+      FieldDeclaration fieldDeclaration,
+      this.instanceCreation,
+      this.constructorElement,
+      this.indexValue)
+      : super(element, declaration, declarationList, fieldDeclaration);
+}
+
+/// Information about a single constructor in the class being converted.
+class _Constructor {
+  /// The declaration of the constructor.
+  final ConstructorDeclaration declaration;
+
+  /// The element representing the constructor.
+  final ConstructorElement element;
+
+  _Constructor(this.declaration, this.element);
+}
+
+/// Information about the constructors in the class being converted.
+class _Constructors {
+  /// A map from elements to constructors.
+  final Map<ConstructorElement, _Constructor> byElement = {};
+
+  _Constructors();
+
+  /// Return the constructors in this collection.
+  Iterable<_Constructor> get constructors => byElement.values;
+
+  /// Add the given [constructor] to this collection.
+  void add(_Constructor constructor) {
+    byElement[constructor.element] = constructor;
+  }
+
+  /// Return the constructor with the given [element].
+  _Constructor? forElement(ConstructorElement element) {
+    return byElement[element];
+  }
+}
+
+/// A description of how to convert the class to an enum.
+class _EnumDescription {
+  /// The class declaration being converted.
+  final ClassDeclaration classDeclaration;
+
+  /// A map from constructor declarations to information about the parameter
+  /// corresponding to the index field. The map is `null` if there is no index
+  /// field.
+  final Map<_Constructor, _Parameter>? constructorMap;
+
+  /// A list of the declarations to be converted into enum constants.
+  final _Fields fields;
+
+  /// A list of the indexes of members that need to be deleted.
+  final List<int> membersToDelete;
+
+  _EnumDescription({
+    required this.classDeclaration,
+    required this.constructorMap,
+    required this.fields,
+    required this.membersToDelete,
+  });
+
+  /// Return the offset immediately following the opening brace for the class
+  /// body.
+  int get bodyOffset => classDeclaration.leftBracket.end;
+
+  /// Use the [builder] and correction [utils] to apply the change necessary to
+  /// convert the class to an enum.
+  void applyChanges(DartFileEditBuilder builder, CorrectionUtils utils) {
+    // Replace the keyword.
+    builder.addSimpleReplacement(
+        range.token(classDeclaration.classKeyword), 'enum');
+
+    // Remove the extends clause if there is one.
+    final extendsClause = classDeclaration.extendsClause;
+    if (extendsClause != null) {
+      var followingToken = extendsClause.endToken.next!;
+      builder.addDeletion(range.startStart(extendsClause, followingToken));
+    }
+
+    // Compute the declarations of the enum constants and delete the fields
+    // being converted.
+    var members = classDeclaration.members;
+    var indent = utils.getIndent(1);
+    var constantsBuffer = StringBuffer();
+    var fieldsToConvert = fields.fieldsToConvert;
+    fieldsToConvert
+        .sort((first, second) => first.indexValue.compareTo(second.indexValue));
+    for (var field in fieldsToConvert) {
+      // Compute the declaration of the corresponding enum constant.
+      if (constantsBuffer.isNotEmpty) {
+        constantsBuffer.write(',\n$indent');
+      }
+      constantsBuffer.write(field.name);
+      var invocation = field.instanceCreation;
+      var constructorNameNode = invocation.constructorName;
+      var invokedConstructorElement = field.constructorElement;
+      var invokedConstructor = constructorMap?.keys.firstWhere(
+          (constructor) => constructor.element == invokedConstructorElement);
+      var parameterData = constructorMap?[invokedConstructor];
+      var typeArguments = constructorNameNode.type.typeArguments;
+      if (typeArguments != null) {
+        constantsBuffer.write(utils.getNodeText(typeArguments));
+      }
+      var constructorName = constructorNameNode.name?.name;
+      if (constructorName != null) {
+        constantsBuffer.write('.$constructorName');
+      }
+      var argumentList = invocation.argumentList;
+      var arguments = argumentList.arguments;
+      var argumentCount = arguments.length - (parameterData == null ? 0 : 1);
+      if (argumentCount == 0) {
+        if (typeArguments != null || constructorName != null) {
+          constantsBuffer.write('()');
+        }
+      } else if (parameterData == null) {
+        constantsBuffer.write(utils.getNodeText(argumentList));
+      } else {
+        constantsBuffer.write('(');
+        var index = parameterData.index;
+        var last = arguments.length - 1;
+        if (index == 0) {
+          var offset = arguments[1].offset;
+          var length = arguments[last].end - offset;
+          constantsBuffer.write(utils.getText(offset, length));
+        } else if (index == last) {
+          var offset = arguments[0].offset;
+          int length;
+          if (arguments[last].endToken.next?.type == TokenType.COMMA) {
+            length = arguments[last].offset - offset;
+          } else {
+            length = arguments[last - 1].end - offset;
+          }
+          constantsBuffer.write(utils.getText(offset, length));
+        } else {
+          var offset = arguments[0].offset;
+          var length = arguments[index].offset - offset;
+          constantsBuffer.write(utils.getText(offset, length));
+
+          offset = arguments[index + 1].offset;
+          length = argumentList.endToken.offset - offset;
+          constantsBuffer.write(utils.getText(offset, length));
+        }
+        constantsBuffer.write(')');
+      }
+
+      // Delete the static field that was converted to an enum constant.
+      _deleteField(builder, field, members);
+    }
+
+    // Remove the index field.
+    var indexField = fields.indexField;
+    if (indexField != null) {
+      _deleteField(builder, indexField, members);
+    }
+
+    // Update the constructors.
+    var removedConstructor = _removeUnnamedConstructor();
+    _transformConstructors(builder, removedConstructor);
+
+    // Special case replacing all of the members.
+    if (membersToDelete.length == members.length) {
+      builder.addSimpleReplacement(range.startEnd(members.first, members.last),
+          constantsBuffer.toString());
+      return;
+    }
+
+    // Insert the declarations of the enum constants.
+    var semicolon = ';';
+    var prefix = '${utils.endOfLine}$indent';
+    var suffix = '$semicolon${utils.endOfLine}';
+    builder.addSimpleInsertion(bodyOffset, '$prefix$constantsBuffer$suffix');
+
+    // Delete any members that are no longer needed.
+    membersToDelete.sort();
+    for (var range in range.nodesInList(members, membersToDelete)) {
+      builder.addDeletion(range);
+    }
+  }
+
+  /// Use the [builder] to delete the [field].
+  void _deleteField(DartFileEditBuilder builder, _Field field,
+      NodeList<ClassMember> members) {
+    var variableList = field.declarationList;
+    if (variableList.variables.length == 1) {
+      membersToDelete.add(members.indexOf(field.fieldDeclaration));
+    } else {
+      builder.addDeletion(
+          range.nodeInList(variableList.variables, field.declaration));
+    }
+  }
+
+  /// If the unnamed constructor is the only constructor, and if it has no
+  /// parameters other than potentially the index field, then remove it.
+  ConstructorDeclaration? _removeUnnamedConstructor() {
+    var members = classDeclaration.members;
+    var constructors = members.whereType<ConstructorDeclaration>().toList();
+    if (constructors.length != 1) {
+      return null;
+    }
+    var constructor = constructors[0];
+    var name = constructor.name?.name;
+    if (name != null && name != 'new') {
+      return null;
+    }
+    var parameters = constructor.parameters.parameters;
+    // If there's only one constructor, then there can only be one entry in the
+    // constructor map.
+    var parameterData = constructorMap?.entries.first.value;
+    // `parameterData` should only be `null` if there is no index field.
+    var updatedParameterCount =
+        parameters.length - (parameterData == null ? 0 : 1);
+    if (updatedParameterCount != 0) {
+      return null;
+    }
+    membersToDelete.add(members.indexOf(constructor));
+    return constructor;
+  }
+
+  /// Transform the used constructors by removing the parameter corresponding to
+  /// the index field.
+  void _transformConstructors(
+      DartFileEditBuilder builder, ConstructorDeclaration? removedConstructor) {
+    final constructorMap = this.constructorMap;
+    if (constructorMap == null) {
+      return;
+    }
+    for (var constructor in constructorMap.keys) {
+      if (constructor.declaration != removedConstructor) {
+        var parameterData = constructorMap[constructor];
+        if (parameterData != null) {
+          var parameters = constructor.declaration.parameters.parameters;
+          builder.addDeletion(
+              range.nodeInList(parameters, parameters[parameterData.index]));
+        }
+      }
+    }
+  }
+
+  /// If the given [node] can be converted into an enum, then return a
+  /// description of the conversion work to be done. Otherwise, return `null`.
+  static _EnumDescription? fromClass(ClassDeclaration node) {
+    // The class must be a concrete class.
+    var classElement = node.declaredElement;
+    if (classElement == null || classElement.isAbstract) {
+      return null;
+    }
+
+    // The class must be a subclass of Object, whether implicitly or explicitly.
+    var extendsClause = node.extendsClause;
+    if (extendsClause != null &&
+        extendsClause.superclass.type?.isDartCoreObject == false) {
+      return null;
+    }
+
+    // The class must either be private or must only have private constructors.
+    var constructors = _validateConstructors(node, classElement);
+    if (constructors == null) {
+      return null;
+    }
+
+    // The class must not override either `==` or `hashCode`.
+    if (!_validateMethods(node)) {
+      return null;
+    }
+
+    // There must be at least one static field that can be converted into an
+    // enum constant.
+    //
+    // The instance fields must all be final.
+    var fields = _validateFields(node, classElement);
+    if (fields == null || fields.fieldsToConvert.isEmpty) {
+      return null;
+    }
+
+    var visitor = _EnumVisitor(classElement, fields.fieldsToConvert);
+    try {
+      node.accept(visitor);
+    } on _CannotConvertException {
+      return null;
+    }
+
+    // Within the defining library,
+    // - there can't be any subclasses of the class to be converted,
+    // - there can't be any invocations of any constructor from that class.
+    try {
+      node.root.accept(_NonEnumVisitor(classElement));
+    } on _CannotConvertException {
+      return null;
+    }
+
+    var usedConstructors = _computeUsedConstructors(constructors, fields);
+    var constructorMap = _indexFieldData(usedConstructors, fields);
+    if (fields.indexField != null && constructorMap == null) {
+      return null;
+    }
+
+    var membersToDelete = <int>[];
+    return _EnumDescription(
+      classDeclaration: node,
+      constructorMap: constructorMap,
+      fields: fields,
+      membersToDelete: membersToDelete,
+    );
+  }
+
+  /// Return the subset of [constructors] that are invoked by the [fields] to be
+  /// converted.
+  static _Constructors _computeUsedConstructors(
+      _Constructors constructors, _Fields fields) {
+    var usedElements = <ConstructorElement>{};
+    for (var field in fields.fieldsToConvert) {
+      usedElements.add(field.constructorElement);
+    }
+    var usedConstructors = _Constructors();
+    for (var element in usedElements) {
+      var constructor = constructors.forElement(element);
+      if (constructor != null) {
+        usedConstructors.add(constructor);
+      }
+    }
+    return usedConstructors;
+  }
+
+  /// If the index field can be removed, return a map describing the changes
+  /// that need to be made to both the constructors and the invocations of those
+  /// constructors. Otherwise, return `null`.
+  static Map<_Constructor, _Parameter>? _indexFieldData(
+      _Constructors usedConstructors, _Fields fields) {
+    var indexField = fields.indexField;
+    if (indexField == null) {
+      return null;
+    }
+    // Ensure that the index field has a corresponding field formal initializer
+    // in each of the used constructors.
+    var constructorMap = <_Constructor, _Parameter>{};
+    for (var constructor in usedConstructors.constructors) {
+      var parameterData = _indexParameter(constructor, indexField);
+      if (parameterData == null) {
+        return null;
+      }
+      constructorMap[constructor] = parameterData;
+    }
+
+    var fieldsToConvert = fields.fieldsToConvert;
+    var values = <int>{};
+    for (var field in fieldsToConvert) {
+      var constructorElement = field.constructorElement;
+      var constructor = usedConstructors.forElement(constructorElement);
+      if (constructor == null) {
+        // We should never reach this point.
+        return null;
+      }
+      var parameterData = constructorMap[constructor];
+      if (parameterData == null) {
+        // We should never reach this point.
+        return null;
+      }
+      var arguments = field.instanceCreation.argumentList.arguments;
+      var argument = parameterData.getArgument(arguments);
+      if (argument is! IntegerLiteral) {
+        return null;
+      }
+      var value = argument.value;
+      if (value == null) {
+        return null;
+      }
+      if (!values.add(value)) {
+        // Duplicate value.
+        return null;
+      }
+    }
+    var sortedValues = values.toList()..sort();
+    if (sortedValues.length == fieldsToConvert.length &&
+        sortedValues.first == 0 &&
+        sortedValues.last == fieldsToConvert.length - 1) {
+      return constructorMap;
+    }
+    return null;
+  }
+
+  static _Parameter? _indexParameter(
+      _Constructor constructor, _Field? indexField) {
+    if (indexField == null) {
+      return null;
+    }
+    var parameters = constructor.declaration.parameters.parameters;
+    var indexFieldElement = indexField.element;
+    for (var i = 0; i < parameters.length; i++) {
+      var element = parameters[i].declaredElement;
+      if (element is FieldFormalParameterElement) {
+        if (element.field == indexFieldElement) {
+          if (element.isPositional) {
+            return _Parameter(i, element);
+          } else {
+            return _Parameter(i, element);
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  /// Return a representation of all of the constructors declared by the
+  /// [classDeclaration], or `null` if the class can't be converted.
+  ///
+  /// The [classElement] must be the element declared by the [classDeclaration].
+  static _Constructors? _validateConstructors(
+      ClassDeclaration classDeclaration, ClassElement classElement) {
+    var constructors = _Constructors();
+    for (var member in classDeclaration.members) {
+      if (member is ConstructorDeclaration) {
+        var constructor = member.declaredElement;
+        if (constructor is ConstructorElement) {
+          if (!classElement.isPrivate && !constructor.isPrivate) {
+            // Public constructor in public enum.
+            return null;
+          } else if (constructor.isFactory) {
+            // Factory constructor.
+            return null;
+          } else if (!constructor.isConst) {
+            // Non-const constructor.
+            return null;
+          }
+          constructors.add(_Constructor(member, constructor));
+        } else {
+          // Not resolved.
+          return null;
+        }
+      }
+    }
+    return constructors;
+  }
+
+  /// Return a representation of all of the constructors declared by the
+  /// [classDeclaration], or `null` if the class can't be converted.
+  ///
+  /// The [classElement] must be the element declared by the [classDeclaration].
+  static _Fields? _validateFields(
+      ClassDeclaration classDeclaration, ClassElement classElement) {
+    var potentialFieldsToConvert = <DartObject, List<_ConstantField>>{};
+    _Field? indexField;
+
+    for (var member in classDeclaration.members) {
+      if (member is FieldDeclaration) {
+        var fieldList = member.fields;
+        var fields = fieldList.variables;
+        if (member.isStatic) {
+          for (var field in fields) {
+            var fieldElement = field.declaredElement;
+            if (fieldElement is FieldElement) {
+              var fieldType = fieldElement.type;
+              // The field can be converted to be an enum constant if it
+              // - is a const field,
+              // - has a type equal to the type of the class, and
+              // - is initialized by an instance creation expression defined in this
+              //   class.
+              if (fieldElement.isConst &&
+                  fieldType is InterfaceType &&
+                  fieldType.element == classElement) {
+                var initializer = field.initializer;
+                if (initializer is InstanceCreationExpression) {
+                  var constructorElement =
+                      initializer.constructorName.staticElement;
+                  if (constructorElement != null &&
+                      constructorElement.enclosingElement == classElement) {
+                    var fieldValue = fieldElement.computeConstantValue();
+                    if (fieldValue != null) {
+                      if (fieldList.variables.length != 1) {
+                        // Too many constants in the field declaration.
+                        return null;
+                      }
+                      potentialFieldsToConvert
+                          .putIfAbsent(fieldValue, () => [])
+                          .add(_ConstantField(
+                              fieldElement,
+                              field,
+                              fieldList,
+                              member,
+                              initializer,
+                              constructorElement,
+                              fieldValue.getField('index')?.toIntValue() ??
+                                  -1));
+                    }
+                  }
+                }
+              }
+            }
+          }
+        } else {
+          for (var field in fields) {
+            if (!field.isFinal) {
+              // Non-final instance field.
+              return null;
+            }
+            var fieldElement = field.declaredElement;
+            if (fieldElement is FieldElement) {
+              var fieldType = fieldElement.type;
+              if (fieldElement.name == 'index' && fieldType.isDartCoreInt) {
+                indexField = _Field(fieldElement, field, fieldList, member);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    var fieldsToConvert = <_ConstantField>[];
+    for (var list in potentialFieldsToConvert.values) {
+      if (list.length == 1) {
+        fieldsToConvert.add(list[0]);
+      } else {
+        // TODO(brianwilkerson) We could potentially handle the case where
+        //  there's only one non-deprecated field in the list. We'd need to
+        //  change the return type for this method so that we could return two
+        //  lists: the list of fields to convert and the list of fields whose
+        //  initializer needs to be updated to refer to the constant.
+        return null;
+      }
+    }
+    return _Fields(fieldsToConvert, indexField);
+  }
+
+  /// Return `true` if the [classDeclaration] does not contain any methods that
+  /// prevent it from being converted.
+  static bool _validateMethods(ClassDeclaration classDeclaration) {
+    for (var member in classDeclaration.members) {
+      if (member is MethodDeclaration) {
+        if (member.name.name == '==' || member.name.name == 'hashCode') {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+}
+
+/// A visitor used to visit the class being converted. This visitor throws an
+/// exception if a constructor for the class is invoked anywhere other than the
+/// top-level expression of an initializer for one of the fields being converted.
+class _EnumVisitor extends _BaseVisitor {
+  /// The declarations of the fields that are to be converted.
+  final List<VariableDeclaration> fieldsToConvert;
+
+  /// A flag indicating whether we are currently visiting the children of a
+  /// field declaration that will be converted to be a constant.
+  bool inConstantDeclaration = false;
+
+  /// Initialize a newly created visitor to visit the class declaration
+  /// corresponding to the given [classElement].
+  _EnumVisitor(ClassElement classElement, List<_ConstantField> fieldsToConvert)
+      : fieldsToConvert =
+            fieldsToConvert.map((field) => field.declaration).toList(),
+        super(classElement);
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (!inConstantDeclaration) {
+      if (invokesEnumConstructor(node)) {
+        throw _CannotConvertException(
+            'Constructor used outside constant initializer');
+      }
+    }
+    inConstantDeclaration = false;
+    super.visitInstanceCreationExpression(node);
+  }
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    if (fieldsToConvert.contains(node)) {
+      inConstantDeclaration = true;
+    }
+    super.visitVariableDeclaration(node);
+    inConstantDeclaration = false;
+  }
+}
+
+/// A representation of a field of interest in the class being converted.
+class _Field {
+  /// The element representing the field.
+  final FieldElement element;
+
+  /// The declaration of the field.
+  final VariableDeclaration declaration;
+
+  /// The list containing the [declaration]
+  final VariableDeclarationList declarationList;
+
+  /// The field declaration containing the [declarationList].
+  final FieldDeclaration fieldDeclaration;
+
+  _Field(this.element, this.declaration, this.declarationList,
+      this.fieldDeclaration);
+
+  /// Return the name of the field.
+  String get name => declaration.name.name;
+}
+
+/// A representation of all the fields of interest in the class being converted.
+class _Fields {
+  /// The fields to be converted into enum constants.
+  List<_ConstantField> fieldsToConvert;
+
+  /// The index field, or `null` if there is no index field.
+  _Field? indexField;
+
+  _Fields(this.fieldsToConvert, this.indexField);
+}
+
+/// A visitor that visits everything in the library other than the class being
+/// converted. This visitor throws an exception if the class can't be converted
+/// because
+/// - there is a subclass of the class, or
+/// - there is an invocation of one of the constructors of the class.
+class _NonEnumVisitor extends _BaseVisitor {
+  /// Initialize a newly created visitor to visit everything except the class
+  /// declaration corresponding to the given [classElement].
+  _NonEnumVisitor(ClassElement classElement) : super(classElement);
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    var element = node.declaredElement;
+    if (element == null) {
+      throw _CannotConvertException('Unresolved');
+    }
+    if (element != classElement) {
+      if (element.supertype?.element == classElement) {
+        throw _CannotConvertException('Class is extended');
+      } else if (element.interfaces
+          .map((e) => e.element)
+          .contains(classElement)) {
+        throw _CannotConvertException('Class is implemented');
+      } else if (element.mixins.map((e) => e.element).contains(classElement)) {
+        // This case won't occur unless there's an error in the source code, but
+        // it's easier to check for the condition than it is to check for the
+        // diagnostic.
+        throw _CannotConvertException('Class is mixed in');
+      }
+      super.visitClassDeclaration(node);
+    }
+  }
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (invokesEnumConstructor(node)) {
+      throw _CannotConvertException(
+          'Constructor used outside class being converted');
+    }
+    super.visitInstanceCreationExpression(node);
+  }
+}
+
+/// An object used to access information about a specific parameter, including
+/// its index in the parameter list as well as any associated argument in an
+/// argument list.
+class _Parameter {
+  /// The index of this parameter in the enclosing constructor's parameter list.
+  final int index;
+
+  /// The element associated with the parameter.
+  final ParameterElement element;
+
+  _Parameter(this.index, this.element);
+
+  /// Return the expression representing the argument associated with this
+  /// parameter, or `null` if there is no such argument.
+  Expression? getArgument(NodeList<Expression> arguments) {
+    return arguments.firstWhereOrNull(
+        (argument) => argument.staticParameterElement == element);
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/snippets/dart/flutter_snippet_producers.dart b/pkg/analysis_server/lib/src/services/snippets/dart/flutter_snippet_producers.dart
new file mode 100644
index 0000000..6222f82
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/snippets/dart/flutter_snippet_producers.dart
@@ -0,0 +1,160 @@
+// 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/snippets/dart/snippet_manager.dart';
+import 'package:analysis_server/src/utilities/flutter.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/analysis/session_helper.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:meta/meta.dart';
+
+abstract class FlutterSnippetProducer extends SnippetProducer {
+  final flutter = Flutter.instance;
+  final AnalysisSessionHelper sessionHelper;
+
+  late ClassElement? classWidget;
+
+  FlutterSnippetProducer(DartSnippetRequest request)
+      : sessionHelper = AnalysisSessionHelper(request.analysisSession),
+        super(request);
+
+  @override
+  @mustCallSuper
+  Future<bool> isValid() async {
+    classWidget = await _getClass('Widget');
+
+    return classWidget != null;
+  }
+
+  Future<ClassElement?> _getClass(String name) =>
+      sessionHelper.getClass(flutter.widgetsUri, name);
+
+  DartType _getType(
+    ClassElement classElement, [
+    NullabilitySuffix nullabilitySuffix = NullabilitySuffix.none,
+  ]) =>
+      classElement.instantiate(
+        typeArguments: const [],
+        nullabilitySuffix: nullabilitySuffix,
+      );
+}
+
+/// Contributes snippets useful in Flutter applications, such as creating
+/// Flutter widget classes.
+class FlutterStatelessWidgetSnippetProducer extends FlutterSnippetProducer {
+  static const prefix = 'stless';
+  static const label = 'Flutter Stateless Widget';
+
+  late ClassElement? classStatelessWidget;
+  late ClassElement? classBuildContext;
+  late ClassElement? classKey;
+
+  FlutterStatelessWidgetSnippetProducer(DartSnippetRequest request)
+      : super(request);
+
+  @override
+  Future<Snippet> compute() async {
+    final builder = ChangeBuilder(session: request.analysisSession);
+
+    // Checked by isValid().
+    final classStatelessWidget = this.classStatelessWidget!;
+    final classWidget = this.classWidget!;
+    final classBuildContext = this.classBuildContext!;
+    final classKey = this.classKey!;
+
+    // Only include `?` for nulable types like Key? if in a null-safe library.
+    final nullableSuffix = request.unit.libraryElement.isNonNullableByDefault
+        ? NullabilitySuffix.question
+        : NullabilitySuffix.none;
+
+    final className = 'MyWidget';
+    await builder.addDartFileEdit(request.filePath, (builder) {
+      builder.addReplacement(request.replacementRange, (builder) {
+        builder.writeClassDeclaration(
+          className,
+          nameGroupName: 'name',
+          superclass: _getType(classStatelessWidget),
+          membersWriter: () {
+            // Add the constructor.
+            builder.write('  ');
+            builder.writeConstructorDeclaration(
+              className,
+              classNameGroupName: 'name',
+              isConst: true,
+              parameterWriter: () {
+                builder.write('{');
+                builder.writeParameter(
+                  'key',
+                  type: _getType(classKey, nullableSuffix),
+                );
+                builder.write('}');
+              },
+              initializerWriter: () => builder.write('super(key: key)'),
+            );
+            builder.writeln();
+            builder.writeln();
+
+            // Add the build method.
+            builder.writeln('  @override');
+            builder.write('  ');
+            builder.writeFunctionDeclaration(
+              'build',
+              returnType: _getType(classWidget),
+              parameterWriter: () {
+                builder.writeParameter(
+                  'context',
+                  type: _getType(classBuildContext),
+                );
+              },
+              bodyWriter: () {
+                builder.writeln('{');
+                builder.write('    ');
+                builder.selectHere();
+                builder.writeln('');
+                builder.writeln('  }');
+              },
+            );
+          },
+        );
+      });
+    });
+
+    return Snippet(
+      prefix,
+      label,
+      'Insert a StatelessWidget',
+      builder.sourceChange,
+    );
+  }
+
+  @override
+  Future<bool> isValid() async {
+    if (!await super.isValid()) {
+      return false;
+    }
+
+    classStatelessWidget = await _getClass('StatelessWidget');
+    if (classStatelessWidget == null) {
+      return false;
+    }
+
+    classBuildContext = await _getClass('BuildContext');
+    if (classBuildContext == null) {
+      return false;
+    }
+
+    classKey = await _getClass('Key');
+    if (classKey == null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  static FlutterStatelessWidgetSnippetProducer newInstance(
+          DartSnippetRequest request) =>
+      FlutterStatelessWidgetSnippetProducer(request);
+}
diff --git a/pkg/analysis_server/lib/src/services/snippets/dart/snippet_manager.dart b/pkg/analysis_server/lib/src/services/snippets/dart/snippet_manager.dart
new file mode 100644
index 0000000..6895873
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/snippets/dart/snippet_manager.dart
@@ -0,0 +1,186 @@
+// 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/protocol_server.dart';
+import 'package:analysis_server/src/provisional/completion/completion_core.dart';
+import 'package:analysis_server/src/services/snippets/dart/flutter_snippet_producers.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
+import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
+
+typedef SnippetProducerGenerator = SnippetProducer Function(DartSnippetRequest);
+
+/// [DartSnippetManager] determines if a snippet request is Dart specific
+/// and forwards those requests to all Snippet Producers that return `true` from
+/// their `isValid()` method.
+class DartSnippetManager {
+  final producerGenerators =
+      const <SnippetContext, List<SnippetProducerGenerator>>{
+    SnippetContext.atTopLevel: [
+      FlutterStatelessWidgetSnippetProducer.newInstance,
+    ]
+  };
+
+  Future<List<Snippet>> computeSnippets(
+    DartSnippetRequest request,
+  ) async {
+    var pathContext = request.resourceProvider.pathContext;
+    if (!file_paths.isDart(pathContext, request.filePath)) {
+      return const [];
+    }
+
+    try {
+      final snippets = <Snippet>[];
+      final generators = producerGenerators[request.context];
+      if (generators == null) {
+        return snippets;
+      }
+      for (final generator in generators) {
+        final producer = generator(request);
+        if (await producer.isValid()) {
+          snippets.add(await producer.compute());
+        }
+      }
+      return snippets;
+    } on InconsistentAnalysisException {
+      // The state of the code being analyzed has changed, so results are likely
+      // to be inconsistent. Just abort the operation.
+      throw AbortCompletion();
+    }
+  }
+}
+
+/// The information about a request for a list of snippets within a Dart file.
+class DartSnippetRequest {
+  /// The resolved unit for the file that snippets are being requested for.
+  final ResolvedUnitResult unit;
+
+  /// The path of the file snippets are being requested for.
+  final String filePath;
+
+  /// The offset within the source at which snippets are being
+  /// requested for.
+  final int offset;
+
+  /// The context in which the snippet request is being made.
+  late final SnippetContext context;
+
+  /// The source range that represents the region of text that should be
+  /// replaced if the snippet is selected.
+  late final SourceRange replacementRange;
+
+  DartSnippetRequest({
+    required this.unit,
+    required this.offset,
+  }) : filePath = unit.path {
+    final target = CompletionTarget.forOffset(unit.unit, offset);
+    context = _getContext(target);
+    replacementRange = target.computeReplacementRange(offset);
+  }
+
+  /// The analysis session that produced the elements of the request.
+  AnalysisSession get analysisSession => unit.session;
+
+  /// The resource provider associated with this request.
+  ResourceProvider get resourceProvider => analysisSession.resourceProvider;
+
+  static SnippetContext _getContext(CompletionTarget target) {
+    final entity = target.entity;
+    if (entity is Token) {
+      final tokenType = (entity.beforeSynthetic ?? entity).type;
+
+      if (tokenType == TokenType.MULTI_LINE_COMMENT ||
+          tokenType == TokenType.SINGLE_LINE_COMMENT) {
+        return SnippetContext.inComment;
+      }
+
+      if (tokenType == TokenType.STRING ||
+          tokenType == TokenType.STRING_INTERPOLATION_EXPRESSION ||
+          tokenType == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
+        return SnippetContext.inString;
+      }
+    }
+
+    AstNode? node = target.containingNode;
+    while (node != null) {
+      if (node is Comment) {
+        return SnippetContext.inComment;
+      }
+
+      if (node is StringLiteral) {
+        return SnippetContext.inString;
+      }
+
+      if (node is Block) {
+        return SnippetContext.inBlock;
+      }
+
+      if (node is Statement || node is Expression || node is Annotation) {
+        return SnippetContext.inExpressionOrStatement;
+      }
+
+      if (node is BlockFunctionBody) {
+        return SnippetContext.inBlock;
+      }
+
+      if (node is ClassOrMixinDeclaration || node is ExtensionDeclaration) {
+        return SnippetContext.inClass;
+      }
+
+      node = node.parent;
+    }
+
+    return SnippetContext.atTopLevel;
+  }
+}
+
+class Snippet {
+  /// The text the user will type to use this snippet.
+  final String prefix;
+
+  /// The label/title of this snippet.
+  final String label;
+
+  /// A description of/documentation for the snippet.
+  final String? documentation;
+
+  /// The source changes to be made to insert this snippet.
+  final SourceChange change;
+
+  Snippet(
+    this.prefix,
+    this.label,
+    this.documentation,
+    this.change,
+  );
+}
+
+/// The context in which a snippet request was made.
+///
+/// This is used to filter the available snippets (for example preventing
+/// snippets that create classes showing up when inside an existing class or
+/// function body).
+enum SnippetContext {
+  atTopLevel,
+  inClass,
+  inBlock,
+  inExpressionOrStatement,
+  inComment,
+  inString,
+}
+
+abstract class SnippetProducer {
+  final DartSnippetRequest request;
+
+  SnippetProducer(this.request);
+
+  Future<Snippet> compute();
+
+  Future<bool> isValid();
+}
diff --git a/pkg/analysis_server/lib/src/utilities/extensions/range_factory.dart b/pkg/analysis_server/lib/src/utilities/extensions/range_factory.dart
index 95c5384..eedc3c7 100644
--- a/pkg/analysis_server/lib/src/utilities/extensions/range_factory.dart
+++ b/pkg/analysis_server/lib/src/utilities/extensions/range_factory.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:analysis_server/src/utilities/index_range.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/source/source_range.dart';
@@ -67,6 +68,30 @@
     }
   }
 
+  /// Return a list of the ranges that cover all of the elements in the [list]
+  /// whose index is in the list of [indexes].
+  List<SourceRange> nodesInList<T extends AstNode>(
+      NodeList<T> list, List<int> indexes) {
+    var ranges = <SourceRange>[];
+    var indexRanges = IndexRange.contiguousSubRanges(indexes);
+    if (indexRanges.length == 1) {
+      var indexRange = indexRanges[0];
+      if (indexRange.lower == 0 && indexRange.upper == list.length - 1) {
+        ranges.add(startEnd(list[indexRange.lower], list[indexRange.upper]));
+        return ranges;
+      }
+    }
+    for (var indexRange in indexRanges) {
+      if (indexRange.lower == 0) {
+        ranges.add(
+            startStart(list[indexRange.lower], list[indexRange.upper + 1]));
+      } else {
+        ranges.add(endEnd(list[indexRange.lower - 1], list[indexRange.upper]));
+      }
+    }
+    return ranges;
+  }
+
   /// Return a source range that covers the given [node] with any leading and
   /// trailing comments.
   ///
diff --git a/pkg/analysis_server/lib/src/utilities/index_range.dart b/pkg/analysis_server/lib/src/utilities/index_range.dart
new file mode 100644
index 0000000..648ec02
--- /dev/null
+++ b/pkg/analysis_server/lib/src/utilities/index_range.dart
@@ -0,0 +1,42 @@
+// 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.
+
+/// A range of indexes within a list.
+class IndexRange {
+  /// The index of the first element in the range.
+  final int lower;
+
+  /// The index of the last element in the range. This will be the same as the
+  /// [lower] if there is a single element in the range.
+  final int upper;
+
+  /// Initialize a newly created range.
+  IndexRange(this.lower, this.upper);
+
+  /// Return the number of indices in this range.
+  int get count => upper - lower + 1;
+
+  @override
+  String toString() => '[$lower..$upper]';
+
+  static List<IndexRange> contiguousSubRanges(List<int> indexes) {
+    var ranges = <IndexRange>[];
+    if (indexes.isEmpty) {
+      return ranges;
+    }
+    var lower = indexes[0];
+    var previous = lower;
+    for (var index = 1; index < indexes.length; index++) {
+      var current = indexes[index];
+      if (current == previous + 1) {
+        previous = current;
+      } else {
+        ranges.add(IndexRange(lower, previous));
+        lower = previous = current;
+      }
+    }
+    ranges.add(IndexRange(lower, previous));
+    return ranges;
+  }
+}
diff --git a/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart b/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
index 925b13b..9a541dc 100644
--- a/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
@@ -341,7 +341,7 @@
   }
 
   Future<void> test_outsideRoot() async {
-    final otherFilePath = '/home/otherProject/foo.dart';
+    final otherFilePath = convertPath('/home/otherProject/foo.dart');
     final otherFileUri = Uri.file(otherFilePath);
     newFile(otherFilePath, content: 'bad code to create error');
     await initialize(
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 1f749a6..5588ee9 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -5,6 +5,7 @@
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/services/linter/lint_names.dart';
+import 'package:analysis_server/src/services/snippets/dart/flutter_snippet_producers.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:collection/collection.dart';
@@ -19,6 +20,9 @@
 void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(CompletionTest);
+    defineReflectiveTests(DartSnippetCompletionTest);
+    defineReflectiveTests(FlutterSnippetCompletionTest);
+    defineReflectiveTests(FlutterSnippetCompletionWithNullSafetyTest);
     defineReflectiveTests(CompletionTestWithNullSafetyTest);
   });
 }
@@ -2363,3 +2367,246 @@
     expect(completion.detail, '(int? a, [int b = 1]) → String?');
   }
 }
+
+@reflectiveTest
+class DartSnippetCompletionTest extends SnippetCompletionTest {
+  Future<void> test_snippets_disabled() async {
+    final content = '^';
+
+    // Advertise support (this is done by the editor), but with the user
+    // preference disabled.
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'enableSnippets': true},
+    );
+
+    await expectNoSnippet(
+      content,
+      FlutterStatelessWidgetSnippetProducer.prefix,
+    );
+  }
+
+  Future<void>
+      test_snippets_flutterStateless_notAvailable_notFlutterProject() async {
+    final content = '''
+class A {}
+
+stle^
+
+class B {}
+''';
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    await expectNoSnippet(
+      content,
+      FlutterStatelessWidgetSnippetProducer.prefix,
+    );
+  }
+
+  Future<void> test_snippets_notSupported() async {
+    final content = '^';
+
+    // If we don't send support for Snippet CompletionItem kinds, we don't
+    // expect any snippets at all.
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    expect(res.any((c) => c.kind == CompletionItemKind.Snippet), isFalse);
+  }
+}
+
+@reflectiveTest
+class FlutterSnippetCompletionTest extends SnippetCompletionTest {
+  /// Nullability suffix expected in this test class.
+  ///
+  /// Used to allow all tests to be run in both modes without having to
+  /// duplicate all tests ([FlutterSnippetCompletionWithNullSafetyTest]
+  /// overrides this).
+  String get questionSuffix => '';
+
+  @override
+  void setUp() {
+    super.setUp();
+    writePackageConfig(
+      projectFolderPath,
+      flutter: true,
+    );
+  }
+
+  Future<void> test_snippets_flutterStateless() async {
+    final content = '''
+import 'package:flutter/widgets.dart';
+
+class A {}
+
+stle^
+
+class B {}
+''';
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    final updated = await expectAndApplySnippet(
+      content,
+      prefix: FlutterStatelessWidgetSnippetProducer.prefix,
+      label: FlutterStatelessWidgetSnippetProducer.label,
+    );
+
+    expect(updated, '''
+import 'package:flutter/widgets.dart';
+
+class A {}
+
+class \${1:MyWidget} extends StatelessWidget {
+  const \${1:MyWidget}({Key$questionSuffix key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    \$0
+  }
+}
+
+class B {}
+''');
+  }
+
+  Future<void> test_snippets_flutterStateless_addsImports() async {
+    final content = '''
+class A {}
+
+stle^
+
+class B {}
+''';
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    final updated = await expectAndApplySnippet(
+      content,
+      prefix: FlutterStatelessWidgetSnippetProducer.prefix,
+      label: FlutterStatelessWidgetSnippetProducer.label,
+    );
+
+    expect(updated, '''
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+
+class A {}
+
+class \${1:MyWidget} extends StatelessWidget {
+  const \${1:MyWidget}({Key$questionSuffix key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    \$0
+  }
+}
+
+class B {}
+''');
+  }
+
+  Future<void> test_snippets_flutterStateless_notAvailable_notTopLevel() async {
+    final content = '''
+class A {
+
+  stle^
+
+}
+''';
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    await expectNoSnippet(
+      content,
+      FlutterStatelessWidgetSnippetProducer.prefix,
+    );
+  }
+}
+
+@reflectiveTest
+class FlutterSnippetCompletionWithNullSafetyTest
+    extends FlutterSnippetCompletionTest {
+  @override
+  String get questionSuffix => '?';
+
+  @override
+  String get testPackageLanguageVersion => latestLanguageVersion;
+}
+
+abstract class SnippetCompletionTest extends AbstractLspAnalysisServerTest
+    with CompletionTestMixin {
+  /// Expect that there is a snippet for [prefix] at [position] with the label
+  /// [label] and return the results of applying it to [content].
+  Future<String> expectAndApplySnippet(
+    String content, {
+    required String prefix,
+    required String label,
+  }) async {
+    final snippet = await expectSnippet(
+      content,
+      prefix: prefix,
+      label: label,
+    );
+
+    // Also apply the edit and check that it went in the right place with the
+    // correct formatting. Edit groups will just appear in the raw textmate
+    // snippet syntax here, as we don't do any special handling of them (and
+    // assume what's coded here is correct, and that the client will correctly
+    // interpret them).
+    final updated = applyTextEdits(
+      withoutMarkers(content),
+      [toTextEdit(snippet.textEdit!)]
+          .followedBy(snippet.additionalTextEdits!)
+          .toList(),
+    );
+    return updated;
+  }
+
+  /// Expect that there is no snippet for [prefix] at the position of `^` within
+  /// [content].
+  Future<void> expectNoSnippet(
+    String content,
+    String prefix,
+  ) async {
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final hasSnippet = res.any((c) => c.filterText == prefix);
+    expect(hasSnippet, isFalse);
+  }
+
+  /// Expect that there is a snippet for [prefix] with the label [label] at
+  /// [position] in [content].
+  Future<CompletionItem> expectSnippet(
+    String content, {
+    required String prefix,
+    required String label,
+  }) async {
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere(
+      (c) => c.filterText == prefix && c.label == label,
+    );
+    expect(item.insertTextFormat, InsertTextFormat.Snippet);
+    expect(item.insertText, isNull);
+    expect(item.textEdit, isNotNull);
+    return item;
+  }
+
+  Future<void> initializeWithSnippetSupport() => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+      );
+
+  Future<void> initializeWithSnippetSupportAndPreviewFlag() => provideConfig(
+        () => initialize(
+          textDocumentCapabilities: withCompletionItemSnippetSupport(
+              emptyTextDocumentClientCapabilities),
+          workspaceCapabilities:
+              withConfigurationSupport(emptyWorkspaceClientCapabilities),
+        ),
+        {'previewEnableSnippets': true},
+      );
+}
diff --git a/pkg/analysis_server/test/lsp/snippets_test.dart b/pkg/analysis_server/test/lsp/snippets_test.dart
index ba25212..a126c9b 100644
--- a/pkg/analysis_server/test/lsp/snippets_test.dart
+++ b/pkg/analysis_server/test/lsp/snippets_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/lsp/snippets.dart' as lsp;
 import 'package:analysis_server/src/lsp/snippets.dart';
+import 'package:analysis_server/src/protocol_server.dart' as server;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -129,6 +130,146 @@
 
 @reflectiveTest
 class SnippetsTest {
+  /// Paths aren't used for anything except filtering edit groups positions
+  /// so the specific values are not important.
+  final mainPath = '/home/test.dart';
+  final otherPath = '/home/other.dart';
+
+  Future<void> test_editGroups_choices() async {
+    var result = lsp.buildSnippetStringForEditGroups(
+      r'''
+var a = 1;
+''',
+      filePath: mainPath,
+      editOffset: 0,
+      editGroups: [
+        server.LinkedEditGroup(
+          [_pos(4)],
+          1,
+          [
+            _suggestion('aaa'),
+            _suggestion(r'bbb${},|'), // test for escaping
+            _suggestion('ccc'),
+          ],
+        ),
+      ],
+    );
+    expect(result, equals(r'''
+var ${1|a,aaa,bbb\${\}\,\|,ccc|} = 1;
+'''));
+  }
+
+  Future<void> test_editGroups_emptyGroup() async {
+    var result = lsp.buildSnippetStringForEditGroups(
+      r'''
+class  {
+  ();
+}
+''',
+      filePath: mainPath,
+      editOffset: 0,
+      editGroups: [
+        server.LinkedEditGroup(
+          [
+            _pos(6),
+            _pos(11),
+          ],
+          0,
+          [],
+        ),
+      ],
+    );
+    expect(result, equals(r'''
+class $1 {
+  $1();
+}
+'''));
+  }
+
+  Future<void> test_editGroups_positionsInOtherFiles() async {
+    var result = lsp.buildSnippetStringForEditGroups(
+      r'''
+class A {
+  A();
+}
+''',
+      filePath: mainPath,
+      editOffset: 0,
+      editGroups: [
+        server.LinkedEditGroup(
+          [
+            _pos(6),
+            _pos(10, otherPath), // Should not be included.
+            _pos(12),
+          ],
+          1,
+          [],
+        ),
+      ],
+    );
+    expect(result, equals(r'''
+class ${1:A} {
+  ${1:A}();
+}
+'''));
+  }
+
+  Future<void> test_editGroups_simpleGroup() async {
+    var result = lsp.buildSnippetStringForEditGroups(
+      r'''
+class A {
+  A();
+}
+''',
+      filePath: mainPath,
+      editOffset: 0,
+      editGroups: [
+        server.LinkedEditGroup(
+          [
+            _pos(6),
+            _pos(12),
+          ],
+          1,
+          [],
+        ),
+      ],
+    );
+    expect(result, equals(r'''
+class ${1:A} {
+  ${1:A}();
+}
+'''));
+  }
+
+  Future<void> test_editGroups_withOffset() async {
+    var result = lsp.buildSnippetStringForEditGroups(
+      r'''
+class A {
+  A();
+}
+''',
+      filePath: mainPath,
+      // This means the edit will be inserted at offset 100, so all linked edit
+      // offsets will be 100 more than in the supplied text.
+      editOffset: 100,
+      editGroups: [
+        server.LinkedEditGroup(
+          [
+            _pos(100 + 6),
+            _pos(100 + 12),
+          ],
+          1,
+          [],
+        ),
+      ],
+    );
+    expect(result, equals(r'''
+class ${1:A} {
+  ${1:A}();
+}
+'''));
+  }
+
   Future<void> test_tabStops_contains() async {
     var result = lsp.buildSnippetStringWithTabStops('a, b, c', [3, 1]);
     expect(result, equals(r'a, ${0:b}, c'));
@@ -165,4 +306,13 @@
     var result = lsp.buildSnippetStringWithTabStops('a, b', [0, 1]);
     expect(result, equals(r'${0:a}, b'));
   }
+
+  server.Position _pos(int offset, [String? path]) =>
+      server.Position(path ?? mainPath, offset);
+
+  server.LinkedEditSuggestion _suggestion(String text) =>
+      server.LinkedEditSuggestion(
+        text,
+        server.LinkedEditSuggestionKind.TYPE, // We don't use type.
+      );
 }
diff --git a/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart b/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart
new file mode 100644
index 0000000..1b9449c
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart
@@ -0,0 +1,120 @@
+// 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/snippets/dart/flutter_snippet_producers.dart';
+import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../abstract_single_unit.dart';
+import 'test_support.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(FlutterStatelessWidgetSnippetProducerTest);
+  });
+}
+
+class FlutterSnippetProducerTest extends AbstractSingleUnitTest {
+  Future<void> expectNotValidSnippet(
+    SnippetProducerGenerator generator,
+    String code,
+  ) async {
+    await resolveTestCode(withoutMarkers(code));
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: offsetFromMarker(code),
+    );
+
+    final producer = generator(request);
+    expect(await producer.isValid(), isFalse);
+  }
+
+  Future<Snippet> expectValidSnippet(
+      SnippetProducerGenerator generator, String code) async {
+    await resolveTestCode(withoutMarkers(code));
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: offsetFromMarker(code),
+    );
+
+    final producer = generator(request);
+    expect(await producer.isValid(), isTrue);
+    return producer.compute();
+  }
+}
+
+@reflectiveTest
+class FlutterStatelessWidgetSnippetProducerTest
+    extends FlutterSnippetProducerTest {
+  Future<void> test_notValid_notFlutterProject() async {
+    writeTestPackageConfig();
+
+    await expectNotValidSnippet(
+      FlutterStatelessWidgetSnippetProducer.newInstance,
+      '^',
+    );
+  }
+
+  Future<void> test_notValid_notTopLevel() async {
+    writeTestPackageConfig();
+
+    await expectNotValidSnippet(
+      FlutterStatelessWidgetSnippetProducer.newInstance,
+      'class A { ^ }',
+    );
+  }
+
+  Future<void> test_valid() async {
+    writeTestPackageConfig(flutter: true);
+
+    final snippet = await expectValidSnippet(
+      FlutterStatelessWidgetSnippetProducer.newInstance,
+      '^',
+    );
+    expect(snippet.prefix, 'stless');
+    expect(snippet.label, 'Flutter Stateless Widget');
+    expect(snippet.change.toJson(), {
+      'message': '',
+      'edits': [
+        {
+          'file': testFile,
+          'fileStamp': 0,
+          'edits': [
+            {
+              'offset': 0,
+              'length': 0,
+              'replacement':
+                  'import \'package:flutter/src/foundation/key.dart\';\n'
+                      'import \'package:flutter/src/widgets/framework.dart\';\n'
+            },
+            {
+              'offset': 0,
+              'length': 0,
+              'replacement': 'class MyWidget extends StatelessWidget {\n'
+                  '  const MyWidget({Key? key}) : super(key: key);\n'
+                  '\n'
+                  '  @override\n'
+                  '  Widget build(BuildContext context) {\n'
+                  '    \n'
+                  '  }\n'
+                  '}'
+            }
+          ]
+        }
+      ],
+      'linkedEditGroups': [
+        {
+          'positions': [
+            {'file': testFile, 'offset': 109},
+            {'file': testFile, 'offset': 152}
+          ],
+          'length': 8,
+          'suggestions': []
+        }
+      ],
+      'selection': {'file': testFile, 'offset': 248}
+    });
+  }
+}
diff --git a/pkg/analysis_server/test/services/snippets/dart/snippet_manager_test.dart b/pkg/analysis_server/test/services/snippets/dart/snippet_manager_test.dart
new file mode 100644
index 0000000..622d84f
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/dart/snippet_manager_test.dart
@@ -0,0 +1,116 @@
+// 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/snippets/dart/snippet_manager.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../abstract_single_unit.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SnippetManagerTest);
+  });
+}
+
+@reflectiveTest
+class SnippetManagerTest extends AbstractSingleUnitTest {
+  Future<void> test_notValidProducers() async {
+    await resolveTestCode('');
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: 0,
+    );
+
+    final manager = _TestDartSnippetManager({
+      SnippetContext.atTopLevel: [_NotValidSnippetProducer.newInstance],
+    });
+    final results = await manager.computeSnippets(request);
+    expect(results, isEmpty);
+  }
+
+  Future<void> test_onlyCreatesForContext() async {
+    await resolveTestCode('');
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: 0,
+    );
+
+    final manager = _TestDartSnippetManager({
+      SnippetContext.atTopLevel: [_ValidSnippetProducer.newInstance],
+      SnippetContext.inClass: [
+        (context) => throw 'Tried to create producer for wrong context',
+      ]
+    });
+    final results = await manager.computeSnippets(request);
+    expect(results, hasLength(1));
+  }
+
+  Future<void> test_validProducers() async {
+    await resolveTestCode('');
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: 0,
+    );
+
+    final manager = _TestDartSnippetManager({
+      SnippetContext.atTopLevel: [_ValidSnippetProducer.newInstance],
+    });
+    final results = await manager.computeSnippets(request);
+    expect(results, hasLength(1));
+    final snippet = results.single;
+    expect(snippet.prefix, 'mysnip');
+    expect(snippet.label, 'My Test Snippet');
+  }
+}
+
+/// A snippet producer that always returns `false` from [isValid] and throws
+/// if [compute] is called.
+class _NotValidSnippetProducer extends SnippetProducer {
+  _NotValidSnippetProducer(DartSnippetRequest request) : super(request);
+
+  @override
+  Future<Snippet> compute() {
+    throw UnsupportedError(
+      'compute should not be called for a producer '
+      'that returned false from isValid',
+    );
+  }
+
+  @override
+  Future<bool> isValid() async => false;
+
+  static _NotValidSnippetProducer newInstance(DartSnippetRequest request) =>
+      _NotValidSnippetProducer(request);
+}
+
+class _TestDartSnippetManager extends DartSnippetManager {
+  @override
+  final Map<SnippetContext, List<SnippetProducerGenerator>> producerGenerators;
+
+  _TestDartSnippetManager(this.producerGenerators);
+}
+
+/// A snippet producer that always returns `true` from [isValid] and a simple
+/// snippet from [compute].
+class _ValidSnippetProducer extends SnippetProducer {
+  _ValidSnippetProducer(DartSnippetRequest request) : super(request);
+
+  @override
+  Future<Snippet> compute() async {
+    return Snippet(
+      'mysnip',
+      'My Test Snippet',
+      'This is a test snippet',
+      SourceChange('message'),
+    );
+  }
+
+  @override
+  Future<bool> isValid() async => true;
+
+  static _ValidSnippetProducer newInstance(DartSnippetRequest request) =>
+      _ValidSnippetProducer(request);
+}
diff --git a/pkg/analysis_server/test/services/snippets/dart/snippet_request_test.dart b/pkg/analysis_server/test/services/snippets/dart/snippet_request_test.dart
new file mode 100644
index 0000000..fd6a240
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/dart/snippet_request_test.dart
@@ -0,0 +1,495 @@
+// 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/snippets/dart/snippet_manager.dart';
+import 'package:analyzer/src/test_utilities/platform.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../abstract_single_unit.dart';
+import 'test_support.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SnippetRequestTest);
+  });
+}
+
+@reflectiveTest
+class SnippetRequestTest extends AbstractSingleUnitTest {
+  SnippetRequestTest() {
+    verifyNoTestUnitErrors = false;
+  }
+
+  Future<void> test_expression_constructor() async {
+    await testRequest(r'''
+final a = new [[^]]
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_expression_constructorName() async {
+    await testRequest(r'''
+class A {
+  A.foo();
+}
+final a = new A.[[fo^]]
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_inAnnotation() async {
+    await testRequest(r'''
+@[[depre^]]
+class A {}
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_inBlock_forBody() async {
+    await testRequest(r'''
+foo() {
+  for (var i = 0; i < 10; i++) {
+    [[^]]
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inClass_atEnd() async {
+    await testRequest(r'''
+class A {
+  foo() {}
+
+  [[^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_atEnd_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {}
+
+  [[mysnip^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_atStart() async {
+    await testRequest(r'''
+class A {
+  [[^]]
+
+  foo() {}
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_atStart_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  [[mysnip^]]
+
+  foo() {}
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_betweenMembers() async {
+    await testRequest(r'''
+class A {
+  foo() {}
+
+  [[^]]
+
+  bar() {}
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_betweenMembers_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {}
+
+  [[mysnip^]]
+
+  bar() {}
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_empty() async {
+    await testRequest(r'''
+class A {
+  [[^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inClass_empty_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  [[mysnip^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inComment_dartDoc() async {
+    await testRequest(r'''
+/// [[^]]
+class A {}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_dartDoc_reference_member() async {
+    await testRequest(r'''
+class A {
+  /// [ [[A^]] ]
+  foo() {}
+}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_dartDoc_reference_topLevel() async {
+    await testRequest(r'''
+/// [ [[A^]] ]
+class A {}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_multiline_member() async {
+    await testRequest(r'''
+class A {
+  /*
+   * [[^]]
+   */
+  foo() {}
+}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_multiline_topLevel() async {
+    await testRequest(r'''
+/*
+ * [[^]]
+ */
+class A {}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_singleLine_member() async {
+    await testRequest(r'''
+class A {
+  // [[^]]
+  foo () {}
+}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inComment_singleLine_topLevel() async {
+    await testRequest(r'''
+// [[^]]
+class A {}
+''', SnippetContext.inComment);
+  }
+
+  Future<void> test_inExpression_functionCall() async {
+    await testRequest(r'''
+foo() {
+  print([[^]]
+}
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_inExtension() async {
+    await testRequest(r'''
+extension on Object {
+  [[^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inFunction_atEnd() async {
+    await testRequest(r'''
+foo() {
+  var a = 1;
+  [[^]]
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_atEnd_partialIdentifier() async {
+    await testRequest(r'''
+foo() {
+  var a = 1;
+  [[mysnip^]]
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_atStart() async {
+    await testRequest(r'''
+foo() {
+  [[^]]
+  var a = 1;
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_atStart_partialIdentifier() async {
+    await testRequest(r'''
+foo() {
+  [[mysnip^]]
+  var a = 1;
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_betweenStatements() async {
+    await testRequest(r'''
+foo() {
+  var a = 1;
+  [[^]]
+  var b = 1;
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_betweenStatements_partialIdentifier() async {
+    await testRequest(r'''
+foo() {
+  var a = 1;
+  [[mysnip^]]
+  var b = 1;
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_empty() async {
+    await testRequest(r'''
+foo() {
+  [[^]]
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inFunction_empty_partialIdentifier() async {
+    await testRequest(r'''
+foo() {
+  [[mysnip^]]
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_atEnd() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    var a = 1;
+    [[^]]
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_atEnd_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    var a = 1;
+    [[mysnip^]]
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_atStart() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    [[^]]
+    var a = 1;
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_atStart_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    [[mysnip^]]
+    var a = 1;
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_betweenStatements() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    var a = 1;
+    [[^]]
+    var b = 1;
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_betweenStatements_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    var a = 1;
+    [[mysnip^]]
+    var b = 1;
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_empty() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    [[^]]
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMethod_empty_partialIdentifier() async {
+    await testRequest(r'''
+class A {
+  foo() {
+    [[mysnip^]]
+  }
+}
+''', SnippetContext.inBlock);
+  }
+
+  Future<void> test_inMixin() async {
+    await testRequest(r'''
+mixin A {
+  [[^]]
+}
+''', SnippetContext.inClass);
+  }
+
+  Future<void> test_inStatement_forCondition() async {
+    await testRequest(r'''
+foo() {
+  for (var i = [[^]]
+}
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_inStatement_variableDeclaration() async {
+    await testRequest(r'''
+foo() {
+  var a = [[^]]
+}
+''', SnippetContext.inExpressionOrStatement);
+  }
+
+  Future<void> test_inString() async {
+    await testRequest(r'''
+const a = '[[^]]';
+''', SnippetContext.inString);
+  }
+
+  Future<void> test_inString_raw() async {
+    await testRequest(r'''
+const a = r'[[^]]';
+''', SnippetContext.inString);
+  }
+
+  Future<void> test_inString_unterminated() async {
+    await testRequest(r'''
+const a = r'[[^]]
+''', SnippetContext.inString);
+  }
+
+  Future<void> test_topLevel_atEnd() async {
+    await testRequest(r'''
+class A {}
+
+[[^]]
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_atEnd_partialIdentifier() async {
+    await testRequest(r'''
+class A {}
+
+[[mysnip^]]
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_atStart() async {
+    await testRequest(r'''
+[[^]]
+
+class A {}
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_atStart_partialIdentifier() async {
+    await testRequest(r'''
+[[mysnip^]]
+
+class A {}
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_betweenClasses() async {
+    await testRequest(r'''
+class A {}
+
+[[^]]
+
+class B {}
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_betweenClasses_partialIdentifier() async {
+    await testRequest(r'''
+class A {}
+
+[[mysnip^]]
+
+class B {}
+''', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_empty() async {
+    await testRequest('[[^]]', SnippetContext.atTopLevel);
+  }
+
+  Future<void> test_topLevel_empty_partialIdentifier() async {
+    await testRequest('[[mysnip^]]', SnippetContext.atTopLevel);
+  }
+
+  /// Checks that [code] produces a context of [expectedContext] where the
+  /// character '^' in [code] represents the supplied offset and the range
+  /// surrounded `[[` by brackets `]]` is the expected replacement range.
+  ///
+  /// `^`, `[[` and `]]` will be removed from the code before resolving.
+  Future<void> testRequest(String code, SnippetContext expectedContext) async {
+    code = normalizeNewlinesForPlatform(code);
+    final offset = offsetFromMarker(code);
+    final expectedReplacementRange = rangeFromMarkers(code);
+    await resolveTestCode(withoutMarkers(code));
+
+    final request = DartSnippetRequest(
+      unit: testAnalysisResult,
+      offset: offset,
+    );
+
+    expect(request.filePath, testFile);
+    expect(request.offset, offset);
+    expect(request.context, expectedContext);
+    expect(request.replacementRange, expectedReplacementRange);
+  }
+}
diff --git a/pkg/analysis_server/test/services/snippets/dart/test_all.dart b/pkg/analysis_server/test/services/snippets/dart/test_all.dart
new file mode 100644
index 0000000..907e05a
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/dart/test_all.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2014, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'flutter_snippet_producers_test.dart' as flutter_snippet_producers;
+import 'snippet_manager_test.dart' as snippet_manager;
+import 'snippet_request_test.dart' as snippet_request;
+
+void main() {
+  defineReflectiveSuite(() {
+    flutter_snippet_producers.main();
+    snippet_manager.main();
+    snippet_request.main();
+  }, name: 'dart');
+}
diff --git a/pkg/analysis_server/test/services/snippets/dart/test_support.dart b/pkg/analysis_server/test/services/snippets/dart/test_support.dart
new file mode 100644
index 0000000..9f2d740
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/dart/test_support.dart
@@ -0,0 +1,30 @@
+// 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:analyzer/source/source_range.dart';
+import 'package:test/test.dart';
+
+int offsetFromMarker(String code) {
+  final offset = withoutRangeMarkers(code).indexOf('^');
+  expect(offset, isNot(-1));
+  return offset;
+}
+
+SourceRange rangeFromMarkers(String code) {
+  code = _withoutPositionMarker(code);
+  final start = code.indexOf('[[');
+  final end = code.indexOf(']]');
+  expect(start, isNot(-1));
+  expect(end, isNot(-1));
+  final endAdjusted = end - 2; // Account for the [[ before this marker
+  return SourceRange(start, endAdjusted - start);
+}
+
+String withoutMarkers(String code) =>
+    withoutRangeMarkers(_withoutPositionMarker(code));
+
+String withoutRangeMarkers(String code) =>
+    code.replaceAll('[[', '').replaceAll(']]', '');
+
+String _withoutPositionMarker(String code) => code.replaceAll('^', '');
diff --git a/pkg/analysis_server/test/services/snippets/test_all.dart b/pkg/analysis_server/test/services/snippets/test_all.dart
new file mode 100644
index 0000000..899f5ae
--- /dev/null
+++ b/pkg/analysis_server/test/services/snippets/test_all.dart
@@ -0,0 +1,13 @@
+// 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'dart/test_all.dart' as dart_all;
+
+void main() {
+  defineReflectiveSuite(() {
+    dart_all.main();
+  }, name: 'snippets');
+}
diff --git a/pkg/analysis_server/test/services/test_all.dart b/pkg/analysis_server/test/services/test_all.dart
index 954023d..a6feeba 100644
--- a/pkg/analysis_server/test/services/test_all.dart
+++ b/pkg/analysis_server/test/services/test_all.dart
@@ -9,6 +9,7 @@
 import 'linter/test_all.dart' as linter_all;
 import 'refactoring/test_all.dart' as refactoring_all;
 import 'search/test_all.dart' as search_all;
+import 'snippets/test_all.dart' as snippets_all;
 
 void main() {
   defineReflectiveSuite(() {
@@ -17,5 +18,6 @@
     linter_all.main();
     refactoring_all.main();
     search_all.main();
+    snippets_all.main();
   });
 }
diff --git a/pkg/analysis_server/test/src/services/correction/assist/convert_class_to_enum_test.dart b/pkg/analysis_server/test/src/services/correction/assist/convert_class_to_enum_test.dart
new file mode 100644
index 0000000..931cc73
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/assist/convert_class_to_enum_test.dart
@@ -0,0 +1,516 @@
+// 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(ConvertClassToEnumTest);
+  });
+}
+
+@reflectiveTest
+class ConvertClassToEnumTest extends AssistProcessorTest {
+  @override
+  AssistKind get kind => DartAssistKind.CONVERT_CLASS_TO_ENUM;
+
+  Future<void> test_extends_object_privateClass() async {
+    await resolveTestCode('''
+class _E extends Object {
+  static const _E c = _E();
+
+  const _E();
+}
+''');
+    await assertHasAssistAt('E extends', '''
+enum _E {
+  c
+}
+''');
+  }
+
+  Future<void> test_extends_object_publicClass() async {
+    await resolveTestCode('''
+class E extends Object {
+  static const E c = E._();
+
+  const E._();
+}
+''');
+    await assertHasAssistAt('E extends', '''
+enum E {
+  c._();
+
+  const E._();
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_first_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(0, 'a');
+  static const _E c1 = _E(1, 'b');
+
+  final int index;
+
+  final String code;
+
+  const _E(this.index, this.code);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0('a'),
+  c1('b');
+
+  final String code;
+
+  const _E(this.code);
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_last_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E('a', 0);
+  static const _E c1 = _E('b', 1);
+
+  final String code;
+
+  final int index;
+
+  const _E(this.code, this.index);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0('a'),
+  c1('b');
+
+  final String code;
+
+  const _E(this.code);
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_middle_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E('a', 0, 'b');
+  static const _E c1 = _E('c', 1, 'd');
+
+  final String first;
+
+  final int index;
+
+  final String last;
+
+  const _E(this.first, this.index, this.last);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0('a', 'b'),
+  c1('c', 'd');
+
+  final String first;
+
+  final String last;
+
+  const _E(this.first, this.last);
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_only_outOfOrder() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(1);
+  static const _E c1 = _E(0);
+
+  final int index;
+
+  const _E(this.index);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c1,
+  c0
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_only_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(0);
+  static const _E c1 = _E(1);
+
+  final int index;
+
+  const _E(this.index);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0,
+  c1
+}
+''');
+  }
+
+  Future<void> test_index_namedIndex_only_publicClass() async {
+    await resolveTestCode('''
+class E {
+  static const E c0 = E._(0);
+  static const E c1 = E._(1);
+
+  final int index;
+
+  const E._(this.index);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum E {
+  c0._(),
+  c1._();
+
+  const E._();
+}
+''');
+  }
+
+  Future<void> test_index_notNamedIndex_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(0);
+  static const _E c1 = _E(1);
+
+  final int value;
+
+  const _E(this.value);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0(0),
+  c1(1);
+
+  final int value;
+
+  const _E(this.value);
+}
+''');
+  }
+
+  Future<void> test_index_notNamedIndex_publicClass() async {
+    await resolveTestCode('''
+class E {
+  static const E c0 = E._(0);
+  static const E c1 = E._(1);
+
+  final int value;
+
+  const E._(this.value);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum E {
+  c0._(0),
+  c1._(1);
+
+  final int value;
+
+  const E._(this.value);
+}
+''');
+  }
+
+  Future<void> test_invalid_abstractClass() async {
+    await resolveTestCode('''
+abstract class E {}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_constructorUsedInConstructor() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  // ignore: unused_element
+  const _E({_E e = const _E()});
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_constructorUsedOutsideClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+}
+_E get e => _E();
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_extended() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+}
+class F extends _E  {}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_extends_notObject() async {
+    await resolveTestCode('''
+class E extends C {
+  static const E c = E._();
+
+  const E._();
+}
+class C {
+  const C();
+}
+''');
+    await assertNoAssistAt('E extends');
+  }
+
+  Future<void> test_invalid_hasPart() async {
+    // Change this test if the assist becomes able to look for references to the
+    // class and its constructors in part files.
+    newFile('$testPackageLibPath/a.dart', content: '''
+part of 'test.dart';
+''');
+    await resolveTestCode('''
+part 'a.dart';
+
+class E {
+  static const E c = E._();
+
+  const E._();
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_implemented() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+}
+class F implements _E  {}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_indexFieldNotSequential() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(0);
+  static const _E c1 = _E(3);
+
+  final int index;
+
+  const _E(this.index);
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_multipleConstantsInSameFieldDeclaration() async {
+    // Change this test if support is added to cover cases where multiple
+    // constants are defined in a single field declaration.
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E('a'), c1 = _E('b');
+
+  final String s;
+
+  const _E(this.s);
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_nonConstConstructor() async {
+    await resolveTestCode('''
+class _E {
+  static _E c = _E();
+
+  _E();
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_overrides_equal() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+
+  @override
+  int get hashCode => 0;
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_invalid_overrides_hashCode() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+
+  @override
+  bool operator ==(Object other) => true;
+}
+''');
+    await assertNoAssistAt('E {');
+  }
+
+  Future<void> test_minimal_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c = _E();
+
+  const _E();
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c
+}
+''');
+  }
+
+  Future<void> test_minimal_publicClass() async {
+    await resolveTestCode('''
+class E {
+  static const E c = E._();
+
+  const E._();
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum E {
+  c._();
+
+  const E._();
+}
+''');
+  }
+
+  Future<void> test_noIndex_int_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E(2);
+  static const _E c1 = _E(4);
+
+  final int count;
+
+  const _E(this.count);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0(2),
+  c1(4);
+
+  final int count;
+
+  const _E(this.count);
+}
+''');
+  }
+
+  Future<void> test_noIndex_int_publicClass() async {
+    await resolveTestCode('''
+class E {
+  static const E c0 = E._(2);
+  static const E c1 = E._(4);
+
+  final int count;
+
+  const E._(this.count);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum E {
+  c0._(2),
+  c1._(4);
+
+  final int count;
+
+  const E._(this.count);
+}
+''');
+  }
+
+  Future<void> test_noIndex_notInt_privateClass() async {
+    await resolveTestCode('''
+class _E {
+  static const _E c0 = _E('c0');
+  static const _E c1 = _E('c1');
+
+  final String name;
+
+  const _E(this.name);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum _E {
+  c0('c0'),
+  c1('c1');
+
+  final String name;
+
+  const _E(this.name);
+}
+''');
+  }
+
+  Future<void> test_noIndex_notInt_publicClass() async {
+    await resolveTestCode('''
+class E {
+  static const E c0 = E._('c0');
+  static const E c1 = E._('c1');
+
+  final String name;
+
+  const E._(this.name);
+}
+''');
+    await assertHasAssistAt('E {', '''
+enum E {
+  c0._('c0'),
+  c1._('c1');
+
+  final String name;
+
+  const E._(this.name);
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
index e61b6f7..2c05865 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
@@ -9,6 +9,7 @@
 import 'add_return_type_test.dart' as add_return_type;
 import 'add_type_annotation_test.dart' as add_type_annotation;
 import 'assign_to_local_variable_test.dart' as assign_to_local_variable;
+import 'convert_class_to_enum_test.dart' as convert_class_to_enum;
 import 'convert_class_to_mixin_test.dart' as convert_class_to_mixin;
 import 'convert_documentation_into_block_test.dart'
     as convert_documentation_into_block;
@@ -97,6 +98,7 @@
     add_return_type.main();
     add_type_annotation.main();
     assign_to_local_variable.main();
+    convert_class_to_enum.main();
     convert_class_to_mixin.main();
     convert_documentation_into_block.main();
     convert_documentation_into_line.main();
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 29f3b33..9e07497 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -44,16 +44,17 @@
   Token get beginToken => _strings.beginToken!;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll(_strings);
-
-  @override
   Token get endToken => _strings.endToken!;
 
   @override
   NodeListImpl<StringLiteral> get strings => _strings;
 
   @override
+  ChildEntities get _childEntities {
+    return ChildEntities()..addNodeList('strings', strings);
+  }
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAdjacentStrings(this);
 
   @override
@@ -126,17 +127,11 @@
     ]..sort(AstNode.LEXICAL_ORDER);
   }
 
-  /// Return a holder of child entities that subclasses can add to.
+  @override
   ChildEntities get _childEntities {
-    ChildEntities result = ChildEntities();
-    if (_commentIsBeforeAnnotations()) {
-      result
-        ..add(_comment)
-        ..addAll(_metadata);
-    } else {
-      result.addAll(sortedCommentAndAnnotations);
-    }
-    return result;
+    return ChildEntities()
+      ..addNode('documentationComment', documentationComment)
+      ..addNodeList('metadata', metadata);
   }
 
   @override
@@ -237,15 +232,6 @@
   Token get beginToken => atSign;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(atSign)
-    ..add(_name)
-    ..add(_typeArguments)
-    ..add(period)
-    ..add(_constructorName)
-    ..add(_arguments);
-
-  @override
   SimpleIdentifierImpl? get constructorName => _constructorName;
 
   set constructorName(SimpleIdentifier? name) {
@@ -296,6 +282,17 @@
   }
 
   @override
+  ChildEntities get _childEntities {
+    return ChildEntities()
+      ..addToken('atSign', atSign)
+      ..addNode('name', name)
+      ..addNode('typeArguments', typeArguments)
+      ..addToken('period', period)
+      ..addNode('constructorName', constructorName)
+      ..addNode('arguments', arguments);
+  }
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAnnotation(this);
 
   @override
@@ -349,13 +346,6 @@
   @override
   Token get beginToken => leftParenthesis;
 
-  @override
-  // TODO(paulberry): Add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftParenthesis)
-    ..addAll(_arguments)
-    ..add(rightParenthesis);
-
   List<ParameterElement?>? get correspondingStaticParameters =>
       _correspondingStaticParameters;
 
@@ -371,6 +361,13 @@
   Token get endToken => rightParenthesis;
 
   @override
+  // TODO(paulberry): Add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNodeList('arguments', arguments)
+    ..addToken('rightParenthesis', rightParenthesis);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitArgumentList(this);
 
   @override
@@ -429,12 +426,6 @@
   Token get beginToken => _expression.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_expression)
-    ..add(asOperator)
-    ..add(_type);
-
-  @override
   Token get endToken => _type.endToken;
 
   @override
@@ -455,6 +446,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('expression', expression)
+    ..addToken('asOperator', asOperator)
+    ..addNode('type', type);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAsExpression(this);
 
   @override
@@ -500,15 +497,6 @@
   Token get beginToken => assertKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(assertKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(comma)
-    ..add(_message)
-    ..add(rightParenthesis);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression condition) {
@@ -526,6 +514,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('assertKeyword', assertKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('comma', comma)
+    ..addNode('message', message)
+    ..addToken('rightParenthesis', rightParenthesis);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAssertInitializer(this);
 
   @override
@@ -573,16 +570,6 @@
   Token get beginToken => assertKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(assertKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(comma)
-    ..add(_message)
-    ..add(rightParenthesis)
-    ..add(semicolon);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression condition) {
@@ -600,6 +587,16 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('assertKeyword', assertKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('comma', comma)
+    ..addNode('message', message)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAssertStatement(this);
 
   @override
@@ -644,12 +641,6 @@
   Token get beginToken => _leftHandSide.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_leftHandSide)
-    ..add(operator)
-    ..add(_rightHandSide);
-
-  @override
   Token get endToken => _rightHandSide.endToken;
 
   @override
@@ -670,6 +661,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('leftHandSide', leftHandSide)
+    ..addToken('operator', operator)
+    ..addNode('rightHandSide', rightHandSide);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   /// If the AST structure has been resolved, and the function being invoked is
@@ -724,6 +721,10 @@
   Map<String, Object>? _propertyMap;
 
   @override
+  Iterable<SyntacticEntity> get childEntities =>
+      _childEntities.syntacticEntities;
+
+  @override
   int get end => offset + length;
 
   @override
@@ -736,6 +737,11 @@
     return endToken.offset + endToken.length - beginToken.offset;
   }
 
+  /// Return properties (tokens and nodes) of this node, with names, in the
+  /// order in which these entities should normally appear, not necessary in
+  /// the order they really are (because of recovery).
+  Iterable<ChildEntity> get namedChildEntities => _childEntities.entities;
+
   @override
   int get offset {
     final beginToken = this.beginToken;
@@ -756,6 +762,8 @@
     return root;
   }
 
+  ChildEntities get _childEntities => ChildEntities();
+
   void detachFromParent() {
     _parent = null;
   }
@@ -845,11 +853,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(awaitKeyword)
-    ..add(_expression);
-
-  @override
   Token get endToken => _expression.endToken;
 
   @override
@@ -863,6 +866,11 @@
   Precedence get precedence => Precedence.prefix;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('awaitKeyword', awaitKeyword)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitAwaitExpression(this);
 
   @override
@@ -905,12 +913,6 @@
   Token get beginToken => _leftOperand.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_leftOperand)
-    ..add(operator)
-    ..add(_rightOperand);
-
-  @override
   Token get endToken => _rightOperand.endToken;
 
   @override
@@ -931,6 +933,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('leftOperand', leftOperand)
+    ..addToken('operator', operator)
+    ..addNode('rightOperand', rightOperand);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitBinaryExpression(this);
 
   @override
@@ -983,12 +991,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(keyword)
-    ..add(star)
-    ..add(_block);
-
-  @override
   Token get endToken => _block.endToken;
 
   @override
@@ -1001,6 +1003,12 @@
   bool get isSynchronous => keyword?.lexeme != Keyword.ASYNC.lexeme;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('keyword', keyword)
+    ..addToken('star', star)
+    ..addNode('block', block);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitBlockFunctionBody(this);
 
   @override
@@ -1038,18 +1046,18 @@
   Token get beginToken => leftBracket;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftBracket)
-    ..addAll(_statements)
-    ..add(rightBracket);
-
-  @override
   Token get endToken => rightBracket;
 
   @override
   NodeListImpl<Statement> get statements => _statements;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('statements', statements)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitBlock(this);
 
   @override
@@ -1078,15 +1086,16 @@
   Token get beginToken => literal;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(literal);
-
-  @override
   Token get endToken => literal;
 
   @override
   bool get isSynthetic => literal.isSynthetic;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('literal', literal);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitBooleanLiteral(this);
 
   @override
@@ -1131,12 +1140,6 @@
   Token get beginToken => breakKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(breakKeyword)
-    ..add(_label)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -1147,6 +1150,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('breakKeyword', breakKeyword)
+    ..addNode('label', label)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitBreakStatement(this);
 
   @override
@@ -1192,11 +1201,6 @@
   NodeListImpl<Expression> get cascadeSections => _cascadeSections;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_target)
-    ..addAll(_cascadeSections);
-
-  @override
   Token get endToken => _cascadeSections.endToken!;
 
   @override
@@ -1215,6 +1219,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('target', target)
+    ..addNodeList('cascadeSections', cascadeSections);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => null;
 
   @override
@@ -1316,18 +1325,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(onKeyword)
-    ..add(_exceptionType)
-    ..add(catchKeyword)
-    ..add(leftParenthesis)
-    ..add(_exceptionParameter)
-    ..add(comma)
-    ..add(_stackTraceParameter)
-    ..add(rightParenthesis)
-    ..add(_body);
-
-  @override
   Token get endToken => _body.endToken;
 
   @override
@@ -1352,6 +1349,18 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('onKeyword', onKeyword)
+    ..addNode('exceptionType', exceptionType)
+    ..addToken('catchKeyword', catchKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('exceptionParameter', exceptionParameter)
+    ..addToken('comma', comma)
+    ..addNode('stackTraceParameter', stackTraceParameter)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitCatchClause(this);
 
   @override
@@ -1364,28 +1373,81 @@
 }
 
 /// Helper class to allow iteration of child entities of an AST node.
-class ChildEntities
-    with IterableMixin<SyntacticEntity>
-    implements Iterable<SyntacticEntity> {
+class ChildEntities {
   /// The list of child entities to be iterated over.
-  final List<SyntacticEntity> _entities = [];
+  final List<ChildEntity> entities = [];
 
-  @override
-  Iterator<SyntacticEntity> get iterator => _entities.iterator;
+  List<SyntacticEntity> get syntacticEntities {
+    var result = <SyntacticEntity>[];
+    for (var entity in entities) {
+      var entityValue = entity.value;
+      if (entityValue is SyntacticEntity) {
+        result.add(entityValue);
+      } else if (entityValue is List<Object>) {
+        for (var element in entityValue) {
+          if (element is SyntacticEntity) {
+            result.add(element);
+          }
+        }
+      }
+    }
 
-  /// Add an AST node or token as the next child entity, if it is not `null`.
-  void add(SyntacticEntity? entity) {
-    if (entity != null) {
-      _entities.add(entity);
+    var needsSorting = false;
+    int? lastOffset;
+    for (var entity in result) {
+      if (lastOffset != null && lastOffset > entity.offset) {
+        needsSorting = true;
+        break;
+      }
+      lastOffset = entity.offset;
+    }
+
+    if (needsSorting) {
+      result.sort((a, b) => a.offset - b.offset);
+    }
+
+    return result;
+  }
+
+  void addAll(ChildEntities other) {
+    entities.addAll(other.entities);
+  }
+
+  void addNode(String name, AstNode? value) {
+    if (value != null) {
+      entities.add(
+        ChildEntity(name, value),
+      );
     }
   }
 
-  /// Add the given items as the next child entities, if [items] is not `null`.
-  void addAll(Iterable<SyntacticEntity>? items) {
-    if (items != null) {
-      _entities.addAll(items);
+  void addNodeList(String name, List<AstNode> value) {
+    entities.add(
+      ChildEntity(name, value),
+    );
+  }
+
+  void addToken(String name, Token? value) {
+    if (value != null) {
+      entities.add(
+        ChildEntity(name, value),
+      );
     }
   }
+
+  void addTokenList(String name, List<Token> value) {
+    entities.add(
+      ChildEntity(name, value),
+    );
+  }
+}
+
+/// A named child of an [AstNode], usually a token, node, or a list of nodes.
+class ChildEntity {
+  final String name;
+  final Object value;
+
+  ChildEntity(this.name, this.value);
 }
 
 /// The declaration of a class.
@@ -1455,22 +1517,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(abstractKeyword)
-    ..add(macroKeyword)
-    ..add(augmentKeyword)
-    ..add(classKeyword)
-    ..add(_name)
-    ..add(_typeParameters)
-    ..add(_extendsClause)
-    ..add(_withClause)
-    ..add(_implementsClause)
-    ..add(_nativeClause)
-    ..add(leftBracket)
-    ..addAll(members)
-    ..add(rightBracket);
-
-  @override
   ClassElement? get declaredElement => _name.staticElement as ClassElement?;
 
   @override
@@ -1503,6 +1549,22 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('abstractKeyword', abstractKeyword)
+    ..addToken('macroKeyword', macroKeyword)
+    ..addToken('augmentKeyword', augmentKeyword)
+    ..addToken('classKeyword', classKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('extendsClause', extendsClause)
+    ..addNode('withClause', withClause)
+    ..addNode('implementsClause', implementsClause)
+    ..addNode('nativeClause', nativeClause)
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('members', members)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitClassDeclaration(this);
 
   @override
@@ -1710,20 +1772,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(typedefKeyword)
-    ..add(_name)
-    ..add(_typeParameters)
-    ..add(equals)
-    ..add(abstractKeyword)
-    ..add(macroKeyword)
-    ..add(augmentKeyword)
-    ..add(_superclass)
-    ..add(_withClause)
-    ..add(_implementsClause)
-    ..add(semicolon);
-
-  @override
   ClassElement? get declaredElement => _name.staticElement as ClassElement?;
 
   @override
@@ -1768,6 +1816,20 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('typedefKeyword', typedefKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addToken('equals', equals)
+    ..addToken('abstractKeyword', abstractKeyword)
+    ..addToken('macroKeyword', macroKeyword)
+    ..addToken('augmentKeyword', augmentKeyword)
+    ..addNode('superclass', superclass)
+    ..addNode('withClause', withClause)
+    ..addNode('implementsClause', implementsClause)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitClassTypeAlias(this);
 
   @override
@@ -1848,10 +1910,6 @@
   Token get beginToken => tokens[0];
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll(tokens);
-
-  @override
   Token get endToken => tokens[tokens.length - 1];
 
   @override
@@ -1867,6 +1925,10 @@
   NodeListImpl<CommentReference> get references => _references;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addTokenList('tokens', tokens);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitComment(this);
 
   @override
@@ -1920,11 +1982,6 @@
   Token get beginToken => newKeyword ?? _expression.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(newKeyword)
-    ..add(_expression);
-
-  @override
   Token get endToken => _expression.endToken;
 
   @override
@@ -1944,6 +2001,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('newKeyword', newKeyword)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitCommentReference(this);
 
   @override
@@ -2048,19 +2110,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities {
-    ChildEntities result = ChildEntities()..add(_scriptTag);
-    if (_directivesAreBeforeDeclarations) {
-      result
-        ..addAll(_directives)
-        ..addAll(_declarations);
-    } else {
-      result.addAll(sortedDirectivesAndDeclarations);
-    }
-    return result;
-  }
-
-  @override
   NodeListImpl<CompilationUnitMember> get declarations => _declarations;
 
   @override
@@ -2111,6 +2160,14 @@
     ]..sort(AstNode.LEXICAL_ORDER);
   }
 
+  @override
+  ChildEntities get _childEntities {
+    return ChildEntities()
+      ..addNode('scriptTag', scriptTag)
+      ..addNodeList('directives', directives)
+      ..addNodeList('declarations', declarations);
+  }
+
   /// Return `true` if all of the directives are lexically before any
   /// declarations.
   bool get _directivesAreBeforeDeclarations {
@@ -2211,14 +2268,6 @@
   Token get beginToken => _condition.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_condition)
-    ..add(question)
-    ..add(_thenExpression)
-    ..add(colon)
-    ..add(_elseExpression);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression expression) {
@@ -2246,6 +2295,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('condition', condition)
+    ..addToken('question', question)
+    ..addNode('thenExpression', thenExpression)
+    ..addToken('colon', colon)
+    ..addNode('elseExpression', elseExpression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitConditionalExpression(this);
 
@@ -2300,16 +2357,6 @@
   Token get beginToken => ifKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(ifKeyword)
-    ..add(leftParenthesis)
-    ..add(_name)
-    ..add(equalToken)
-    ..add(_value)
-    ..add(rightParenthesis)
-    ..add(_uri);
-
-  @override
   Token get endToken => _uri.endToken;
 
   @override
@@ -2334,6 +2381,16 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('ifKeyword', ifKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('name', name)
+    ..addToken('equalToken', equalToken)
+    ..addNode('value', value)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('uri', uri);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitConfiguration(this);
 
   @override
@@ -2478,20 +2535,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(externalKeyword)
-    ..add(constKeyword)
-    ..add(factoryKeyword)
-    ..add(_returnType)
-    ..add(period)
-    ..add(_name)
-    ..add(_parameters)
-    ..add(separator)
-    ..addAll(initializers)
-    ..add(_redirectedConstructor)
-    ..add(_body);
-
-  @override
   Token get endToken {
     return _body.endToken;
   }
@@ -2536,6 +2579,20 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('externalKeyword', externalKeyword)
+    ..addToken('constKeyword', constKeyword)
+    ..addToken('factoryKeyword', factoryKeyword)
+    ..addNode('returnType', returnType)
+    ..addToken('period', period)
+    ..addNode('name', name)
+    ..addNode('parameters', parameters)
+    ..addToken('separator', separator)
+    ..addNodeList('initializers', initializers)
+    ..addNode('redirectedConstructor', redirectedConstructor)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitConstructorDeclaration(this);
 
@@ -2594,14 +2651,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(thisKeyword)
-    ..add(period)
-    ..add(_fieldName)
-    ..add(equals)
-    ..add(_expression);
-
-  @override
   Token get endToken => _expression.endToken;
 
   @override
@@ -2619,6 +2668,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('thisKeyword', thisKeyword)
+    ..addToken('period', period)
+    ..addNode('fieldName', fieldName)
+    ..addToken('equals', equals)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitConstructorFieldInitializer(this);
 
@@ -2672,12 +2729,6 @@
   Token get beginToken => _type.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_type)
-    ..add(period)
-    ..add(_name);
-
-  @override
   Token get endToken {
     if (_name != null) {
       return _name!.endToken;
@@ -2704,6 +2755,12 @@
   NamedTypeImpl get type2 => _type;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('type', type)
+    ..addToken('period', period)
+    ..addNode('name', name);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitConstructorName(this);
 
   @override
@@ -2731,10 +2788,6 @@
   Token get beginToken => constructorName.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(constructorName);
-
-  @override
   ConstructorNameImpl get constructorName => _constructorName;
 
   set constructorName(ConstructorNameImpl value) {
@@ -2748,6 +2801,10 @@
   Precedence get precedence => Precedence.postfix;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addNode('constructorName', constructorName);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitConstructorReference(this);
 
@@ -2776,12 +2833,12 @@
   Token get beginToken => period;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(period)
-    ..add(name);
+  Token get endToken => name.token;
 
   @override
-  Token get endToken => name.token;
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('period', period)
+    ..addNode('name', name);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) {
@@ -2827,12 +2884,6 @@
   Token get beginToken => continueKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(continueKeyword)
-    ..add(_label)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -2843,6 +2894,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('continueKeyword', continueKeyword)
+    ..addNode('label', label)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitContinueStatement(this);
 
   @override
@@ -2892,12 +2949,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_type)
-    ..add(_identifier);
-
-  @override
   LocalVariableElement? get declaredElement {
     return _identifier.staticElement as LocalVariableElement;
   }
@@ -2931,6 +2982,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('type', type)
+    ..addNode('identifier', identifier);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitDeclaredIdentifier(this);
 
   @override
@@ -2996,12 +3053,6 @@
   Token get beginToken => _parameter.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_parameter)
-    ..add(separator)
-    ..add(_defaultValue);
-
-  @override
   Token? get covariantKeyword => null;
 
   @override
@@ -3045,6 +3096,12 @@
   Token? get requiredKeyword => null;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('parameter', parameter)
+    ..addToken('separator', separator)
+    ..addNode('defaultValue', defaultValue);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitDefaultFormalParameter(this);
 
@@ -3138,16 +3195,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(doKeyword)
-    ..add(_body)
-    ..add(whileKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(rightParenthesis)
-    ..add(semicolon);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression expression) {
@@ -3158,6 +3205,16 @@
   Token get endToken => semicolon;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('doKeyword', doKeyword)
+    ..addNode('body', body)
+    ..addToken('whileKeyword', whileKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitDoStatement(this);
 
   @override
@@ -3184,17 +3241,17 @@
   Token get beginToken => _components.beginToken!;
 
   @override
-  // TODO(paulberry): add "." tokens.
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll(_components);
-
-  @override
   NodeListImpl<SimpleIdentifier> get components => _components;
 
   @override
   Token get endToken => _components.endToken!;
 
   @override
+  // TODO(paulberry): add "." tokens.
+  ChildEntities get _childEntities =>
+      ChildEntities()..addNodeList('components', components);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitDottedName(this);
 
   @override
@@ -3227,10 +3284,11 @@
   Token get beginToken => literal;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(literal);
+  Token get endToken => literal;
 
   @override
-  Token get endToken => literal;
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('literal', literal);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitDoubleLiteral(this);
@@ -3260,11 +3318,11 @@
   Token get beginToken => semicolon;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(semicolon);
+  Token get endToken => semicolon;
 
   @override
-  Token get endToken => semicolon;
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('semicolon', semicolon);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitEmptyFunctionBody(this);
@@ -3295,16 +3353,16 @@
   Token get beginToken => semicolon;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
   bool get isSynthetic => semicolon.isSynthetic;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitEmptyStatement(this);
 
   @override
@@ -3339,13 +3397,13 @@
       (typeArguments ?? constructorSelector ?? argumentList).beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(typeArguments)
-    ..add(constructorSelector)
-    ..add(argumentList);
+  Token get endToken => argumentList.endToken;
 
   @override
-  Token get endToken => argumentList.endToken;
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('typeArguments', typeArguments)
+    ..addNode('constructorSelector', constructorSelector)
+    ..addNode('argumentList', argumentList);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) {
@@ -3387,11 +3445,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(_name)
-    ..add(arguments);
-
-  @override
   FieldElement get declaredElement => _name.staticElement as FieldElement;
 
   @override
@@ -3408,6 +3461,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addNode('name', name)
+    ..addNode('arguments', arguments);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitEnumConstantDeclaration(this);
 
@@ -3486,19 +3544,6 @@
   }
 
   @override
-  // TODO(brianwilkerson) Add commas?
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(enumKeyword)
-    ..add(_name)
-    ..add(_typeParameters)
-    ..add(_withClause)
-    ..add(_implementsClause)
-    ..add(leftBracket)
-    ..addAll(_constants)
-    ..addAll(_members)
-    ..add(rightBracket);
-
-  @override
   NodeListImpl<EnumConstantDeclaration> get constants => _constants;
 
   @override
@@ -3536,6 +3581,19 @@
   }
 
   @override
+  // TODO(brianwilkerson) Add commas?
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('enumKeyword', enumKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('withClause', withClause)
+    ..addNode('implementsClause', implementsClause)
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('constants', constants)
+    ..addNodeList('members', members)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitEnumDeclaration(this);
 
   @override
@@ -3581,13 +3639,6 @@
             combinators, semicolon);
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_uri)
-    ..addAll(combinators)
-    ..add(semicolon);
-
-  @override
   ExportElement? get element => super.element as ExportElement?;
 
   @override
@@ -3596,6 +3647,13 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('uri', uri)
+    ..addNodeList('combinators', combinators)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitExportDirective(this);
 
   @override
@@ -3654,14 +3712,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(keyword)
-    ..add(star)
-    ..add(functionDefinition)
-    ..add(_expression)
-    ..add(semicolon);
-
-  @override
   Token get endToken {
     if (semicolon != null) {
       return semicolon!;
@@ -3686,6 +3736,14 @@
   bool get isSynchronous => keyword?.lexeme != Keyword.ASYNC.lexeme;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('keyword', keyword)
+    ..addToken('star', star)
+    ..addToken('functionDefinition', functionDefinition)
+    ..addNode('expression', expression)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitExpressionFunctionBody(this);
 
@@ -3817,11 +3875,6 @@
   Token get beginToken => _expression.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_expression)
-    ..add(semicolon);
-
-  @override
   Token get endToken {
     if (semicolon != null) {
       return semicolon!;
@@ -3841,6 +3894,11 @@
       _expression.isSynthetic && (semicolon == null || semicolon!.isSynthetic);
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('expression', expression)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitExpressionStatement(this);
 
   @override
@@ -3870,11 +3928,6 @@
   Token get beginToken => extendsKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(extendsKeyword)
-    ..add(_superclass);
-
-  @override
   Token get endToken => _superclass.endToken;
 
   @override
@@ -3889,6 +3942,11 @@
   NamedTypeImpl get superclass2 => _superclass;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('extendsKeyword', extendsKeyword)
+    ..addNode('superclass', superclass);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitExtendsClause(this);
 
   @override
@@ -3967,17 +4025,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(extensionKeyword)
-    ..add(name)
-    ..add(typeParameters)
-    ..add(onKeyword)
-    ..add(extendedType)
-    ..add(leftBracket)
-    ..addAll(members)
-    ..add(rightBracket);
-
-  @override
   ExtensionElement? get declaredElement => _declaredElement;
 
   /// Set the element declared by this declaration to the given [element].
@@ -4030,6 +4077,17 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('extensionKeyword', extensionKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addToken('onKeyword', onKeyword)
+    ..addNode('extendedType', extendedType)
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('members', members)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitExtensionDeclaration(this);
 
@@ -4085,12 +4143,6 @@
   Token get beginToken => _extensionName.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_extensionName)
-    ..add(_typeArguments)
-    ..add(_argumentList);
-
-  @override
   Token get endToken => _argumentList.endToken;
 
   @override
@@ -4123,6 +4175,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('extensionName', extensionName)
+    ..addNode('typeArguments', typeArguments)
+    ..addNode('argumentList', argumentList);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) {
     return visitor.visitExtensionOverride(this);
   }
@@ -4180,12 +4238,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(staticKeyword)
-    ..add(_fieldList)
-    ..add(semicolon);
-
-  @override
   Element? get declaredElement => null;
 
   @override
@@ -4209,6 +4261,12 @@
   bool get isStatic => staticKeyword != null;
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('staticKeyword', staticKeyword)
+    ..addNode('fields', fields)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFieldDeclaration(this);
 
   @override
@@ -4299,15 +4357,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_type)
-    ..add(thisKeyword)
-    ..add(period)
-    ..add(identifier)
-    ..add(_parameters);
-
-  @override
   Token get endToken {
     return question ?? _parameters?.endToken ?? identifier.endToken;
   }
@@ -4343,6 +4392,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('type', type)
+    ..addToken('thisKeyword', thisKeyword)
+    ..addToken('period', period)
+    ..addNode('identifier', identifier)
+    ..addNode('parameters', parameters);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitFieldFormalParameter(this);
 
@@ -4375,11 +4433,6 @@
   Token get beginToken => inKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(inKeyword)
-    ..add(_iterable);
-
-  @override
   Token get endToken => _iterable.endToken;
 
   @override
@@ -4390,6 +4443,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('inKeyword', inKeyword)
+    ..addNode('iterable', iterable);
+
+  @override
   void visitChildren(AstVisitor visitor) {
     _iterable.accept(visitor);
   }
@@ -4412,11 +4470,6 @@
   Token get beginToken => _loopVariable.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_loopVariable)
-    ..addAll(super.childEntities);
-
-  @override
   DeclaredIdentifierImpl get loopVariable => _loopVariable;
 
   set loopVariable(DeclaredIdentifier variable) {
@@ -4424,6 +4477,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('loopVariable', loopVariable)
+    ..addAll(super._childEntities);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitForEachPartsWithDeclaration(this);
 
@@ -4451,11 +4509,6 @@
   Token get beginToken => _identifier.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_identifier)
-    ..addAll(super.childEntities);
-
-  @override
   SimpleIdentifierImpl get identifier => _identifier;
 
   set identifier(SimpleIdentifier identifier) {
@@ -4463,6 +4516,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('identifier', identifier)
+    ..addAll(super._childEntities);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitForEachPartsWithIdentifier(this);
 
@@ -4509,15 +4567,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(awaitKeyword)
-    ..add(forKeyword)
-    ..add(leftParenthesis)
-    ..add(_forLoopParts)
-    ..add(rightParenthesis)
-    ..add(_body);
-
-  @override
   Token get endToken => _body.endToken;
 
   @override
@@ -4528,6 +4577,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('awaitKeyword', awaitKeyword)
+    ..addToken('forKeyword', forKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('forLoopParts', forLoopParts)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitForElement(this);
 
   @override
@@ -4670,25 +4728,6 @@
   Token get beginToken => leftParenthesis;
 
   @override
-  Iterable<SyntacticEntity> get childEntities {
-    // TODO(paulberry): include commas.
-    ChildEntities result = ChildEntities()..add(leftParenthesis);
-    bool leftDelimiterNeeded = leftDelimiter != null;
-    int length = _parameters.length;
-    for (int i = 0; i < length; i++) {
-      FormalParameter parameter = _parameters[i];
-      if (leftDelimiterNeeded && leftDelimiter!.offset < parameter.offset) {
-        result.add(leftDelimiter);
-        leftDelimiterNeeded = false;
-      }
-      result.add(parameter);
-    }
-    return result
-      ..add(rightDelimiter)
-      ..add(rightParenthesis);
-  }
-
-  @override
   Token get endToken => rightParenthesis;
 
   @override
@@ -4705,6 +4744,25 @@
   NodeListImpl<FormalParameter> get parameters => _parameters;
 
   @override
+  ChildEntities get _childEntities {
+    // TODO(paulberry): include commas.
+    var result = ChildEntities()..addToken('leftParenthesis', leftParenthesis);
+    bool leftDelimiterNeeded = leftDelimiter != null;
+    int length = _parameters.length;
+    for (int i = 0; i < length; i++) {
+      FormalParameter parameter = _parameters[i];
+      if (leftDelimiterNeeded && leftDelimiter!.offset < parameter.offset) {
+        result..addToken('leftDelimiter', leftDelimiter);
+        leftDelimiterNeeded = false;
+      }
+      result..addNode('parameter', parameter);
+    }
+    return result
+      ..addToken('rightDelimiter', rightDelimiter)
+      ..addToken('rightParenthesis', rightParenthesis);
+  }
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFormalParameterList(this);
 
   @override
@@ -4741,13 +4799,6 @@
   Token get beginToken => leftSeparator;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftSeparator)
-    ..add(_condition)
-    ..add(rightSeparator)
-    ..addAll(_updaters);
-
-  @override
   ExpressionImpl? get condition => _condition;
 
   set condition(Expression? expression) {
@@ -4761,6 +4812,13 @@
   NodeListImpl<Expression> get updaters => _updaters;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftSeparator', leftSeparator)
+    ..addNode('condition', condition)
+    ..addToken('rightSeparator', rightSeparator)
+    ..addNodeList('updaters', updaters);
+
+  @override
   void visitChildren(AstVisitor visitor) {
     _condition?.accept(visitor);
     _updaters.accept(visitor);
@@ -4791,11 +4849,6 @@
   Token get beginToken => _variableList.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_variableList)
-    ..addAll(super.childEntities);
-
-  @override
   VariableDeclarationListImpl get variables => _variableList;
 
   set variables(VariableDeclarationList? variableList) {
@@ -4804,6 +4857,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('variables', variables)
+    ..addAll(super._childEntities);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitForPartsWithDeclarations(this);
 
@@ -4838,11 +4896,6 @@
   Token get beginToken => initialization?.beginToken ?? super.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_initialization)
-    ..addAll(super.childEntities);
-
-  @override
   ExpressionImpl? get initialization => _initialization;
 
   set initialization(Expression? initialization) {
@@ -4850,6 +4903,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('initialization', initialization)
+    ..addAll(super._childEntities);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitForPartsWithExpression(this);
 
@@ -4896,15 +4954,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(awaitKeyword)
-    ..add(forKeyword)
-    ..add(leftParenthesis)
-    ..add(_forLoopParts)
-    ..add(rightParenthesis)
-    ..add(_body);
-
-  @override
   Token get endToken => _body.endToken;
 
   @override
@@ -4915,6 +4964,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('awaitKeyword', awaitKeyword)
+    ..addToken('forKeyword', forKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('forLoopParts', forLoopParts)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitForStatement(this);
 
   @override
@@ -5027,14 +5085,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(externalKeyword)
-    ..add(_returnType)
-    ..add(propertyKeyword)
-    ..add(_name)
-    ..add(_functionExpression);
-
-  @override
   ExecutableElement? get declaredElement =>
       _name.staticElement as ExecutableElement?;
 
@@ -5071,6 +5121,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('externalKeyword', externalKeyword)
+    ..addNode('returnType', returnType)
+    ..addToken('propertyKeyword', propertyKeyword)
+    ..addNode('name', name)
+    ..addNode('functionExpression', functionExpression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFunctionDeclaration(this);
 
   @override
@@ -5097,10 +5155,6 @@
   Token get beginToken => _functionDeclaration.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(_functionDeclaration);
-
-  @override
   Token get endToken => _functionDeclaration.endToken;
 
   @override
@@ -5112,6 +5166,10 @@
   }
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addNode('functionDeclaration', functionDeclaration);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitFunctionDeclarationStatement(this);
 
@@ -5171,12 +5229,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_typeParameters)
-    ..add(_parameters)
-    ..add(_body);
-
-  @override
   Token get endToken {
     return _body.endToken;
   }
@@ -5199,6 +5251,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('parameters', parameters)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFunctionExpression(this);
 
   @override
@@ -5239,11 +5297,6 @@
   Token get beginToken => _function.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_function)
-    ..add(_argumentList);
-
-  @override
   Token get endToken => _argumentList.endToken;
 
   @override
@@ -5257,6 +5310,11 @@
   Precedence get precedence => Precedence.postfix;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('function', function)
+    ..addNode('argumentList', argumentList);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   @override
@@ -5295,11 +5353,6 @@
   Token get beginToken => function.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(function)
-    ..add(typeArguments);
-
-  @override
   Token get endToken => typeArguments?.endToken ?? function.endToken;
 
   @override
@@ -5321,6 +5374,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('function', function)
+    ..addNode('typeArguments', typeArguments);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFunctionReference(this);
 
   @override
@@ -5370,15 +5428,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(typedefKeyword)
-    ..add(_returnType)
-    ..add(_name)
-    ..add(_typeParameters)
-    ..add(_parameters)
-    ..add(semicolon);
-
-  @override
   TypeAliasElement? get declaredElement =>
       _name.staticElement as TypeAliasElement?;
 
@@ -5404,6 +5453,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('typedefKeyword', typedefKeyword)
+    ..addNode('returnType', returnType)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('parameters', parameters)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitFunctionTypeAlias(this);
 
   @override
@@ -5474,12 +5532,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(_returnType)
-    ..add(identifier)
-    ..add(parameters);
-
-  @override
   Token get endToken => question ?? _parameters.endToken;
 
   @override
@@ -5513,6 +5565,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addNode('returnType', returnType)
+    ..addNode('identifier', identifier)
+    ..addNode('parameters', parameters);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitFunctionTypedFormalParameter(this);
 
@@ -5593,14 +5651,6 @@
   Token get beginToken => _returnType?.beginToken ?? functionKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_returnType)
-    ..add(functionKeyword)
-    ..add(_typeParameters)
-    ..add(_parameters)
-    ..add(question);
-
-  @override
   Token get endToken => question ?? _parameters.endToken;
 
   @override
@@ -5629,6 +5679,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('returnType', returnType)
+    ..addToken('functionKeyword', functionKeyword)
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('parameters', parameters)
+    ..addToken('question', question);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) {
     return visitor.visitGenericFunctionType(this);
   }
@@ -5676,15 +5734,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..addAll(metadata)
-    ..add(typedefKeyword)
-    ..add(name)
-    ..add(_typeParameters)
-    ..add(equals)
-    ..add(_type);
-
-  @override
   Element? get declaredElement => name.staticElement;
 
   /// The type of function being defined by the alias.
@@ -5718,6 +5767,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNodeList('metadata', metadata)
+    ..addToken('typedefKeyword', typedefKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addToken('equals', equals)
+    ..addNode('type', type);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) {
     return visitor.visitGenericTypeAlias(this);
   }
@@ -5752,17 +5810,17 @@
   Token get beginToken => hideKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(hideKeyword)
-    ..addAll(elements);
-
-  @override
   NodeListImpl<ShowHideClauseElement> get elements => _elements;
 
   @override
   Token get endToken => _elements.endToken!;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('hideKeyword', hideKeyword)
+    ..addNodeList('elements', elements);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitHideClause(this);
 
   @override
@@ -5787,17 +5845,17 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(keyword)
-    ..addAll(_hiddenNames);
-
-  @override
   Token get endToken => _hiddenNames.endToken!;
 
   @override
   NodeListImpl<SimpleIdentifier> get hiddenNames => _hiddenNames;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('keyword', keyword)
+    ..addNodeList('hiddenNames', hiddenNames);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitHideCombinator(this);
 
   @override
@@ -5858,16 +5916,6 @@
   Token get beginToken => ifKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(ifKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(rightParenthesis)
-    ..add(_thenElement)
-    ..add(elseKeyword)
-    ..add(_elseElement);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression condition) {
@@ -5892,6 +5940,16 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('ifKeyword', ifKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('thenElement', thenElement)
+    ..addToken('elseKeyword', elseKeyword)
+    ..addNode('elseElement', elseElement);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitIfElement(this);
 
   @override
@@ -5954,16 +6012,6 @@
   Token get beginToken => ifKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(ifKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(rightParenthesis)
-    ..add(_thenStatement)
-    ..add(elseKeyword)
-    ..add(_elseStatement);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression condition) {
@@ -5993,6 +6041,16 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('ifKeyword', ifKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('thenStatement', thenStatement)
+    ..addToken('elseKeyword', elseKeyword)
+    ..addNode('elseStatement', elseStatement);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitIfStatement(this);
 
   @override
@@ -6024,12 +6082,6 @@
   Token get beginToken => implementsKeyword;
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(implementsKeyword)
-    ..addAll(interfaces);
-
-  @override
   Token get endToken => _interfaces.endToken!;
 
   @override
@@ -6040,6 +6092,12 @@
   NodeListImpl<NamedType> get interfaces2 => _interfaces;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('implementsKeyword', implementsKeyword)
+    ..addNodeList('interfaces', interfaces);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitImplementsClause(this);
 
   @override
@@ -6074,11 +6132,6 @@
   Token get beginToken => expression.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(expression)
-    ..add(typeArguments);
-
-  @override
   Token get endToken => typeArguments?.endToken ?? expression.endToken;
 
   @override
@@ -6100,6 +6153,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('expression', expression)
+    ..addNode('typeArguments', typeArguments);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) {
     return visitor.visitImplicitCallReference(this);
   }
@@ -6157,16 +6215,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_uri)
-    ..add(deferredKeyword)
-    ..add(asKeyword)
-    ..add(_prefix)
-    ..addAll(combinators)
-    ..add(semicolon);
-
-  @override
   ImportElement? get element => super.element as ImportElement?;
 
   @override
@@ -6182,6 +6230,16 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('uri', uri)
+    ..addToken('deferredKeyword', deferredKeyword)
+    ..addToken('asKeyword', asKeyword)
+    ..addNode('prefix', prefix)
+    ..addNodeList('combinators', combinators)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitImportDirective(this);
 
   @override
@@ -6249,14 +6307,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_target)
-    ..add(period)
-    ..add(leftBracket)
-    ..add(_index)
-    ..add(rightBracket);
-
-  @override
   Token get endToken => rightBracket;
 
   @override
@@ -6314,6 +6364,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('target', target)
+    ..addToken('period', period)
+    ..addToken('leftBracket', leftBracket)
+    ..addNode('index', index)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   AstNode get _nullShortingExtensionCandidate => parent!;
 
   /// If the AST structure has been resolved, and the function being invoked is
@@ -6428,13 +6486,6 @@
   Token get beginToken => keyword ?? _constructorName.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(keyword)
-    ..add(_constructorName)
-    ..add(_typeArguments)
-    ..add(_argumentList);
-
-  @override
   ConstructorNameImpl get constructorName => _constructorName;
 
   set constructorName(ConstructorName name) {
@@ -6474,6 +6525,13 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('keyword', keyword)
+    ..addNode('constructorName', constructorName)
+    ..addNode('typeArguments', typeArguments)
+    ..addNode('argumentList', argumentList);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitInstanceCreationExpression(this);
 
@@ -6513,9 +6571,6 @@
   Token get beginToken => literal;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(literal);
-
-  @override
   Token get endToken => literal;
 
   /// Returns whether this literal's [parent] is a [PrefixExpression] of unary
@@ -6531,6 +6586,10 @@
   }
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('literal', literal);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitIntegerLiteral(this);
 
   @override
@@ -6631,12 +6690,6 @@
   Token get beginToken => leftBracket;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftBracket)
-    ..add(_expression)
-    ..add(rightBracket);
-
-  @override
   Token get endToken => rightBracket ?? _expression.endToken;
 
   @override
@@ -6647,6 +6700,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftBracket', leftBracket)
+    ..addNode('expression', expression)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitInterpolationExpression(this);
 
@@ -6678,9 +6737,6 @@
   Token get beginToken => contents;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(contents);
-
-  @override
   int get contentsEnd => offset + _lexemeHelper.end;
 
   @override
@@ -6692,6 +6748,10 @@
   @override
   StringInterpolation get parent => super.parent as StringInterpolation;
 
+  @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('contents', contents);
+
   StringLexemeHelper get _lexemeHelper {
     String lexeme = contents.lexeme;
     return StringLexemeHelper(lexeme, identical(this, parent.elements.first),
@@ -6774,13 +6834,6 @@
   Token get beginToken => _expression.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_expression)
-    ..add(isOperator)
-    ..add(notOperator)
-    ..add(_type);
-
-  @override
   Token get endToken => _type.endToken;
 
   @override
@@ -6801,6 +6854,13 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('expression', expression)
+    ..addToken('isOperator', isOperator)
+    ..addToken('notOperator', notOperator)
+    ..addNode('type', type);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitIsExpression(this);
 
   @override
@@ -6836,11 +6896,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..addAll(_labels)
-    ..add(_statement);
-
-  @override
   Token get endToken => _statement.endToken;
 
   @override
@@ -6857,6 +6912,11 @@
   StatementImpl get unlabeled => _statement.unlabeled;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNodeList('labels', labels)
+    ..addNode('statement', statement);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitLabeledStatement(this);
 
   @override
@@ -6887,11 +6947,6 @@
   Token get beginToken => _label.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_label)
-    ..add(colon);
-
-  @override
   Token get endToken => colon;
 
   @override
@@ -6902,6 +6957,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('label', label)
+    ..addToken('colon', colon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitLabel(this);
 
   @override
@@ -6936,12 +6996,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(libraryKeyword)
-    ..add(_name)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -6958,6 +7012,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('libraryKeyword', libraryKeyword)
+    ..addNode('name', name)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitLibraryDirective(this);
 
   @override
@@ -6985,11 +7045,6 @@
   Token get beginToken => _components.beginToken!;
 
   @override
-  // TODO(paulberry): add "." tokens.
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll(_components);
-
-  @override
   NodeListImpl<SimpleIdentifier> get components => _components;
 
   @override
@@ -7019,6 +7074,11 @@
   Element? get staticElement => null;
 
   @override
+  // TODO(paulberry): add "." tokens.
+  ChildEntities get _childEntities =>
+      ChildEntities()..addNodeList('components', components);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitLibraryIdentifier(this);
 
   @override
@@ -7077,19 +7137,19 @@
   }
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(leftBracket)
-    ..addAll(_elements)
-    ..add(rightBracket);
-
-  @override
   NodeListImpl<CollectionElement> get elements => _elements;
 
   @override
   Token get endToken => rightBracket;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('elements', elements)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitListLiteral(this);
 
   @override
@@ -7153,12 +7213,6 @@
   Token get beginToken => _key.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_key)
-    ..add(separator)
-    ..add(_value);
-
-  @override
   Token get endToken => _value.endToken;
 
   @override
@@ -7176,6 +7230,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('key', key)
+    ..addToken('separator', separator)
+    ..addNode('value', value);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitMapLiteralEntry(this);
 
   @override
@@ -7278,17 +7338,6 @@
     _body = _becomeParentOf(functionBody as FunctionBodyImpl);
   }
 
-  @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(externalKeyword)
-    ..add(modifierKeyword)
-    ..add(_returnType)
-    ..add(propertyKeyword)
-    ..add(operatorKeyword)
-    ..add(_name)
-    ..add(_parameters)
-    ..add(_body);
-
   /// Return the element associated with this method, or `null` if the AST
   /// structure has not been resolved. The element can either be a
   /// [MethodElement], if this represents the declaration of a normal method, or
@@ -7357,6 +7406,17 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('externalKeyword', externalKeyword)
+    ..addToken('modifierKeyword', modifierKeyword)
+    ..addNode('returnType', returnType)
+    ..addToken('propertyKeyword', propertyKeyword)
+    ..addToken('operatorKeyword', operatorKeyword)
+    ..addNode('name', name)
+    ..addNode('parameters', parameters)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitMethodDeclaration(this);
 
   @override
@@ -7419,13 +7479,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_target)
-    ..add(operator)
-    ..add(_methodName)
-    ..add(_argumentList);
-
-  @override
   Token get endToken => _argumentList.endToken;
 
   @override
@@ -7501,6 +7554,13 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('target', target)
+    ..addToken('operator', operator)
+    ..addNode('methodName', methodName)
+    ..addNode('argumentList', argumentList);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   @override
@@ -7556,17 +7616,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(mixinKeyword)
-    ..add(_name)
-    ..add(_typeParameters)
-    ..add(_onClause)
-    ..add(_implementsClause)
-    ..add(leftBracket)
-    ..addAll(members)
-    ..add(rightBracket);
-
-  @override
   ClassElement? get declaredElement => _name.staticElement as ClassElement?;
 
   @override
@@ -7591,6 +7640,17 @@
   TypeParameterListImpl? get typeParameters => _typeParameters;
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('mixinKeyword', mixinKeyword)
+    ..addNode('name', name)
+    ..addNode('typeParameters', typeParameters)
+    ..addNode('onClause', onClause)
+    ..addNode('implementsClause', implementsClause)
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('members', members)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitMixinDeclaration(this);
 
   @override
@@ -7649,11 +7709,6 @@
   Token get beginToken => _name.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_name)
-    ..add(_expression);
-
-  @override
   ParameterElement? get element {
     var element = _name.label.staticElement;
     if (element is ParameterElement) {
@@ -7683,6 +7738,11 @@
   Precedence get precedence => Precedence.none;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('name', name)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitNamedExpression(this);
 
   @override
@@ -7724,12 +7784,6 @@
   Token get beginToken => _name.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_name)
-    ..add(_typeArguments)
-    ..add(question);
-
-  @override
   Token get endToken => question ?? _typeArguments?.endToken ?? _name.endToken;
 
   @override
@@ -7759,6 +7813,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('name', name)
+    ..addNode('typeArguments', typeArguments)
+    ..addToken('question', question);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitNamedType(this);
 
   @override
@@ -7850,11 +7910,6 @@
   Token get beginToken => nativeKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(nativeKeyword)
-    ..add(_name);
-
-  @override
   Token get endToken {
     return _name?.endToken ?? nativeKeyword;
   }
@@ -7867,6 +7922,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('nativeKeyword', nativeKeyword)
+    ..addNode('name', name);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitNativeClause(this);
 
   @override
@@ -7905,12 +7965,6 @@
   Token get beginToken => nativeKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(nativeKeyword)
-    ..add(_stringLiteral)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -7921,6 +7975,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('nativeKeyword', nativeKeyword)
+    ..addNode('stringLiteral', stringLiteral)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitNativeFunctionBody(this);
 
   @override
@@ -8123,19 +8183,13 @@
     ]..sort(AstNode.LEXICAL_ORDER);
   }
 
+  @override
   ChildEntities get _childEntities {
-    ChildEntities result = ChildEntities();
-    if (_commentIsBeforeAnnotations()) {
-      result
-        ..add(_comment)
-        ..addAll(_metadata);
-    } else {
-      result.addAll(sortedCommentAndAnnotations);
-    }
-    result
-      ..add(requiredKeyword)
-      ..add(covariantKeyword);
-    return result;
+    return ChildEntities()
+      ..addNode('documentationComment', documentationComment)
+      ..addNodeList('metadata', metadata)
+      ..addToken('requiredKeyword', requiredKeyword)
+      ..addToken('covariantKeyword', covariantKeyword);
   }
 
   @override
@@ -8182,10 +8236,11 @@
   Token get beginToken => literal;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(literal);
+  Token get endToken => literal;
 
   @override
-  Token get endToken => literal;
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('literal', literal);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitNullLiteral(this);
@@ -8243,12 +8298,6 @@
   Token get beginToken => onKeyword;
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(onKeyword)
-    ..addAll(superclassConstraints);
-
-  @override
   Token get endToken => _superclassConstraints.endToken!;
 
   @override
@@ -8259,6 +8308,12 @@
   NodeListImpl<NamedType> get superclassConstraints2 => _superclassConstraints;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('onKeyword', onKeyword)
+    ..addNodeList('superclassConstraints', superclassConstraints);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitOnClause(this);
 
   @override
@@ -8294,12 +8349,6 @@
   Token get beginToken => leftParenthesis;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftParenthesis)
-    ..add(_expression)
-    ..add(rightParenthesis);
-
-  @override
   Token get endToken => rightParenthesis;
 
   @override
@@ -8324,6 +8373,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('expression', expression)
+    ..addToken('rightParenthesis', rightParenthesis);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitParenthesizedExpression(this);
 
@@ -8354,12 +8409,6 @@
       : super(comment, metadata, partUri);
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(partKeyword)
-    ..add(_uri)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -8372,6 +8421,12 @@
   CompilationUnitElement? get uriElement => element as CompilationUnitElement?;
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('partKeyword', partKeyword)
+    ..addNode('uri', uri)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitPartDirective(this);
 }
 
@@ -8417,14 +8472,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(partKeyword)
-    ..add(ofKeyword)
-    ..add(_uri)
-    ..add(_libraryName)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -8448,6 +8495,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('partKeyword', partKeyword)
+    ..addToken('ofKeyword', ofKeyword)
+    ..addNode('uri', uri)
+    ..addNode('libraryName', libraryName)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitPartOfDirective(this);
 
   @override
@@ -8487,11 +8542,6 @@
   Token get beginToken => _operand.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_operand)
-    ..add(operator);
-
-  @override
   Token get endToken => operator;
 
   @override
@@ -8505,6 +8555,11 @@
   Precedence get precedence => Precedence.postfix;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('operand', operand)
+    ..addToken('operator', operator);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   /// If the AST structure has been resolved, and the function being invoked is
@@ -8561,12 +8616,6 @@
   Token get beginToken => _prefix.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_prefix)
-    ..add(period)
-    ..add(_identifier);
-
-  @override
   Token get endToken => _identifier.endToken;
 
   @override
@@ -8609,6 +8658,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('prefix', prefix)
+    ..addToken('period', period)
+    ..addNode('identifier', identifier);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitPrefixedIdentifier(this);
 
   @override
@@ -8647,11 +8702,6 @@
   Token get beginToken => operator;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(operator)
-    ..add(_operand);
-
-  @override
   Token get endToken => _operand.endToken;
 
   @override
@@ -8665,6 +8715,11 @@
   Precedence get precedence => Precedence.prefix;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('operator', operator)
+    ..addNode('operand', operand);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   /// If the AST structure has been resolved, and the function being invoked is
@@ -8731,12 +8786,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_target)
-    ..add(operator)
-    ..add(_propertyName);
-
-  @override
   Token get endToken => _propertyName.endToken;
 
   @override
@@ -8794,6 +8843,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('target', target)
+    ..addToken('operator', operator)
+    ..addNode('propertyName', propertyName);
+
+  @override
   AstNode? get _nullShortingExtensionCandidate => parent;
 
   @override
@@ -8859,13 +8914,6 @@
   Token get beginToken => thisKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(thisKeyword)
-    ..add(period)
-    ..add(_constructorName)
-    ..add(_argumentList);
-
-  @override
   SimpleIdentifierImpl? get constructorName => _constructorName;
 
   set constructorName(SimpleIdentifier? identifier) {
@@ -8876,6 +8924,13 @@
   Token get endToken => _argumentList.endToken;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('thisKeyword', thisKeyword)
+    ..addToken('period', period)
+    ..addNode('constructorName', constructorName)
+    ..addNode('argumentList', argumentList);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitRedirectingConstructorInvocation(this);
 
@@ -8903,16 +8958,16 @@
   Token get beginToken => rethrowKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(rethrowKeyword);
-
-  @override
   Token get endToken => rethrowKeyword;
 
   @override
   Precedence get precedence => Precedence.assignment;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('rethrowKeyword', rethrowKeyword);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitRethrowExpression(this);
 
   @override
@@ -8948,12 +9003,6 @@
   Token get beginToken => returnKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(returnKeyword)
-    ..add(_expression)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -8964,6 +9013,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('returnKeyword', returnKeyword)
+    ..addNode('expression', expression)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitReturnStatement(this);
 
   @override
@@ -8989,11 +9044,11 @@
   Token get beginToken => scriptTag;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(scriptTag);
+  Token get endToken => scriptTag;
 
   @override
-  Token get endToken => scriptTag;
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('scriptTag', scriptTag);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitScriptTag(this);
@@ -9054,13 +9109,6 @@
   }
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(leftBracket)
-    ..addAll(elements)
-    ..add(rightBracket);
-
-  @override
   NodeListImpl<CollectionElement> get elements => _elements;
 
   @override
@@ -9073,6 +9121,13 @@
   bool get isSet => _resolvedKind == _SetOrMapKind.set;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('elements', elements)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSetOrMapLiteral(this);
 
   void becomeMap() {
@@ -9119,17 +9174,17 @@
   Token get beginToken => showKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(showKeyword)
-    ..addAll(elements);
-
-  @override
   NodeListImpl<ShowHideClauseElement> get elements => _elements;
 
   @override
   Token get endToken => _elements.endToken!;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('showKeyword', showKeyword)
+    ..addNodeList('elements', elements);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitShowClause(this);
 
   @override
@@ -9155,18 +9210,18 @@
   }
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(keyword)
-    ..addAll(_shownNames);
-
-  @override
   Token get endToken => _shownNames.endToken!;
 
   @override
   NodeListImpl<SimpleIdentifier> get shownNames => _shownNames;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('keyword', keyword)
+    ..addNodeList('shownNames', shownNames);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitShowCombinator(this);
 
   @override
@@ -9199,11 +9254,12 @@
   Token get beginToken => modifier ?? name.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll([if (modifier != null) modifier!, name]);
+  Token get endToken => name.endToken;
 
   @override
-  Token get endToken => name.endToken;
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('modifier', modifier)
+    ..addNode('name', name);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitShowHideElement(this);
@@ -9270,12 +9326,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_type)
-    ..add(identifier);
-
-  @override
   Token get endToken => identifier?.endToken ?? type!.endToken;
 
   @override
@@ -9292,6 +9342,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('type', type)
+    ..addNode('identifier', identifier);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitSimpleFormalParameter(this);
 
@@ -9350,9 +9406,6 @@
   Token get beginToken => token;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(token);
-
-  @override
   Token get endToken => token;
 
   @override
@@ -9416,6 +9469,9 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()..addToken('token', token);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSimpleIdentifier(this);
 
   @override
@@ -9551,9 +9607,6 @@
   Token get beginToken => literal;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()..add(literal);
-
-  @override
   int get contentsEnd => offset + _helper.end;
 
   @override
@@ -9574,6 +9627,10 @@
   @override
   bool get isSynthetic => literal.isSynthetic;
 
+  @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('literal', literal);
+
   StringLexemeHelper get _helper {
     return StringLexemeHelper(literal.lexeme, true, true);
   }
@@ -9615,11 +9672,6 @@
   Token get beginToken => spreadOperator;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(spreadOperator)
-    ..add(_expression);
-
-  @override
   Token get endToken => _expression.endToken;
 
   @override
@@ -9634,6 +9686,11 @@
       spreadOperator.type == TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('spreadOperator', spreadOperator)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) {
     return visitor.visitSpreadElement(this);
   }
@@ -9705,10 +9762,6 @@
   Token get beginToken => _elements.beginToken!;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..addAll(_elements);
-
-  @override
   int get contentsEnd {
     var element = _elements.last as InterpolationString;
     return element.contentsEnd;
@@ -9744,6 +9797,10 @@
   InterpolationStringImpl get lastString =>
       elements.last as InterpolationStringImpl;
 
+  @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addNodeList('elements', elements);
+
   StringLexemeHelper get _firstHelper {
     var lastString = _elements.first as InterpolationString;
     String lexeme = lastString.contents.lexeme;
@@ -9927,13 +9984,6 @@
   Token get beginToken => superKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(superKeyword)
-    ..add(period)
-    ..add(_constructorName)
-    ..add(_argumentList);
-
-  @override
   SimpleIdentifierImpl? get constructorName => _constructorName;
 
   set constructorName(SimpleIdentifier? identifier) {
@@ -9944,6 +9994,13 @@
   Token get endToken => _argumentList.endToken;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('superKeyword', superKeyword)
+    ..addToken('period', period)
+    ..addNode('constructorName', constructorName)
+    ..addNode('argumentList', argumentList);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitSuperConstructorInvocation(this);
 
@@ -9970,16 +10027,16 @@
   Token get beginToken => superKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(superKeyword);
-
-  @override
   Token get endToken => superKeyword;
 
   @override
   Precedence get precedence => Precedence.primary;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('superKeyword', superKeyword);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSuperExpression(this);
 
   @override
@@ -10069,15 +10126,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_type)
-    ..add(superKeyword)
-    ..add(period)
-    ..add(identifier)
-    ..add(_parameters);
-
-  @override
   Token get endToken {
     return question ?? _parameters?.endToken ?? identifier.endToken;
   }
@@ -10113,6 +10161,15 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('type', type)
+    ..addToken('superKeyword', superKeyword)
+    ..addToken('period', period)
+    ..addNode('identifier', identifier)
+    ..addNode('parameters', parameters);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitSuperFormalParameter(this);
 
@@ -10143,14 +10200,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..addAll(labels)
-    ..add(keyword)
-    ..add(_expression)
-    ..add(colon)
-    ..addAll(statements);
-
-  @override
   ExpressionImpl get expression => _expression;
 
   set expression(Expression expression) {
@@ -10158,6 +10207,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNodeList('labels', labels)
+    ..addToken('keyword', keyword)
+    ..addNode('expression', expression)
+    ..addToken('colon', colon)
+    ..addNodeList('statements', statements);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSwitchCase(this);
 
   @override
@@ -10180,11 +10237,11 @@
       : super(labels, keyword, colon, statements);
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..addAll(labels)
-    ..add(keyword)
-    ..add(colon)
-    ..addAll(statements);
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNodeList('labels', labels)
+    ..addToken('keyword', keyword)
+    ..addToken('colon', colon)
+    ..addNodeList('statements', statements);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSwitchDefault(this);
@@ -10297,16 +10354,6 @@
   Token get beginToken => switchKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(switchKeyword)
-    ..add(leftParenthesis)
-    ..add(_expression)
-    ..add(rightParenthesis)
-    ..add(leftBracket)
-    ..addAll(_members)
-    ..add(rightBracket);
-
-  @override
   Token get endToken => rightBracket;
 
   @override
@@ -10320,6 +10367,16 @@
   NodeListImpl<SwitchMember> get members => _members;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('switchKeyword', switchKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('expression', expression)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('members', members)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSwitchStatement(this);
 
   @override
@@ -10349,13 +10406,13 @@
   Token get beginToken => poundSign;
 
   @override
-  // TODO(paulberry): add "." tokens.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(poundSign)
-    ..addAll(components);
+  Token get endToken => components[components.length - 1];
 
   @override
-  Token get endToken => components[components.length - 1];
+  // TODO(paulberry): add "." tokens.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('poundSign', poundSign)
+    ..addTokenList('components', components);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitSymbolLiteral(this);
@@ -10382,16 +10439,16 @@
   Token get beginToken => thisKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(thisKeyword);
-
-  @override
   Token get endToken => thisKeyword;
 
   @override
   Precedence get precedence => Precedence.primary;
 
   @override
+  ChildEntities get _childEntities =>
+      ChildEntities()..addToken('thisKeyword', thisKeyword);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitThisExpression(this);
 
   @override
@@ -10421,11 +10478,6 @@
   Token get beginToken => throwKeyword;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(throwKeyword)
-    ..add(_expression);
-
-  @override
   Token get endToken {
     return _expression.endToken;
   }
@@ -10441,6 +10493,11 @@
   Precedence get precedence => Precedence.assignment;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('throwKeyword', throwKeyword)
+    ..addNode('expression', expression);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitThrowExpression(this);
 
   @override
@@ -10480,11 +10537,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(_variableList)
-    ..add(semicolon);
-
-  @override
   Element? get declaredElement => null;
 
   @override
@@ -10502,6 +10554,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addNode('variables', variables)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitTopLevelVariableDeclaration(this);
 
@@ -10563,14 +10620,6 @@
   NodeListImpl<CatchClause> get catchClauses => _catchClauses;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(tryKeyword)
-    ..add(_body)
-    ..addAll(_catchClauses)
-    ..add(finallyKeyword)
-    ..add(_finallyBlock);
-
-  @override
   Token get endToken {
     if (_finallyBlock != null) {
       return _finallyBlock!.endToken;
@@ -10590,6 +10639,14 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('tryKeyword', tryKeyword)
+    ..addNode('body', body)
+    ..addNodeList('catchClauses', catchClauses)
+    ..addToken('finallyKeyword', finallyKeyword)
+    ..addNode('finallyBlock', finallyBlock);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitTryStatement(this);
 
   @override
@@ -10669,14 +10726,14 @@
   Token get beginToken => leftBracket;
 
   @override
-  // TODO(paulberry): Add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftBracket)
-    ..addAll(_arguments)
-    ..add(rightBracket);
+  Token get endToken => rightBracket;
 
   @override
-  Token get endToken => rightBracket;
+  // TODO(paulberry): Add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('arguments', arguments)
+    ..addToken('rightBracket', rightBracket);
 
   @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitTypeArgumentList(this);
@@ -10721,9 +10778,10 @@
     _typeArguments = _becomeParentOf(typeArguments as TypeArgumentListImpl?);
   }
 
+  @override
   ChildEntities get _childEntities => ChildEntities()
-    ..add(constKeyword)
-    ..add(_typeArguments);
+    ..addToken('constKeyword', constKeyword)
+    ..addNode('typeArguments', typeArguments);
 
   @override
   void visitChildren(AstVisitor visitor) {
@@ -10753,10 +10811,6 @@
   Token get beginToken => _typeName.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities =>
-      ChildEntities()..add(_typeName);
-
-  @override
   Token get endToken => _typeName.endToken;
 
   @override
@@ -10772,6 +10826,9 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()..addNode('type', type);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitTypeLiteral(this);
 
   @override
@@ -10822,12 +10879,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(_name)
-    ..add(extendsKeyword)
-    ..add(_bound);
-
-  @override
   TypeParameterElement? get declaredElement =>
       _name.staticElement as TypeParameterElement?;
 
@@ -10850,6 +10901,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addNode('name', name)
+    ..addToken('extendsKeyword', extendsKeyword)
+    ..addNode('bound', bound);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitTypeParameter(this);
 
   @override
@@ -10886,18 +10943,18 @@
   Token get beginToken => leftBracket;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(leftBracket)
-    ..addAll(_typeParameters)
-    ..add(rightBracket);
-
-  @override
   Token get endToken => rightBracket;
 
   @override
   NodeListImpl<TypeParameter> get typeParameters => _typeParameters;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('leftBracket', leftBracket)
+    ..addNodeList('typeParameters', typeParameters)
+    ..addToken('rightBracket', rightBracket);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitTypeParameterList(this);
 
   @override
@@ -11031,12 +11088,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(_name)
-    ..add(equals)
-    ..add(_initializer);
-
-  @override
   VariableElement? get declaredElement =>
       _name.staticElement as VariableElement?;
 
@@ -11099,6 +11150,12 @@
   }
 
   @override
+  ChildEntities get _childEntities => super._childEntities
+    ..addNode('name', name)
+    ..addToken('equals', equals)
+    ..addNode('initializer', initializer);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitVariableDeclaration(this);
 
   @override
@@ -11156,13 +11213,6 @@
   }
 
   @override
-  // TODO(paulberry): include commas.
-  Iterable<SyntacticEntity> get childEntities => super._childEntities
-    ..add(keyword)
-    ..add(_type)
-    ..addAll(_variables);
-
-  @override
   Token get endToken => _variables.endToken!;
 
   @override
@@ -11192,6 +11242,13 @@
   NodeListImpl<VariableDeclaration> get variables => _variables;
 
   @override
+  // TODO(paulberry): include commas.
+  ChildEntities get _childEntities => super._childEntities
+    ..addToken('keyword', keyword)
+    ..addNode('type', type)
+    ..addNodeList('variables', variables);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitVariableDeclarationList(this);
 
@@ -11226,11 +11283,6 @@
   Token get beginToken => _variableList.beginToken;
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(_variableList)
-    ..add(semicolon);
-
-  @override
   Token get endToken => semicolon;
 
   @override
@@ -11241,6 +11293,11 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addNode('variables', variables)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) =>
       visitor.visitVariableDeclarationStatement(this);
 
@@ -11291,14 +11348,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(whileKeyword)
-    ..add(leftParenthesis)
-    ..add(_condition)
-    ..add(rightParenthesis)
-    ..add(_body);
-
-  @override
   ExpressionImpl get condition => _condition;
 
   set condition(Expression expression) {
@@ -11309,6 +11358,14 @@
   Token get endToken => _body.endToken;
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('whileKeyword', whileKeyword)
+    ..addToken('leftParenthesis', leftParenthesis)
+    ..addNode('condition', condition)
+    ..addToken('rightParenthesis', rightParenthesis)
+    ..addNode('body', body);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitWhileStatement(this);
 
   @override
@@ -11339,12 +11396,6 @@
   Token get beginToken => withKeyword;
 
   @override
-  // TODO(paulberry): add commas.
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(withKeyword)
-    ..addAll(_mixinTypes);
-
-  @override
   Token get endToken => _mixinTypes.endToken!;
 
   @override
@@ -11355,6 +11406,12 @@
   NodeListImpl<NamedType> get mixinTypes2 => _mixinTypes;
 
   @override
+  // TODO(paulberry): add commas.
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('withKeyword', withKeyword)
+    ..addNodeList('mixinTypes', mixinTypes);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitWithClause(this);
 
   @override
@@ -11396,13 +11453,6 @@
   }
 
   @override
-  Iterable<SyntacticEntity> get childEntities => ChildEntities()
-    ..add(yieldKeyword)
-    ..add(star)
-    ..add(_expression)
-    ..add(semicolon);
-
-  @override
   Token get endToken {
     return semicolon;
   }
@@ -11415,6 +11465,13 @@
   }
 
   @override
+  ChildEntities get _childEntities => ChildEntities()
+    ..addToken('yieldKeyword', yieldKeyword)
+    ..addToken('star', star)
+    ..addNode('expression', expression)
+    ..addToken('semicolon', semicolon);
+
+  @override
   E? accept<E>(AstVisitor<E> visitor) => visitor.visitYieldStatement(this);
 
   @override
diff --git a/pkg/analyzer/lib/src/error/required_parameters_verifier.dart b/pkg/analyzer/lib/src/error/required_parameters_verifier.dart
index c5d9146..0e553d3 100644
--- a/pkg/analyzer/lib/src/error/required_parameters_verifier.dart
+++ b/pkg/analyzer/lib/src/error/required_parameters_verifier.dart
@@ -20,9 +20,9 @@
   @override
   void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
     _check(
-      node.constructorElement?.parameters,
-      node.arguments?.argumentList.arguments ?? <Expression>[],
-      node.name,
+      parameters: node.constructorElement?.parameters,
+      arguments: node.arguments?.argumentList.arguments ?? <Expression>[],
+      errorNode: node.name,
     );
   }
 
@@ -31,9 +31,9 @@
     var type = node.staticInvokeType;
     if (type is FunctionType) {
       _check(
-        type.parameters,
-        node.argumentList.arguments,
-        node,
+        parameters: type.parameters,
+        arguments: node.argumentList.arguments,
+        errorNode: node,
       );
     }
   }
@@ -41,9 +41,9 @@
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     _check(
-      node.constructorName.staticElement?.parameters,
-      node.argumentList.arguments,
-      node.constructorName,
+      parameters: node.constructorName.staticElement?.parameters,
+      arguments: node.argumentList.arguments,
+      errorNode: node.constructorName,
     );
   }
 
@@ -52,16 +52,19 @@
     if (node.methodName.name == FunctionElement.CALL_METHOD_NAME) {
       var targetType = node.realTarget?.staticType;
       if (targetType is FunctionType) {
-        _check(targetType.parameters, node.argumentList.arguments,
-            node.argumentList);
+        _check(
+          parameters: targetType.parameters,
+          arguments: node.argumentList.arguments,
+          errorNode: node.argumentList,
+        );
         return;
       }
     }
 
     _check(
-      _executableElement(node.methodName.staticElement)?.parameters,
-      node.argumentList.arguments,
-      node.methodName,
+      parameters: _executableElement(node.methodName.staticElement)?.parameters,
+      arguments: node.argumentList.arguments,
+      errorNode: node.methodName,
     );
   }
 
@@ -69,26 +72,31 @@
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     _check(
-      _executableElement(node.staticElement)?.parameters,
-      node.argumentList.arguments,
-      node,
+      parameters: _executableElement(node.staticElement)?.parameters,
+      arguments: node.argumentList.arguments,
+      errorNode: node,
     );
   }
 
   @override
-  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+  void visitSuperConstructorInvocation(
+    SuperConstructorInvocation node, {
+    ConstructorElement? enclosingConstructor,
+  }) {
     _check(
-      _executableElement(node.staticElement)?.parameters,
-      node.argumentList.arguments,
-      node,
+      parameters: _executableElement(node.staticElement)?.parameters,
+      enclosingConstructor: enclosingConstructor,
+      arguments: node.argumentList.arguments,
+      errorNode: node,
     );
   }
 
-  void _check(
-    List<ParameterElement>? parameters,
-    List<Expression> arguments,
-    AstNode node,
-  ) {
+  void _check({
+    required List<ParameterElement>? parameters,
+    ConstructorElement? enclosingConstructor,
+    required List<Expression> arguments,
+    required AstNode errorNode,
+  }) {
     if (parameters == null) {
       return;
     }
@@ -96,10 +104,11 @@
     for (ParameterElement parameter in parameters) {
       if (parameter.isRequiredNamed) {
         String parameterName = parameter.name;
-        if (!_containsNamedExpression(arguments, parameterName)) {
+        if (!_containsNamedExpression(
+            enclosingConstructor, arguments, parameterName)) {
           _errorReporter.reportErrorForNode(
             CompileTimeErrorCode.MISSING_REQUIRED_ARGUMENT,
-            node,
+            errorNode,
             [parameterName],
           );
         }
@@ -108,18 +117,19 @@
         var annotation = _requiredAnnotation(parameter);
         if (annotation != null) {
           String parameterName = parameter.name;
-          if (!_containsNamedExpression(arguments, parameterName)) {
+          if (!_containsNamedExpression(
+              enclosingConstructor, arguments, parameterName)) {
             var reason = annotation.reason;
             if (reason != null) {
               _errorReporter.reportErrorForNode(
                 HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS,
-                node,
+                errorNode,
                 [parameterName, reason],
               );
             } else {
               _errorReporter.reportErrorForNode(
                 HintCode.MISSING_REQUIRED_PARAM,
-                node,
+                errorNode,
                 [parameterName],
               );
             }
@@ -130,6 +140,7 @@
   }
 
   static bool _containsNamedExpression(
+    ConstructorElement? enclosingConstructor,
     List<Expression> arguments,
     String name,
   ) {
@@ -141,6 +152,12 @@
         }
       }
     }
+
+    if (enclosingConstructor != null) {
+      return enclosingConstructor.parameters.any((e) =>
+          e is SuperFormalParameterElement && e.isNamed && e.name == name);
+    }
+
     return false;
   }
 
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 83d50a2..f516e46 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -1159,7 +1159,10 @@
 
   @override
   void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
-    _requiredParametersVerifier.visitSuperConstructorInvocation(node);
+    _requiredParametersVerifier.visitSuperConstructorInvocation(
+      node,
+      enclosingConstructor: _enclosingExecutable.element.ifTypeOrNull(),
+    );
     _isInConstructorInitializer = true;
     try {
       super.visitSuperConstructorInvocation(node);
@@ -5280,3 +5283,11 @@
     }
   }
 }
+
+extension on Object? {
+  /// If the target is [T], return it, otherwise `null`.
+  T? ifTypeOrNull<T>() {
+    final self = this;
+    return self is T ? self : null;
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
index ac4c1f3..f20aedb 100644
--- a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
@@ -235,19 +235,51 @@
     ]);
   }
 
-  test_enum_instance() async {
+  test_enum_instance_mixinGetter_mixinSetter() async {
     await assertErrorsInCode('''
-enum E {
-  v;
+mixin M1 {
   num get foo => 0;
+}
+
+mixin M2 {
   set foo(int v) {}
 }
+
+enum E with M1, M2 {
+  v
+}
 ''', [
-      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 24, 3),
+      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 73, 1),
     ]);
   }
 
-  test_enum_instance_field() async {
+  test_enum_instance_mixinGetter_thisSetter() async {
+    await assertErrorsInCode('''
+mixin M {
+  num get foo => 0;
+}
+
+enum E with M {
+  v;
+  set foo(int v) {}
+}
+''', [
+      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 60, 3),
+    ]);
+  }
+
+  test_enum_instance_superGetter_thisSetter_index() async {
+    await assertErrorsInCode('''
+enum E {
+  v;
+  set index(String _) {}
+}
+''', [
+      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 20, 5),
+    ]);
+  }
+
+  test_enum_instance_thisField_thisSetter() async {
     await assertErrorsInCode('''
 enum E {
   v;
@@ -259,6 +291,18 @@
     ]);
   }
 
+  test_enum_instance_thisGetter_thisSetter() async {
+    await assertErrorsInCode('''
+enum E {
+  v;
+  num get foo => 0;
+  set foo(int v) {}
+}
+''', [
+      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 24, 3),
+    ]);
+  }
+
   test_enum_static() async {
     await assertErrorsInCode('''
 enum E {
diff --git a/pkg/analyzer/test/src/diagnostics/missing_required_param_test.dart b/pkg/analyzer/test/src/diagnostics/missing_required_param_test.dart
index c70450c..e066e13 100644
--- a/pkg/analyzer/test/src/diagnostics/missing_required_param_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/missing_required_param_test.dart
@@ -101,6 +101,18 @@
     ]);
   }
 
+  test_constructor_superFormalParameter() async {
+    await assertNoErrorsInCode(r'''
+class A {
+  A({required int a});
+}
+
+class B extends A {
+  B({required super.a}) : super();
+}
+''');
+  }
+
   test_enumConstant_withArguments() async {
     await assertErrorsInCode(r'''
 enum E {
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc
index 7702c24..3b6b707 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc
@@ -26,41 +26,6 @@
 
 #define __ assembler->
 
-// Allocate a GrowableObjectArray:: using the backing array specified.
-// On stack: type argument (+1), data (+0).
-void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler,
-                                             Label* normal_ir_body) {
-  // The newly allocated object is returned in R0.
-  const intptr_t kTypeArgumentsOffset = 1 * target::kWordSize;
-  const intptr_t kArrayOffset = 0 * target::kWordSize;
-
-  // Try allocating in new space.
-  const Class& cls = GrowableObjectArrayClass();
-  __ TryAllocate(cls, normal_ir_body, Assembler::kFarJump, R0, R1);
-
-  // Store backing array object in growable array object.
-  __ ldr(R1, Address(SP, kArrayOffset));  // Data argument.
-  // R0 is new, no barrier needed.
-  __ StoreIntoObjectNoBarrier(
-      R0, FieldAddress(R0, target::GrowableObjectArray::data_offset()), R1);
-
-  // R0: new growable array object start as a tagged pointer.
-  // Store the type argument field in the growable array object.
-  __ ldr(R1, Address(SP, kTypeArgumentsOffset));  // Type argument.
-  __ StoreIntoObjectNoBarrier(
-      R0,
-      FieldAddress(R0, target::GrowableObjectArray::type_arguments_offset()),
-      R1);
-
-  // Set the length field in the growable array object to 0.
-  __ LoadImmediate(R1, 0);
-  __ StoreIntoObjectNoBarrier(
-      R0, FieldAddress(R0, target::GrowableObjectArray::length_offset()), R1);
-  __ Ret();  // Returns the newly allocated object in R0.
-
-  __ Bind(normal_ir_body);
-}
-
 // Loads args from stack into R0 and R1
 // Tests if they are smis, jumps to label not_smi if not.
 static void TestBothArgumentsSmis(Assembler* assembler, Label* not_smi) {
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
index 429099c..eb5cacc 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
@@ -26,40 +26,6 @@
 
 #define __ assembler->
 
-// Allocate a GrowableObjectArray:: using the backing array specified.
-// On stack: type argument (+1), data (+0).
-void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler,
-                                             Label* normal_ir_body) {
-  // The newly allocated object is returned in R0.
-  const intptr_t kTypeArgumentsOffset = 1 * target::kWordSize;
-  const intptr_t kArrayOffset = 0 * target::kWordSize;
-
-  // Try allocating in new space.
-  const Class& cls = GrowableObjectArrayClass();
-  __ TryAllocate(cls, normal_ir_body, Assembler::kFarJump, R0, R1);
-
-  // Store backing array object in growable array object.
-  __ ldr(R1, Address(SP, kArrayOffset));  // Data argument.
-  // R0 is new, no barrier needed.
-  __ StoreCompressedIntoObjectNoBarrier(
-      R0, FieldAddress(R0, target::GrowableObjectArray::data_offset()), R1);
-
-  // R0: new growable array object start as a tagged pointer.
-  // Store the type argument field in the growable array object.
-  __ ldr(R1, Address(SP, kTypeArgumentsOffset));  // Type argument.
-  __ StoreCompressedIntoObjectNoBarrier(
-      R0,
-      FieldAddress(R0, target::GrowableObjectArray::type_arguments_offset()),
-      R1);
-
-  // Set the length field in the growable array object to 0.
-  __ StoreCompressedIntoObjectNoBarrier(
-      R0, FieldAddress(R0, target::GrowableObjectArray::length_offset()), ZR);
-  __ ret();  // Returns the newly allocated object in R0.
-
-  __ Bind(normal_ir_body);
-}
-
 // Loads args from stack into R0 and R1
 // Tests if they are smis, jumps to label not_smi if not.
 static void TestBothArgumentsSmis(Assembler* assembler, Label* not_smi) {
diff --git a/runtime/vm/compiler/asm_intrinsifier_ia32.cc b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
index 7d264aa7..815a2e0 100644
--- a/runtime/vm/compiler/asm_intrinsifier_ia32.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
@@ -31,42 +31,6 @@
 
 #define __ assembler->
 
-// Allocate a GrowableObjectArray:: using the backing array specified.
-// On stack: type argument (+2), data (+1), return-address (+0).
-void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler,
-                                             Label* normal_ir_body) {
-  // This snippet of inlined code uses the following registers:
-  // EAX, EBX
-  // and the newly allocated object is returned in EAX.
-  const intptr_t kTypeArgumentsOffset = 2 * target::kWordSize;
-
-  const intptr_t kArrayOffset = 1 * target::kWordSize;
-
-  // Try allocating in new space.
-  const Class& cls = GrowableObjectArrayClass();
-  __ TryAllocate(cls, normal_ir_body, Assembler::kNearJump, EAX, EBX);
-
-  // Store backing array object in growable array object.
-  __ movl(EBX, Address(ESP, kArrayOffset));  // data argument.
-  // EAX is new, no barrier needed.
-  __ StoreIntoObjectNoBarrier(
-      EAX, FieldAddress(EAX, target::GrowableObjectArray::data_offset()), EBX);
-
-  // EAX: new growable array object start as a tagged pointer.
-  // Store the type argument field in the growable array object.
-  __ movl(EBX, Address(ESP, kTypeArgumentsOffset));  // type argument.
-  __ StoreIntoObjectNoBarrier(
-      EAX,
-      FieldAddress(EAX, target::GrowableObjectArray::type_arguments_offset()),
-      EBX);
-
-  __ ZeroInitSmiField(
-      FieldAddress(EAX, target::GrowableObjectArray::length_offset()));
-  __ ret();  // returns the newly allocated object in EAX.
-
-  __ Bind(normal_ir_body);
-}
-
 // Tests if two top most arguments are smis, jumps to label not_smi if not.
 // Topmost argument is in EAX.
 static void TestBothArgumentsSmis(Assembler* assembler, Label* not_smi) {
diff --git a/runtime/vm/compiler/asm_intrinsifier_riscv.cc b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
index 0e2da82..db03989 100644
--- a/runtime/vm/compiler/asm_intrinsifier_riscv.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
@@ -26,40 +26,6 @@
 
 #define __ assembler->
 
-// Allocate a GrowableObjectArray:: using the backing array specified.
-// On stack: type argument (+1), data (+0).
-void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler,
-                                             Label* normal_ir_body) {
-  // The newly allocated object is returned in R0.
-  const intptr_t kTypeArgumentsOffset = 1 * target::kWordSize;
-  const intptr_t kArrayOffset = 0 * target::kWordSize;
-
-  // Try allocating in new space.
-  const Class& cls = GrowableObjectArrayClass();
-  __ TryAllocate(cls, normal_ir_body, Assembler::kFarJump, A0, A1);
-
-  // Store backing array object in growable array object.
-  __ lx(A1, Address(SP, kArrayOffset));  // Data argument.
-  // R0 is new, no barrier needed.
-  __ StoreCompressedIntoObjectNoBarrier(
-      A0, FieldAddress(A0, target::GrowableObjectArray::data_offset()), A1);
-
-  // R0: new growable array object start as a tagged pointer.
-  // Store the type argument field in the growable array object.
-  __ lx(A1, Address(SP, kTypeArgumentsOffset));  // Type argument.
-  __ StoreCompressedIntoObjectNoBarrier(
-      A0,
-      FieldAddress(A0, target::GrowableObjectArray::type_arguments_offset()),
-      A1);
-
-  // Set the length field in the growable array object to 0.
-  __ StoreCompressedIntoObjectNoBarrier(
-      A0, FieldAddress(A0, target::GrowableObjectArray::length_offset()), ZR);
-  __ ret();  // Returns the newly allocated object in A0.
-
-  __ Bind(normal_ir_body);
-}
-
 // Loads args from stack into A0 and A1
 // Tests if they are smis, jumps to label not_smi if not.
 static void TestBothArgumentsSmis(Assembler* assembler, Label* not_smi) {
diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc
index 48bfe54..0a47313 100644
--- a/runtime/vm/compiler/asm_intrinsifier_x64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc
@@ -26,42 +26,6 @@
 
 #define __ assembler->
 
-// Allocate a GrowableObjectArray using the backing array specified.
-// On stack: type argument (+2), data (+1), return-address (+0).
-void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler,
-                                             Label* normal_ir_body) {
-  // This snippet of inlined code uses the following registers:
-  // RAX, RCX, R13
-  // and the newly allocated object is returned in RAX.
-  const intptr_t kTypeArgumentsOffset = 2 * target::kWordSize;
-  const intptr_t kArrayOffset = 1 * target::kWordSize;
-
-  // Try allocating in new space.
-  const Class& cls = GrowableObjectArrayClass();
-  __ TryAllocate(cls, normal_ir_body, Assembler::kFarJump, RAX, R13);
-
-  // Store backing array object in growable array object.
-  __ movq(RCX, Address(RSP, kArrayOffset));  // data argument.
-  // RAX is new, no barrier needed.
-  __ StoreCompressedIntoObjectNoBarrier(
-      RAX, FieldAddress(RAX, target::GrowableObjectArray::data_offset()), RCX);
-
-  // RAX: new growable array object start as a tagged pointer.
-  // Store the type argument field in the growable array object.
-  __ movq(RCX, Address(RSP, kTypeArgumentsOffset));  // type argument.
-  __ StoreCompressedIntoObjectNoBarrier(
-      RAX,
-      FieldAddress(RAX, target::GrowableObjectArray::type_arguments_offset()),
-      RCX);
-
-  // Set the length field in the growable array object to 0.
-  __ ZeroInitCompressedSmiField(
-      FieldAddress(RAX, target::GrowableObjectArray::length_offset()));
-  __ ret();  // returns the newly allocated object in RAX.
-
-  __ Bind(normal_ir_body);
-}
-
 // Tests if two top most arguments are smis, jumps to label not_smi if not.
 // Topmost argument is in RAX.
 static void TestBothArgumentsSmis(Assembler* assembler, Label* not_smi) {
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 0311465..6fd0db2 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -877,6 +877,7 @@
     case MethodRecognizer::kByteDataViewTypedData:
     case MethodRecognizer::kTypedDataViewTypedData:
     case MethodRecognizer::kClassIDgetID:
+    case MethodRecognizer::kGrowableArrayAllocateWithData:
     case MethodRecognizer::kGrowableArrayCapacity:
     case MethodRecognizer::kListFactory:
     case MethodRecognizer::kObjectArrayAllocate:
@@ -1124,6 +1125,26 @@
       body += LoadLocal(parsed_function_->RawParameterVariable(0));
       body += LoadClassId();
       break;
+    case MethodRecognizer::kGrowableArrayAllocateWithData: {
+      ASSERT(function.IsFactory());
+      ASSERT_EQUAL(function.NumParameters(), 2);
+      const Class& cls =
+          Class::ZoneHandle(Z, compiler::GrowableObjectArrayClass().ptr());
+      body += LoadLocal(parsed_function_->RawParameterVariable(0));
+      body += AllocateObject(TokenPosition::kNoSource, cls, 1);
+      LocalVariable* object = MakeTemporary();
+      body += LoadLocal(object);
+      body += LoadLocal(parsed_function_->RawParameterVariable(1));
+      body += StoreNativeField(Slot::GrowableObjectArray_data(),
+                               StoreInstanceFieldInstr::Kind::kInitializing,
+                               kNoStoreBarrier);
+      body += LoadLocal(object);
+      body += IntConstant(0);
+      body += StoreNativeField(Slot::GrowableObjectArray_length(),
+                               StoreInstanceFieldInstr::Kind::kInitializing,
+                               kNoStoreBarrier);
+      break;
+    }
     case MethodRecognizer::kGrowableArrayCapacity:
       ASSERT_EQUAL(function.NumParameters(), 1);
       body += LoadLocal(parsed_function_->RawParameterVariable(0));
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index 6c67e4c..5d42253 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -18,6 +18,7 @@
   V(List, ., ListFactory, 0xbc820cf9)                                          \
   V(_List, ., ObjectArrayAllocate, 0xd693eee6)                                 \
   V(_List, []=, ObjectArraySetIndexed, 0xd7b48abc)                             \
+  V(_GrowableList, ._withData, GrowableArrayAllocateWithData, 0xa32d060b)      \
   V(_GrowableList, []=, GrowableArraySetIndexed, 0xd7b48abc)                   \
   V(_TypedList, _getInt8, ByteArrayBaseGetInt8, 0x1623dc34)                    \
   V(_TypedList, _getUint8, ByteArrayBaseGetUint8, 0x177ffe2a)                  \
@@ -274,7 +275,6 @@
   V(_Double, get:isNegative, Double_getIsNegative, 0xd4715091)                 \
   V(_Double, _mulFromInteger, Double_mulFromInteger, 0x0a50d2cf)               \
   V(_Double, .fromInteger, DoubleFromInteger, 0x7d0fd999)                      \
-  V(_GrowableList, ._withData, GrowableArray_Allocate, 0xa32d060b)             \
   V(_RegExp, _ExecuteMatch, RegExp_ExecuteMatch, 0x9911d549)                   \
   V(_RegExp, _ExecuteMatchSticky, RegExp_ExecuteMatchSticky, 0x91dd880f)       \
   V(Object, ==, ObjectEquals, 0x46587030)                                      \
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index b70a40e..199ae30 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -899,6 +899,43 @@
   __ Ret();
 }
 
+// Generates allocation stub for _GrowableList class.
+// This stub exists solely for performance reasons: default allocation
+// stub is slower as it doesn't use specialized inline allocation.
+void StubCodeCompiler::GenerateAllocateGrowableArrayStub(Assembler* assembler) {
+#if defined(TARGET_ARCH_IA32)
+  // This stub is not used on IA32 because IA32 version of
+  // StubCodeCompiler::GenerateAllocationStubForClass uses inline
+  // allocation. Also, AllocateObjectSlow stub is not generated on IA32.
+  __ Breakpoint();
+#else
+  const intptr_t instance_size = target::RoundedAllocationSize(
+      target::GrowableObjectArray::InstanceSize());
+
+  if (!FLAG_use_slow_path && FLAG_inline_alloc) {
+    Label slow_case;
+    __ Comment("Inline allocation of GrowableList");
+    __ TryAllocateObject(kGrowableObjectArrayCid, instance_size, &slow_case,
+                         Assembler::kNearJump, AllocateObjectABI::kResultReg,
+                         /*temp_reg=*/AllocateObjectABI::kTagsReg);
+    __ StoreIntoObjectNoBarrier(
+        AllocateObjectABI::kResultReg,
+        FieldAddress(AllocateObjectABI::kResultReg,
+                     target::GrowableObjectArray::type_arguments_offset()),
+        AllocateObjectABI::kTypeArgumentsReg);
+
+    __ Ret();
+    __ Bind(&slow_case);
+  }
+
+  const uword tags = target::MakeTagWordForNewSpaceObject(
+      kGrowableObjectArrayCid, instance_size);
+  __ LoadImmediate(AllocateObjectABI::kTagsReg, tags);
+  __ Jump(
+      Address(THR, target::Thread::allocate_object_slow_entry_point_offset()));
+#endif  // defined(TARGET_ARCH_IA32)
+}
+
 // The UnhandledException class lives in the VM isolate, so it cannot cache
 // an allocation stub for itself. Instead, we cache it in the stub code list.
 void StubCodeCompiler::GenerateAllocateUnhandledExceptionStub(
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 5174ca8..a37565a 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -1728,7 +1728,7 @@
 
 static void GenerateAllocateObjectHelper(Assembler* assembler,
                                          bool is_cls_parameterized) {
-  const Register kTagsReg = R2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   {
     Label slow_case;
@@ -1845,7 +1845,6 @@
 
 void StubCodeCompiler::GenerateAllocateObjectSlowStub(Assembler* assembler) {
   const Register kClsReg = R1;
-  const Register kTagsReg = R2;
 
   if (!FLAG_precompiled_mode) {
     __ ldr(CODE_REG,
@@ -1856,7 +1855,8 @@
   // calling into the runtime.
   __ EnterStubFrame();
 
-  __ ExtractClassIdFromTags(AllocateObjectABI::kResultReg, kTagsReg);
+  __ ExtractClassIdFromTags(AllocateObjectABI::kResultReg,
+                            AllocateObjectABI::kTagsReg);
   __ LoadClassById(kClsReg, AllocateObjectABI::kResultReg);
 
   __ LoadObject(AllocateObjectABI::kResultReg, NullObject());
@@ -1903,7 +1903,7 @@
   const uword tags =
       target::MakeTagWordForNewSpaceObject(cls_id, instance_size);
 
-  const Register kTagsReg = R2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   __ LoadImmediate(kTagsReg, tags);
 
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index e5fe61f..6dc4dbe 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -1904,7 +1904,7 @@
 
 static void GenerateAllocateObjectHelper(Assembler* assembler,
                                          bool is_cls_parameterized) {
-  const Register kTagsReg = R2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   {
     Label slow_case;
@@ -2014,20 +2014,19 @@
 }
 
 void StubCodeCompiler::GenerateAllocateObjectSlowStub(Assembler* assembler) {
-  const Register kTagsToClsIdReg = R2;
-
   if (!FLAG_precompiled_mode) {
     __ ldr(CODE_REG,
            Address(THR, target::Thread::call_to_runtime_stub_offset()));
   }
 
-  __ ExtractClassIdFromTags(kTagsToClsIdReg, kTagsToClsIdReg);
+  __ ExtractClassIdFromTags(AllocateObjectABI::kTagsReg,
+                            AllocateObjectABI::kTagsReg);
 
   // Create a stub frame as we are pushing some objects on the stack before
   // calling into the runtime.
   __ EnterStubFrame();
 
-  __ LoadClassById(R0, kTagsToClsIdReg);
+  __ LoadClassById(R0, AllocateObjectABI::kTagsReg);
   __ PushPair(R0, NULL_REG);  // Pushes result slot, then class object.
 
   // Should be Object::null() if class is non-parameterized.
@@ -2072,7 +2071,7 @@
       target::MakeTagWordForNewSpaceObject(cls_id, instance_size);
 
   // Note: Keep in sync with helper function.
-  const Register kTagsReg = R2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   __ LoadImmediate(kTagsReg, tags);
 
diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc
index c9f604b..14dd9f3 100644
--- a/runtime/vm/compiler/stub_code_compiler_riscv.cc
+++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc
@@ -1831,7 +1831,7 @@
 
 static void GenerateAllocateObjectHelper(Assembler* assembler,
                                          bool is_cls_parameterized) {
-  const Register kTagsReg = T2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   {
     Label slow_case;
@@ -1936,8 +1936,6 @@
 }
 
 void StubCodeCompiler::GenerateAllocateObjectSlowStub(Assembler* assembler) {
-  const Register kTagsToClsIdReg = T2;
-
   if (!FLAG_precompiled_mode) {
     __ lx(CODE_REG,
           Address(THR, target::Thread::call_to_runtime_stub_offset()));
@@ -1947,8 +1945,9 @@
   // calling into the runtime.
   __ EnterStubFrame();
 
-  __ ExtractClassIdFromTags(kTagsToClsIdReg, kTagsToClsIdReg);
-  __ LoadClassById(A0, kTagsToClsIdReg);
+  __ ExtractClassIdFromTags(AllocateObjectABI::kTagsReg,
+                            AllocateObjectABI::kTagsReg);
+  __ LoadClassById(A0, AllocateObjectABI::kTagsReg);
 
   __ subi(SP, SP, 3 * target::kWordSize);
   __ sx(ZR, Address(SP, 2 * target::kWordSize));  // Result slot.
@@ -1993,7 +1992,7 @@
       target::MakeTagWordForNewSpaceObject(cls_id, instance_size);
 
   // Note: Keep in sync with helper function.
-  const Register kTagsReg = T2;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
   ASSERT(kTagsReg != AllocateObjectABI::kTypeArgumentsReg);
 
   __ LoadImmediate(kTagsReg, tags);
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 5e3ba12..de42236 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -1938,7 +1938,7 @@
 static void GenerateAllocateObjectHelper(Assembler* assembler,
                                          bool is_cls_parameterized) {
   // Note: Keep in sync with calling function.
-  const Register kTagsReg = R8;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   {
     Label slow_case;
@@ -2049,14 +2049,13 @@
 }
 
 void StubCodeCompiler::GenerateAllocateObjectSlowStub(Assembler* assembler) {
-  const Register kTagsToClsIdReg = R8;
-
   if (!FLAG_precompiled_mode) {
     __ movq(CODE_REG,
             Address(THR, target::Thread::call_to_runtime_stub_offset()));
   }
 
-  __ ExtractClassIdFromTags(kTagsToClsIdReg, kTagsToClsIdReg);
+  __ ExtractClassIdFromTags(AllocateObjectABI::kTagsReg,
+                            AllocateObjectABI::kTagsReg);
 
   // Create a stub frame.
   // Ensure constant pool is allowed so we can e.g. load class object.
@@ -2067,7 +2066,7 @@
   __ pushq(AllocateObjectABI::kResultReg);
 
   // Push class of object to be allocated.
-  __ LoadClassById(AllocateObjectABI::kResultReg, kTagsToClsIdReg);
+  __ LoadClassById(AllocateObjectABI::kResultReg, AllocateObjectABI::kTagsReg);
   __ pushq(AllocateObjectABI::kResultReg);
 
   // Must be Object::null() if non-parameterized class.
@@ -2118,7 +2117,7 @@
   const uword tags =
       target::MakeTagWordForNewSpaceObject(cls_id, instance_size);
 
-  const Register kTagsReg = R8;
+  const Register kTagsReg = AllocateObjectABI::kTagsReg;
 
   __ movq(kTagsReg, Immediate(tags));
 
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index 94b214a..b7f2374 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -470,6 +470,7 @@
 struct AllocateObjectABI {
   static const Register kResultReg = R0;
   static const Register kTypeArgumentsReg = R3;
+  static const Register kTagsReg = R2;
 };
 
 // ABI for AllocateClosureStub.
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 9e4213d..b2f4e6e 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -309,6 +309,7 @@
 struct AllocateObjectABI {
   static const Register kResultReg = R0;
   static const Register kTypeArgumentsReg = R1;
+  static const Register kTagsReg = R2;
 };
 
 // ABI for AllocateClosureStub.
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index bac9dd5..d06f97f 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -206,6 +206,7 @@
 struct AllocateObjectABI {
   static const Register kResultReg = EAX;
   static const Register kTypeArgumentsReg = EDX;
+  static const Register kTagsReg = kNoRegister;  // Not used.
 };
 
 // ABI for Allocate{Mint,Double,Float32x4,Float64x2}Stub.
diff --git a/runtime/vm/constants_riscv.h b/runtime/vm/constants_riscv.h
index d04317b..a03694c 100644
--- a/runtime/vm/constants_riscv.h
+++ b/runtime/vm/constants_riscv.h
@@ -326,6 +326,7 @@
 struct AllocateObjectABI {
   static constexpr Register kResultReg = A0;
   static constexpr Register kTypeArgumentsReg = T1;
+  static const Register kTagsReg = T2;
 };
 
 // ABI for AllocateClosureStub.
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index 2bc9444..aae0a4c 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -281,6 +281,7 @@
 struct AllocateObjectABI {
   static const Register kResultReg = RAX;
   static const Register kTypeArgumentsReg = RDX;
+  static const Register kTagsReg = R8;
 };
 
 // ABI for AllocateClosureStub.
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index b8eaf21..88d7a2e 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -208,6 +208,7 @@
   RW(Code, allocate_float64x2_array_stub)                                      \
   RW(Code, allocate_closure_stub)                                              \
   RW(Code, allocate_context_stub)                                              \
+  RW(Code, allocate_growable_array_stub)                                       \
   RW(Code, allocate_object_stub)                                               \
   RW(Code, allocate_object_parametrized_stub)                                  \
   RW(Code, allocate_unhandled_exception_stub)                                  \
@@ -284,6 +285,7 @@
   DO(allocate_float64x2_array_stub, AllocateFloat64x2Array)                    \
   DO(allocate_closure_stub, AllocateClosure)                                   \
   DO(allocate_context_stub, AllocateContext)                                   \
+  DO(allocate_growable_array_stub, AllocateGrowableArray)                      \
   DO(allocate_object_stub, AllocateObject)                                     \
   DO(allocate_object_parametrized_stub, AllocateObjectParameterized)           \
   DO(allocate_unhandled_exception_stub, AllocateUnhandledException)            \
diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc
index 0ef8b4d..9b665c2 100644
--- a/runtime/vm/stack_trace.cc
+++ b/runtime/vm/stack_trace.cc
@@ -75,7 +75,6 @@
       controller_subscription_class(Class::Handle(zone)),
       buffering_stream_subscription_class(Class::Handle(zone)),
       stream_iterator_class(Class::Handle(zone)),
-      async_then_wrapper_(Function::Handle(zone)),
       future_result_or_listeners_field(Field::Handle(zone)),
       callback_field(Field::Handle(zone)),
       future_listener_state_field(Field::Handle(zone)),
@@ -112,9 +111,6 @@
   stream_iterator_class =
       async_lib.LookupClassAllowPrivate(Symbols::_StreamIterator());
   ASSERT(!stream_iterator_class.IsNull());
-  async_then_wrapper_ =
-      async_lib.LookupFunctionAllowPrivate(Symbols::AsyncThenWrapperHelper());
-  ASSERT(!async_then_wrapper_.IsNull());
 
   // Look up fields:
   // - async:
diff --git a/runtime/vm/stack_trace.h b/runtime/vm/stack_trace.h
index 6893a7f..566a0a8 100644
--- a/runtime/vm/stack_trace.h
+++ b/runtime/vm/stack_trace.h
@@ -82,7 +82,6 @@
   Class& controller_subscription_class;
   Class& buffering_stream_subscription_class;
   Class& stream_iterator_class;
-  Function& async_then_wrapper_;
 
   Field& future_result_or_listeners_field;
   Field& callback_field;
diff --git a/runtime/vm/stub_code.cc b/runtime/vm/stub_code.cc
index 6303447..be40522 100644
--- a/runtime/vm/stub_code.cc
+++ b/runtime/vm/stub_code.cc
@@ -172,6 +172,10 @@
   switch (cls.id()) {
     case kArrayCid:
       return object_store->allocate_array_stub();
+#if !defined(TARGET_ARCH_IA32)
+    case kGrowableObjectArrayCid:
+      return object_store->allocate_growable_array_stub();
+#endif  // !defined(TARGET_ARCH_IA32)
     case kContextCid:
       return object_store->allocate_context_stub();
     case kUnhandledExceptionCid:
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index 778e08c..3607cbc 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -52,6 +52,7 @@
   V(AllocateMintSharedWithoutFPURegs)                                          \
   V(AllocateClosure)                                                           \
   V(AllocateContext)                                                           \
+  V(AllocateGrowableArray)                                                     \
   V(AllocateObject)                                                            \
   V(AllocateObjectParameterized)                                               \
   V(AllocateObjectSlow)                                                        \
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 707df3d..7784041 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -31,7 +31,6 @@
   V(AsyncStarMoveNextHelper, "_asyncStarMoveNextHelper")                       \
   V(AwaitContextVar, ":await_ctx_var")                                         \
   V(AwaitJumpVar, ":await_jump_var")                                           \
-  V(AsyncThenWrapperHelper, "_asyncThenWrapperHelper")                         \
   V(Bool, "bool")                                                              \
   V(BooleanExpression, "boolean expression")                                   \
   V(BoundsCheckForPartialInstantiation, "_boundsCheckForPartialInstantiation") \
diff --git a/sdk/lib/_internal/vm/lib/growable_array.dart b/sdk/lib/_internal/vm/lib/growable_array.dart
index c6d9e46..2c47725 100644
--- a/sdk/lib/_internal/vm/lib/growable_array.dart
+++ b/sdk/lib/_internal/vm/lib/growable_array.dart
@@ -222,7 +222,7 @@
     return list;
   }
 
-  @pragma("vm:recognized", "asm-intrinsic")
+  @pragma("vm:recognized", "other")
   @pragma("vm:exact-result-type",
       <dynamic>[_GrowableList, "result-type-uses-passed-type-arguments"])
   @pragma("vm:external-name", "GrowableList_allocate")
diff --git a/tools/VERSION b/tools/VERSION
index e933701..aeefac4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 124
+PRERELEASE 125
 PRERELEASE_PATCH 0
\ No newline at end of file