Add a utility to find adjacent siblings in a selection

This will be used to allow multiple top-level declarations to be moved
together.

Change-Id: Ib1c59e9ff3e01891f8cb29b74e78183c36b1f02d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273828
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_context.dart b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_context.dart
index f3f3ab2..ea053a4 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_context.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_context.dart
@@ -6,11 +6,11 @@
 import 'package:analysis_server/src/services/correction/change_workspace.dart';
 import 'package:analysis_server/src/services/correction/util.dart';
 import 'package:analysis_server/src/services/search/search_engine.dart';
+import 'package:analysis_server/src/utilities/selection.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/src/dart/analysis/session_helper.dart';
-import 'package:analyzer/src/utilities/extensions/ast.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
 
 /// The context in which a refactoring was requested.
@@ -34,9 +34,9 @@
   /// Utilities available to be used in the process of computing the edits.
   late final CorrectionUtils utils = CorrectionUtils(resolvedUnitResult);
 
-  /// The node that was selected, or `null` if the selection is not valid.
-  late final AstNode? selectedNode = resolvedUnitResult.unit
-      .nodeCovering(offset: selectionOffset, length: selectionLength);
+  /// The selection, or `null` if the selection is not valid.
+  late final Selection? selection = resolvedUnitResult.unit
+      .select(offset: selectionOffset, length: selectionLength);
 
   /// The helper used to efficiently access resolved units.
   late final AnalysisSessionHelper sessionHelper =
@@ -57,6 +57,9 @@
   /// Return the search engine used to search outside the resolved library.
   SearchEngine get searchEngine => server.searchEngine;
 
+  /// The node that was selected, or `null` if the selection is not valid.
+  AstNode? get selectedNode => selection?.coveringNode;
+
   /// Return the analysis session in which additional resolution can occur.
   AnalysisSession get session => resolvedUnitResult.session;
 }
