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();
});
}