diff --git a/pkg/analysis_server/lib/src/utilities/selection.dart b/pkg/analysis_server/lib/src/utilities/selection.dart
new file mode 100644
index 0000000..b435b12
--- /dev/null
+++ b/pkg/analysis_server/lib/src/utilities/selection.dart
@@ -0,0 +1,436 @@
+// 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/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/utilities/extensions/ast.dart';
+
+/// A selection within a compilation unit.
+class Selection {
+  /// The offset of the selection.
+  final int offset;
+
+  /// The length of the selection.
+  final int length;
+
+  /// The most deeply nested node whose range completely includes the selected
+  /// range of characters.
+  final AstNode coveringNode;
+
+  /// Initialize a newly created selection to include the characters starting at
+  /// the [offset] and including [length] characters, all of which fall within
+  /// the [coveringNode].
+  Selection(
+      {required this.offset, required this.length, required this.coveringNode});
+
+  /// Return a list containing the contiguous children of the [coveringNode]
+  /// that together cover the selected [range] of characters, or an empty list
+  /// if there is no such set of children. The list will never have a single
+  /// element.
+  ///
+  /// A list of nodes is defined to be _contiguous_ if they are syntactically
+  /// adjacent with no intervening tokens other than comments or a comma. For
+  /// example, the nodes might be a sequence of members in a compilation unit, a
+  /// sequence of statements in a block, or a sequence of parameters in a
+  /// parameter list that don't cross a separator such as `{` or `[`.
+  List<AstNode> nodesInRange() {
+    var rangeFinder = _ChildrenFinder(SourceRange(offset, length));
+    coveringNode.accept(rangeFinder);
+    return rangeFinder.nodes;
+  }
+}
+
+/// A visitor used to find a sequence of nodes within the node being visited
+/// that together cover the range.
+class _ChildrenFinder extends SimpleAstVisitor<void> {
+  /// The range that the sequence of nodes must cover.
+  final SourceRange range;
+
+  /// The nodes within the range.
+  List<AstNode> nodes = [];
+
+  /// Initialize a newly created visitor.
+  _ChildrenFinder(this.range);
+
+  @override
+  void visitAdjacentStrings(AdjacentStrings node) {
+    _fromList(node.strings);
+  }
+
+  @override
+  void visitArgumentList(ArgumentList node) {
+    _fromList(node.arguments);
+  }
+
+  @override
+  void visitAugmentationImportDirective(AugmentationImportDirective node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitBlock(Block node) {
+    _fromList(node.statements);
+  }
+
+  @override
+  void visitCascadeExpression(CascadeExpression node) {
+    _fromList(node.cascadeSections);
+  }
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    _fromList(node.metadata) || _fromList(node.members);
+  }
+
+  @override
+  void visitClassTypeAlias(ClassTypeAlias node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitCompilationUnit(CompilationUnit node) {
+    // TODO(brianwilkerson) Support selecting both directives and declarations.
+    _fromList(node.directives) || _fromList(node.declarations);
+  }
+
+  @override
+  void visitConstructorDeclaration(ConstructorDeclaration node) {
+    _fromList(node.metadata) || _fromList(node.initializers);
+  }
+
+  @override
+  void visitDeclaredIdentifier(DeclaredIdentifier node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitDefaultFormalParameter(DefaultFormalParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitDottedName(DottedName node) {
+    _fromList(node.components);
+  }
+
+  @override
+  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    _fromList(node.metadata) ||
+        _fromList(node.constants) ||
+        _fromList(node.members);
+  }
+
+  @override
+  void visitExportDirective(ExportDirective node) {
+    _fromList(node.metadata) ||
+        _fromList(node.configurations) ||
+        _fromList(node.combinators);
+  }
+
+  @override
+  void visitExtensionDeclaration(ExtensionDeclaration node) {
+    _fromList(node.metadata) || _fromList(node.members);
+  }
+
+  @override
+  void visitFieldFormalParameter(FieldFormalParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitFormalParameterList(FormalParameterList node) {
+    var delimiter = node.leftDelimiter;
+    if (delimiter == null || !range.contains(delimiter.offset)) {
+      _fromList(node.parameters);
+    }
+  }
+
+  @override
+  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
+    _fromList(node.updaters);
+  }
+
+  @override
+  void visitForPartsWithExpression(ForPartsWithExpression node) {
+    _fromList(node.updaters);
+  }
+
+  @override
+  void visitForPartsWithPattern(ForPartsWithPattern node) {
+    _fromList(node.updaters);
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitFunctionTypeAlias(FunctionTypeAlias node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitGenericTypeAlias(GenericTypeAlias node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitHideCombinator(HideCombinator node) {
+    _fromList(node.hiddenNames);
+  }
+
+  @override
+  void visitImplementsClause(ImplementsClause node) {
+    _fromList(node.interfaces);
+  }
+
+  @override
+  void visitImportDirective(ImportDirective node) {
+    _fromList(node.metadata) ||
+        _fromList(node.configurations) ||
+        _fromList(node.combinators);
+  }
+
+  @override
+  void visitLabeledStatement(LabeledStatement node) {
+    _fromList(node.labels);
+  }
+
+  @override
+  void visitLibraryAugmentationDirective(LibraryAugmentationDirective node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitLibraryDirective(LibraryDirective node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitLibraryIdentifier(LibraryIdentifier node) {
+    _fromList(node.components);
+  }
+
+  @override
+  void visitListLiteral(ListLiteral node) {
+    _fromList(node.elements);
+  }
+
+  @override
+  void visitListPattern(ListPattern node) {
+    _fromList(node.elements);
+  }
+
+  @override
+  void visitMapPattern(MapPattern node) {
+    _fromList(node.elements);
+  }
+
+  @override
+  void visitMethodDeclaration(MethodDeclaration node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitMixinDeclaration(MixinDeclaration node) {
+    _fromList(node.metadata) || _fromList(node.members);
+  }
+
+  @override
+  void visitObjectPattern(ObjectPattern node) {
+    _fromList(node.fields);
+  }
+
+  @override
+  void visitOnClause(OnClause node) {
+    _fromList(node.superclassConstraints);
+  }
+
+  @override
+  void visitPartDirective(PartDirective node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitPartOfDirective(PartOfDirective node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitRecordLiteral(RecordLiteral node) {
+    _fromList(node.fields);
+  }
+
+  @override
+  void visitRecordPattern(RecordPattern node) {
+    _fromList(node.fields);
+  }
+
+  @override
+  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
+    _fromList(node.positionalFields);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedField(
+      RecordTypeAnnotationNamedField node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedFields(
+      RecordTypeAnnotationNamedFields node) {
+    _fromList(node.fields);
+  }
+
+  @override
+  void visitRecordTypeAnnotationPositionalField(
+      RecordTypeAnnotationPositionalField node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitSetOrMapLiteral(SetOrMapLiteral node) {
+    _fromList(node.elements);
+  }
+
+  @override
+  void visitShowCombinator(ShowCombinator node) {
+    _fromList(node.shownNames);
+  }
+
+  @override
+  void visitSimpleFormalParameter(SimpleFormalParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitSuperFormalParameter(SuperFormalParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitSwitchCase(SwitchCase node) {
+    _fromList(node.labels) || _fromList(node.statements);
+  }
+
+  @override
+  void visitSwitchDefault(SwitchDefault node) {
+    _fromList(node.labels) || _fromList(node.statements);
+  }
+
+  @override
+  void visitSwitchExpression(SwitchExpression node) {
+    _fromList(node.cases);
+  }
+
+  @override
+  void visitSwitchPatternCase(SwitchPatternCase node) {
+    _fromList(node.labels) || _fromList(node.statements);
+  }
+
+  @override
+  void visitSwitchStatement(SwitchStatement node) {
+    _fromList(node.members);
+  }
+
+  @override
+  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitTryStatement(TryStatement node) {
+    _fromList(node.catchClauses);
+  }
+
+  @override
+  void visitTypeArgumentList(TypeArgumentList node) {
+    _fromList(node.arguments);
+  }
+
+  @override
+  void visitTypeParameter(TypeParameter node) {
+    _fromList(node.metadata);
+  }
+
+  @override
+  void visitTypeParameterList(TypeParameterList node) {
+    _fromList(node.typeParameters);
+  }
+
+  @override
+  void visitVariableDeclarationList(VariableDeclarationList node) {
+    _fromList(node.metadata) || _fromList(node.variables);
+  }
+
+  @override
+  void visitWithClause(WithClause node) {
+    _fromList(node.mixinTypes);
+  }
+
+  /// If two or more of the [elements] in the list can cover the [range], then
+  /// add the elements to the list of [nodes] and return `true`.
+  bool _fromList(List<AstNode> elements) {
+    int leftIndex() {
+      var offset = range.offset;
+      for (int i = elements.length - 1; i >= 0; i--) {
+        if (elements[i].offset <= offset) {
+          return i;
+        }
+      }
+      return -1;
+    }
+
+    var first = leftIndex();
+    if (first < 0) {
+      return false;
+    }
+
+    int rightIndex() {
+      var end = range.end;
+      for (int i = first + 1; i < elements.length; i++) {
+        if (elements[i].offset >= end) {
+          return i - 1;
+        }
+      }
+      return -1;
+    }
+
+    var last = rightIndex();
+    if (last < 0) {
+      return false;
+    }
+
+    for (int i = first; i <= last; i++) {
+      nodes.add(elements[i]);
+    }
+    return true;
+  }
+}
+
+extension CompilationUnitExtension on CompilationUnit {
+  /// Return the selection that includes the characters starting at the [offset]
+  /// with the given [length].
+  Selection? select({required int offset, required int length}) {
+    var coveringNode = nodeCovering(offset: offset, length: length);
+    if (coveringNode == null) {
+      return null;
+    }
+    return Selection(
+        offset: offset, length: length, coveringNode: coveringNode);
+  }
+}
diff --git a/pkg/analysis_server/test/src/utilities/selection_test.dart b/pkg/analysis_server/test/src/utilities/selection_test.dart
new file mode 100644
index 0000000..c24370a
--- /dev/null
+++ b/pkg/analysis_server/test/src/utilities/selection_test.dart
@@ -0,0 +1,932 @@
+// 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/utilities/selection.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
+import 'package:analyzer/src/test_utilities/test_code_format.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../abstract_single_unit.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SelectionTest);
+  });
+}
+
+@reflectiveTest
+class SelectionTest extends AbstractSingleUnitTest {
+  @override
+  List<String> get experiments => [
+        ...super.experiments,
+        EnableString.patterns,
+      ];
+
+  Future<void> assertMembers(
+      {String prefix = '', required String postfix}) async {
+    var nodes = await nodesInRange('''
+$prefix
+  int x = 0;
+  [!void m1() {}
+  int y = 1;!]
+  void m2() {}
+$postfix
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as MethodDeclaration;
+    nodes[1] as FieldDeclaration;
+  }
+
+  Future<void> assertMetadata(
+      {String prefix = '', required String postfix}) async {
+    var nodes = await nodesInRange('''
+$prefix
+@a
+[!@b
+@c!]
+@d
+$postfix
+const a = 1;
+const b = 2;
+const c = 3;
+const d = 4;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Annotation).name.name, 'b');
+    expect((nodes[1] as Annotation).name.name, 'c');
+  }
+
+  Future<List<AstNode>> nodesInRange(String sourceCode) async {
+    var range = await _range(sourceCode);
+    var selection =
+        testUnit.select(offset: range.offset, length: range.length)!;
+    return selection.nodesInRange();
+  }
+
+  Future<void> test_adjacentStrings_strings() async {
+    var nodes = await nodesInRange('''
+var s = 'a' [!'b' 'c'!] 'd';
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as StringLiteral).stringValue, 'b');
+    expect((nodes[1] as StringLiteral).stringValue, 'c');
+  }
+
+  Future<void> test_argumentList_arguments() async {
+    var nodes = await nodesInRange('''
+var v = f('0', [!1, 2.0!], '3');
+int f(a, b, c, d) => 0;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as IntegerLiteral).value, 1);
+    expect((nodes[1] as DoubleLiteral).value, 2.0);
+  }
+
+  @FailingTest(reason: 'Augmentation libraries appear to not be supported.')
+  Future<void> test_augmentationImportDirective_metadata() async {
+    newFile('$testPackageLibPath/a.dart', '''
+augmentation library 'test.dart';
+''');
+    await assertMetadata(postfix: '''
+import augment 'a.dart';
+''');
+  }
+
+  Future<void> test_block_statements() async {
+    var nodes = await nodesInRange('''
+void f() {
+  var v = 0;
+  [!if (v < 0) v++;
+  while (v > 0) v--;!]
+  print(v);
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as IfStatement;
+    nodes[1] as WhileStatement;
+  }
+
+  Future<void> test_cascadeExpression_cascadeSections() async {
+    var nodes = await nodesInRange('''
+void f(y) {
+  var x = y..a()[!..b..c()!]..d;
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as PropertyAccess;
+    nodes[1] as MethodInvocation;
+  }
+
+  Future<void> test_classTypeAlias_metadata() async {
+    await assertMetadata(postfix: '''
+class A = C with M;
+class C {}
+mixin M {}
+''');
+  }
+
+  Future<void> test_compilationUnit_declarations() async {
+    var nodes = await nodesInRange('''
+typedef F = void Function();
+[!var x = 0;
+void f() {}!]
+class C {}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as TopLevelVariableDeclaration;
+    nodes[1] as FunctionDeclaration;
+  }
+
+  Future<void> test_compilationUnit_directives() async {
+    newFile('$testPackageLibPath/a.dart', "part of 'test.dart';");
+    var nodes = await nodesInRange('''
+library l;
+[!import '';
+export '';!]
+part 'a.dart';
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as ImportDirective;
+    nodes[1] as ExportDirective;
+  }
+
+  Future<void> test_constructorDeclaration_initializers() async {
+    var nodes = await nodesInRange('''
+class C {
+  int a, b, c, d;
+  C() : a = 0, [!b = 1, c = 2,!] d = 4;
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as ConstructorFieldInitializer).fieldName.name, 'b');
+    expect((nodes[1] as ConstructorFieldInitializer).fieldName.name, 'c');
+  }
+
+  Future<void> test_constructorDeclaration_metadata() async {
+    await assertMetadata(prefix: '''
+class C {
+''', postfix: '''
+  C();
+}
+''');
+  }
+
+  Future<void> test_declaredIdentifier_metadata() async {
+    await assertMetadata(prefix: '''
+void f(List l) {
+  for (
+''', postfix: '''
+var e in l) {}
+}
+''');
+  }
+
+  Future<void> test_defaultFormalParameter_metadata() async {
+    await assertMetadata(prefix: '''
+void f([
+''', postfix: '''
+int x = 0]) {}
+''');
+  }
+
+  Future<void> test_dottedName_components() async {
+    var nodes = await nodesInRange('''
+library a.[!b.c!].d;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SimpleIdentifier).name, 'b');
+    expect((nodes[1] as SimpleIdentifier).name, 'c');
+  }
+
+  Future<void> test_enumConstantDeclaration_metadata() async {
+    await assertMetadata(prefix: '''
+enum E {
+''', postfix: '''
+a }
+''');
+  }
+
+  Future<void> test_enumDeclaration_constants() async {
+    var nodes = await nodesInRange('''
+enum E { a, [!b, c!], d }
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as EnumConstantDeclaration).name.lexeme, 'b');
+    expect((nodes[1] as EnumConstantDeclaration).name.lexeme, 'c');
+  }
+
+  Future<void> test_enumDeclaration_members() async {
+    var nodes = await nodesInRange('''
+enum E {
+  a;
+  final int x = 0;
+  [!void m1() {}
+  final int y = 1;!]
+  void m2() {}
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as MethodDeclaration;
+    nodes[1] as FieldDeclaration;
+  }
+
+  Future<void> test_enumDeclaration_metadata() async {
+    await assertMetadata(postfix: '''
+enum E { a }
+''');
+  }
+
+  Future<void> test_exportDirective_combinators() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    var nodes = await nodesInRange('''
+export 'a.dart' show a [!hide b show c!] hide d;
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as HideCombinator;
+    nodes[1] as ShowCombinator;
+  }
+
+  Future<void> test_exportDirective_configurations() async {
+    var nodes = await nodesInRange('''
+export '' if (a) '' [!if (b) '' if (c) ''!] if (d) '';
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Configuration).name.toSource(), 'b');
+    expect((nodes[1] as Configuration).name.toSource(), 'c');
+  }
+
+  Future<void> test_exportDirective_metadata() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    await assertMetadata(postfix: '''
+export 'a.dart';
+''');
+  }
+
+  Future<void> test_extensionDeclaration_members() async {
+    var nodes = await nodesInRange('''
+extension on int {
+  static int x = 0;
+  [!void m1() {}
+  static int y = 1;!]
+  void m2() {}
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as MethodDeclaration;
+    nodes[1] as FieldDeclaration;
+  }
+
+  Future<void> test_extensionDeclaration_metadata() async {
+    await assertMetadata(postfix: '''
+extension on int {}
+''');
+  }
+
+  Future<void> test_fieldFormalParameter_metadata() async {
+    await assertMetadata(prefix: '''
+class C {
+  int x = 0;
+  C(
+''', postfix: '''
+this.x);
+}
+''');
+  }
+
+  Future<void> test_formalParameterList_parameters_mixed() async {
+    var nodes = await nodesInRange('''
+void f(a, [!b, {c!], d}) {}
+''');
+    expect(nodes, isEmpty);
+  }
+
+  Future<void> test_formalParameterList_parameters_named() async {
+    var nodes = await nodesInRange('''
+void f({a, [!b, c!], d}) {}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as FormalParameter).name?.lexeme, 'b');
+    expect((nodes[1] as FormalParameter).name?.lexeme, 'c');
+  }
+
+  Future<void> test_formalParameterList_parameters_positional() async {
+    var nodes = await nodesInRange('''
+void f(a, [!b, c!], d) {}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as FormalParameter).name?.lexeme, 'b');
+    expect((nodes[1] as FormalParameter).name?.lexeme, 'c');
+  }
+
+  Future<void> test_forPartsWithDeclarations_updaters() async {
+    var nodes = await nodesInRange('''
+void f() {
+  for (var x = 0; x < 0; x++, [!x--, ++x!], --x) {}
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as PostfixExpression;
+    nodes[1] as PrefixExpression;
+  }
+
+  Future<void> test_forPartsWithExpression_updaters() async {
+    var nodes = await nodesInRange('''
+void f() {
+  var x;
+  for (x = 0; x < 0; x++, [!x--, ++x!], --x) {}
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as PostfixExpression;
+    nodes[1] as PrefixExpression;
+  }
+
+  @FailingTest(reason: 'The parser does not yet parse this code')
+  Future<void> test_forPartsWithPattern_updaters() async {
+    var nodes = await nodesInRange('''
+void f() {
+  for (var (x, y) = (0, 0); x < 0; x++, [!x--, ++x!], --x) {}
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as PostfixExpression;
+    nodes[1] as PrefixExpression;
+  }
+
+  Future<void> test_functionDeclaration_metadata() async {
+    await assertMetadata(postfix: '''
+void f() {}
+''');
+  }
+
+  Future<void> test_functionTypeAlias_metadata() async {
+    await assertMetadata(postfix: '''
+typedef void F();
+''');
+  }
+
+  Future<void> test_functionTypedFormalParameter_metadata() async {
+    await assertMetadata(prefix: '''
+void f(
+''', postfix: '''
+int g(int)) {}
+''');
+  }
+
+  Future<void> test_genericTypeAlias_metadata() async {
+    await assertMetadata(postfix: '''
+typedef F = void Function();
+''');
+  }
+
+  Future<void> test_hideCombinator_hiddenNames() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    var nodes = await nodesInRange('''
+import 'a.dart' hide a, [!b, c!], d;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SimpleIdentifier).name, 'b');
+    expect((nodes[1] as SimpleIdentifier).name, 'c');
+  }
+
+  Future<void> test_implementsClause_interfaces() async {
+    var nodes = await nodesInRange('''
+class A implements B, [!C, D!], E {}
+class B {}
+class C {}
+class D {}
+class E {}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as NamedType).name.toSource(), 'C');
+    expect((nodes[1] as NamedType).name.toSource(), 'D');
+  }
+
+  Future<void> test_importDirective_combinators() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    var nodes = await nodesInRange('''
+import 'a.dart' show a [!hide b show c!] hide d;
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as HideCombinator;
+    nodes[1] as ShowCombinator;
+  }
+
+  Future<void> test_importDirective_configurations() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    var nodes = await nodesInRange('''
+import 'a.dart' if (a) '' [!if (b) '' if (c) ''!] if (d) '';
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Configuration).name.toSource(), 'b');
+    expect((nodes[1] as Configuration).name.toSource(), 'c');
+  }
+
+  Future<void> test_importDirective_metadata() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    await assertMetadata(postfix: '''
+import 'a.dart';
+''');
+  }
+
+  Future<void> test_labeledStatement_labels() async {
+    var nodes = await nodesInRange('''
+void f() {
+  a: [!b: c:!] d: while (true) {
+    if (1 < 2) {
+      break a;
+    } else if (2 < 3) {
+      break b;
+    } else if (3 < 4) {
+      break c;
+    } else if (4 < 5) {
+      break d;
+    }
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Label).label.name, 'b');
+    expect((nodes[1] as Label).label.name, 'c');
+  }
+
+  @FailingTest(reason: 'The parser fails')
+  Future<void> test_libraryAugmentationDirective_metadata() async {
+    await assertMetadata(postfix: '''
+library augment '';
+''');
+  }
+
+  Future<void> test_libraryDirective_metadata() async {
+    await assertMetadata(postfix: '''
+library l;
+''');
+  }
+
+  Future<void> test_libraryIdentifier_components() async {
+    var nodes = await nodesInRange('''
+library a.[!b.c!].d;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SimpleIdentifier).name, 'b');
+    expect((nodes[1] as SimpleIdentifier).name, 'c');
+  }
+
+  Future<void> test_listLiteral_elements() async {
+    var nodes = await nodesInRange('''
+var l = ['0', [!1, 2.0!], '3'];
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as IntegerLiteral;
+    nodes[1] as DoubleLiteral;
+  }
+
+  Future<void> test_listPattern_elements() async {
+    var nodes = await nodesInRange('''
+void f(x) {
+  switch (x) {
+    case [1, [!2, 3!], 4]:
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), '2');
+    expect(nodes[1].toSource(), '3');
+  }
+
+  Future<void> test_mapPattern_entries() async {
+    var nodes = await nodesInRange('''
+void f(x) {
+  switch (x) {
+    case {'a': 1, [!'b': 2, 'c': 3!], 'd': 4}:
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), "'b': 2");
+    expect(nodes[1].toSource(), "'c': 3");
+  }
+
+  Future<void> test_methodDeclaration_metadata() async {
+    await assertMetadata(prefix: '''
+class C {
+''', postfix: '''
+  void m() {}
+}
+''');
+  }
+
+  Future<void> test_mixinDeclaration_members() async {
+    await assertMembers(prefix: '''
+mixin M {
+''', postfix: '''
+}
+''');
+  }
+
+  Future<void> test_mixinDeclaration_metadata() async {
+    await assertMetadata(postfix: '''
+mixin M {}
+''');
+  }
+
+  Future<void> test_objectPattern_fields() async {
+    var nodes = await nodesInRange('''
+void f(C x) {
+  switch (x) {
+    case C(a: 1, [!b: 2, c: 3!], d: 4):
+      break;
+  }
+}
+class C {
+  int a = 1, b = 2, c = 3, d = 4;
+}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'b: 2');
+    expect(nodes[1].toSource(), 'c: 3');
+  }
+
+  Future<void> test_onClause_superclassConstraints() async {
+    var nodes = await nodesInRange('''
+mixin M on A, [!B, C!], D {}
+class A {}
+class B {}
+class C {}
+class D {}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'B');
+    expect(nodes[1].toSource(), 'C');
+  }
+
+  Future<void> test_partDirective_metadata() async {
+    newFile('$testPackageLibPath/a.dart', "part of 'test.dart';");
+    await assertMetadata(postfix: '''
+part 'a.dart';
+''');
+  }
+
+  Future<void> test_partOfDirective_metadata() async {
+    await assertMetadata(postfix: '''
+part of '';
+''');
+  }
+
+  Future<void> test_recordLiteral_fields() async {
+    var nodes = await nodesInRange('''
+var r = ('0', [!1, 2.0!], '3');
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as IntegerLiteral).value, 1);
+    expect((nodes[1] as DoubleLiteral).value, 2.0);
+  }
+
+  Future<void> test_recordPattern_fields() async {
+    var nodes = await nodesInRange('''
+void f((String, int, double, String) x) {
+  switch (x) {
+    case ('0', [!1, 2.0!], '3'):
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), '1');
+    expect(nodes[1].toSource(), '2.0');
+  }
+
+  Future<void> test_recordTypeAnnotation_positionalFields() async {
+    var nodes = await nodesInRange('''
+(int, [!String, int!], String) r = (0, '1', 2, '3');
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'String');
+    expect(nodes[1].toSource(), 'int');
+  }
+
+  Future<void> test_recordTypeAnnotationNamedField_metadata() async {
+    await assertMetadata(prefix: '''
+void f(({
+''', postfix: '''
+int x}) r) {}
+''');
+  }
+
+  Future<void> test_recordTypeAnnotationNamedFields_fields() async {
+    var nodes = await nodesInRange('''
+({int a, [!String b, int c!], String d}) r = (a: 0, b: '1', c: 2, d:'3');
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'String b');
+    expect(nodes[1].toSource(), 'int c');
+  }
+
+  Future<void> test_recordTypeAnnotationPositionalField_metadata() async {
+    await assertMetadata(prefix: '''
+void f((int,
+''', postfix: '''
+int) r) {}
+''');
+  }
+
+  Future<void> test_setOrMapLiteral_elements() async {
+    var nodes = await nodesInRange('''
+var s = {'0', [!1, 2.0!], '3'};
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as IntegerLiteral).value, 1);
+    expect((nodes[1] as DoubleLiteral).value, 2.0);
+  }
+
+  Future<void> test_showCombinator_shownNames() async {
+    newFile('$testPackageLibPath/a.dart', '''
+int a, b, c, d;
+''');
+    var nodes = await nodesInRange('''
+import 'a.dart' show a, [!b, c!], d;
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SimpleIdentifier).name, 'b');
+    expect((nodes[1] as SimpleIdentifier).name, 'c');
+  }
+
+  Future<void> test_simpleFormalParameter_metadata() async {
+    await assertMetadata(prefix: '''
+void f(
+''', postfix: '''
+int x) {}
+''');
+  }
+
+  Future<void> test_superFormalParameter_metadata() async {
+    await assertMetadata(prefix: '''
+class C extends B {
+  C({
+''', postfix: '''
+super.x});
+}
+class B {
+  B({int? x});
+}
+''');
+  }
+
+  Future<void> test_switchCase_labels() async {
+    var nodes = await nodesInRange('''
+void f(int x) {
+  switch (x) {
+    a: [!b: c!]: d: case 3: continue a;
+    case 4: continue b;
+    case 5: continue c;
+    case 6: continue d;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Label).label.name, 'b');
+    expect((nodes[1] as Label).label.name, 'c');
+  }
+
+  Future<void> test_switchCase_statements() async {
+    var nodes = await nodesInRange('''
+void f(int x) {
+  switch (x) {
+    case 3:
+      var v = 0;
+      [!if (v < 0) v++;
+      while (v > 0) v--;!]
+      print(v);
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as IfStatement;
+    nodes[1] as WhileStatement;
+  }
+
+  Future<void> test_switchDefault_labels() async {
+    var nodes = await nodesInRange('''
+void f(int x) {
+  switch (x) {
+    case 4: continue b;
+    case 5: continue c;
+    case 6: continue d;
+    a: [!b: c!]: d: default: continue a;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Label).label.name, 'b');
+    expect((nodes[1] as Label).label.name, 'c');
+  }
+
+  Future<void> test_switchDefault_statements() async {
+    var nodes = await nodesInRange('''
+void f(int x) {
+  switch (x) {
+    default:
+      var v = 0;
+      [!if (v < 0) v++;
+      while (v > 0) v--;!]
+      print(v);
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as IfStatement;
+    nodes[1] as WhileStatement;
+  }
+
+  Future<void> test_switchExpression_members() async {
+    var nodes = await nodesInRange('''
+String f(int x) {
+  return switch (x) {
+    1 => '1',
+    [!2 => '2',
+    3 => '3'!],
+    4 => '4',
+  };
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SwitchExpressionCase).expression.toSource(), "'2'");
+    expect((nodes[1] as SwitchExpressionCase).expression.toSource(), "'3'");
+  }
+
+  Future<void> test_switchPatternCase_labels() async {
+    var nodes = await nodesInRange('''
+void f((int, int) x) {
+  switch (x) {
+    a: [!b: c!]: d: case (1, 2): continue a;
+    case (3, 4): continue b;
+    case (5, 6): continue c;
+    case (7, 8): continue d;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as Label).label.name, 'b');
+    expect((nodes[1] as Label).label.name, 'c');
+  }
+
+  Future<void> test_switchPatternCase_statements() async {
+    var nodes = await nodesInRange('''
+void f((int, int) r) {
+  switch (r) {
+    case (1, 2):
+      var v = 0;
+      [!if (v < 0) v++;
+      while (v > 0) v--;!]
+      print(v);
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    nodes[0] as IfStatement;
+    nodes[1] as WhileStatement;
+  }
+
+  Future<void> test_switchStatement_members() async {
+    var nodes = await nodesInRange('''
+void f(int x) {
+  switch (x) {
+    case 1:
+      break;
+    [!case 2:
+      break;
+    case 3:
+      break;!]
+    case 4:
+      break;
+  }
+}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as SwitchPatternCase).guardedPattern.toSource(), '2');
+    expect((nodes[1] as SwitchPatternCase).guardedPattern.toSource(), '3');
+  }
+
+  Future<void> test_topLevelVariableDeclaration_metadata() async {
+    await assertMetadata(postfix: '''
+int x = 0;
+''');
+  }
+
+  Future<void> test_tryStatement_catchClauses() async {
+    var nodes = await nodesInRange('''
+void f() {
+  try {
+  } on A {
+  } [!on B {
+  } on C {!]
+  } on D {
+  }
+}
+class A {}
+class B {}
+class C {}
+class D {}
+''');
+    expect(nodes, hasLength(2));
+    expect((nodes[0] as CatchClause).exceptionType?.toSource(), 'B');
+    expect((nodes[1] as CatchClause).exceptionType?.toSource(), 'C');
+  }
+
+  Future<void> test_typeArgumentList_arguments() async {
+    var nodes = await nodesInRange('''
+C<A, [!B, C!], D> c = C();
+class A {}
+class B {}
+class C<Q, R, S, T> {}
+class D {}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'B');
+    expect(nodes[1].toSource(), 'C');
+  }
+
+  Future<void> test_typeParameter_metadata() async {
+    await assertMetadata(prefix: '''
+class C<
+''', postfix: '''
+T> {}
+''');
+  }
+
+  Future<void> test_typeParameterList_typeParameters() async {
+    var nodes = await nodesInRange('''
+class C<A, [!B, D!], E> {}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'B');
+    expect(nodes[1].toSource(), 'D');
+  }
+
+  Future<void> test_variableDeclarationList_metadata() async {
+    await assertMetadata(prefix: '''
+void f() {
+''', postfix: '''
+  int x = 0;
+}
+''');
+  }
+
+  Future<void> test_variableDeclarationList_variables() async {
+    var nodes = await nodesInRange('''
+var a = 1, [!b = 2, c = 3!], d = 4;
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'b = 2');
+    expect(nodes[1].toSource(), 'c = 3');
+  }
+
+  Future<void> test_withClause_mixinTypes() async {
+    var nodes = await nodesInRange('''
+class C with A, [!B, D!], E {}
+mixin A {}
+mixin B {}
+mixin D {}
+mixin E {}
+''');
+    expect(nodes, hasLength(2));
+    expect(nodes[0].toSource(), 'B');
+    expect(nodes[1].toSource(), 'D');
+  }
+
+  Future<SourceRange> _range(String sourceCode) async {
+    var parsedTestCode = TestCode.parse(sourceCode);
+    await resolveTestCode(parsedTestCode.code);
+    SourceRange range;
+    if (parsedTestCode.positions.isEmpty) {
+      expect(parsedTestCode.ranges, hasLength(1));
+      range = parsedTestCode.range.sourceRange;
+    } else {
+      expect(parsedTestCode.positions, hasLength(1));
+      expect(parsedTestCode.ranges, isEmpty);
+      range = SourceRange(parsedTestCode.position.offset, 0);
+    }
+    return range;
+  }
+}
diff --git a/pkg/analysis_server/test/src/utilities/test_all.dart b/pkg/analysis_server/test/src/utilities/test_all.dart
index 85b835f..e09ad1f 100644
--- a/pkg/analysis_server/test/src/utilities/test_all.dart
+++ b/pkg/analysis_server/test/src/utilities/test_all.dart
@@ -8,6 +8,7 @@
 import 'flutter_test.dart' as flutter_test;
 import 'import_analyzer_test.dart' as import_analyzer;
 import 'profiling_test.dart' as profiling_test;
+import 'selection_test.dart' as selection_test;
 import 'strings_test.dart' as strings_test;
 
 void main() {
@@ -16,6 +17,7 @@
     flutter_test.main();
     import_analyzer.main();
     profiling_test.main();
+    selection_test.main();
     strings_test.main();
   });
 }