Add a refactor to convert an unnamed constructor to a named constructor

Let me know whether there are any missed cases that ought to be covered
here. There are tests for the rename constructor that cover a lot of
the additional cases, so I mostly focused here on tests related to where
the refactor is and isn't provided, but that might not be sufficient.

Change-Id: I6e0008597cf852b4acc81a96fa564e0e1a414bd9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/489620
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson
2026-03-25 06:51:51 -07:00
committed by Commit Queue
parent 62e11ac90a
commit f62c82aaf8
8 changed files with 1193 additions and 1 deletions
@@ -0,0 +1,90 @@
// Copyright (c) 2026, 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/refactoring/framework/refactoring_producer.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart';
import 'package:analysis_server/src/services/search/search_engine_internal.dart';
import 'package:analysis_server/src/utilities/extensions/selection.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:language_server_protocol/protocol_custom_generated.dart';
/// The refactoring that adds a name to an unnamed constructor.
class AddConstructorName extends RefactoringProducer {
static const String commandName = 'dart.refactor.add_constructor_name';
static const String constTitle = 'Add a name to the constructor';
AddConstructorName(super.context);
@override
bool get isExperimental => false;
@override
List<CommandParameter> get parameters => const <CommandParameter>[];
@override
String get title => constTitle;
@override
Future<ComputeStatus> compute(
List<Object?> commandArguments,
ChangeBuilder builder,
) async {
var element = selection?.constructor(mustNotHaveName: true);
if (element == null) {
// This should never happen because `isAvailable` would have returned
// `false`, so this method wouldn't have been called.
return ComputeStatusFailure();
}
var refactoring = _createRefactoring(element);
if (refactoring == null) {
return ComputeStatusFailure();
}
refactoring.newName = _computeName(element);
var status = await refactoring.checkAllConditions();
if (status.hasError) {
return ComputeStatusFailure();
}
await refactoring.createChange(builder: builder);
return ComputeStatusSuccess();
}
@override
bool isAvailable() {
return selection?.constructor(mustNotHaveName: true) != null;
}
/// Compute a name for the new constructor.
String _computeName(ConstructorElement element) {
var enclosingElement = element.enclosingElement;
var usedNames = <String>{};
usedNames.addAll(enclosingElement.constructors.map((c) => c.name ?? ''));
usedNames.addAll(enclosingElement.methods.map((m) => m.name ?? ''));
usedNames.addAll(enclosingElement.fields.map((f) => f.name ?? ''));
var candidate = 'name';
var index = 1;
while (usedNames.contains(candidate)) {
candidate = 'name$index';
index++;
}
return candidate;
}
RenameRefactoring? _createRefactoring(ConstructorElement element) {
var analysisContext = libraryResult.session.analysisContext;
if (analysisContext is! DriverBasedAnalysisContext) {
return null;
}
var driver = analysisContext.driver;
var searchEngine = SearchEngineImpl([driver]);
return RenameRefactoring.create(
RefactoringWorkspace([driver], searchEngine),
unitResult,
element,
);
}
}
@@ -4,6 +4,7 @@
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/services/correction/refactoring_performance.dart';
import 'package:analysis_server/src/services/refactoring/add_constructor_name.dart';
import 'package:analysis_server/src/services/refactoring/convert_all_formal_parameters_to_named.dart';
import 'package:analysis_server/src/services/refactoring/convert_selected_formal_parameters_to_named.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart';
@@ -19,6 +20,7 @@ typedef RefactoringProducerGenerator =
class RefactoringProcessor {
/// A list of the generators used to produce refactorings.
static const Map<String, RefactoringProducerGenerator> generators = {
AddConstructorName.commandName: AddConstructorName.new,
ConvertAllFormalParametersToNamed.commandName:
ConvertAllFormalParametersToNamed.new,
ConvertSelectedFormalParametersToNamed.commandName:
@@ -61,7 +61,6 @@ class RenameConstructorRefactoringImpl extends RenameRefactoringImpl {
});
continue;
} else if (coveringParent is ConstructorDeclaration &&
// TODO(scheglov): support primary constructors
coveringParent.typeName!.offset == reference.range.offset) {
await builder.addDartFileEdit(reference.file, (builder) {
_addSuperInvocationToConstructor(
@@ -0,0 +1,76 @@
// Copyright (c) 2026, 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_plugin/src/utilities/selection.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
extension SelectionExtension on Selection {
/// The end of the selection.
int get end => offset + length;
/// Returns the element of the constructor at the given [selection].
///
/// Returns `null` if
/// - the selection doesn't identify a constructor, or
/// - [mustHaveName] is `true` and the referenced constructor doesn't have a
/// name, or
/// - [mustNotHaveName] is `true` and the referenced constructor has a name.
ConstructorElement? constructor({
bool mustHaveName = false,
bool mustNotHaveName = false,
}) {
var node = coveringNode;
bool meetsRequirements(Object? name) {
if (name == null) {
if (mustHaveName) return false;
} else {
if (mustNotHaveName) return false;
}
return true;
}
if (node is ConstructorDeclaration) {
if (!meetsRequirements(node.name)) return null;
var left =
node.typeName?.offset ??
node.factoryKeyword?.offset ??
node.newKeyword?.offset ??
node.firstTokenAfterCommentAndMetadata.offset;
var right = node.separator?.offset ?? node.parameters.offset;
if (left <= offset && end <= right) {
return node.declaredFragment?.element;
}
} else if (node is SimpleIdentifier || node is FormalParameterList) {
var parent = node.parent;
if (parent is ConstructorDeclaration) {
if (!meetsRequirements(parent.name)) return null;
return parent.declaredFragment?.element;
} else if (parent is ConstructorName) {
if (!meetsRequirements(parent.name)) return null;
return parent.element;
}
} else if (node is PrimaryConstructorDeclaration) {
if (!meetsRequirements(node.constructorName)) return null;
return node.declaredFragment?.element;
} else if (node is PrimaryConstructorName) {
if (!meetsRequirements(node.name)) return null;
var parent = node.parent;
if (parent is PrimaryConstructorDeclaration) {
return parent.declaredFragment?.element;
}
} else if (node is NamedType) {
var parent = node.parent;
if (parent is ConstructorName) {
if (!meetsRequirements(node.name)) return null;
return parent.element;
}
} else if (node is ConstructorName) {
if (!meetsRequirements(node.name)) return null;
return node.element;
}
return null;
}
}
@@ -0,0 +1,525 @@
// Copyright (c) 2026, 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/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/extensions/code_action.dart';
import 'package:analysis_server/src/services/refactoring/add_constructor_name.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../lsp/request_helpers_mixin.dart';
import 'refactoring_test_support.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AddConstructorNameInClassTest);
defineReflectiveTests(AddConstructorNameInEnumTest);
defineReflectiveTests(AddConstructorNameInExtensionTypeTest);
});
}
@reflectiveTest
class AddConstructorNameInClassTest extends _AddConstructorNameTest {
Future<void> test_primary() async {
var originalSource = '''
class C^() {}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C.name() {}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_primary_named_onClassName() async {
var originalSource = '''
class C^.name() {}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_primary_named_onConstructorName() async {
var originalSource = '''
class C.n^ame() {}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_factory_named_onKeyword() async {
var originalSource = '''
class C {
factory^ name() => C._()
C._();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_factory_named_onName() async {
var originalSource = '''
class C {
factory nam^e() => C._()
C._();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_factory_noSpace() async {
var originalSource = '''
class C {
factory^() => C._()
C._();
}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
factory name() => C._()
C._();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_factory_onKeyword() async {
var originalSource = '''
class C {
factory^ () => C._()
C._();
}
void f() {
C();
}
''';
// Unfortunately, the refactor doesn't get the AST for the constructor
// declaration, so it doesn't know about the space after `factory`.
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
factory name () => C._()
C._();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_factory_onParameterList() async {
var originalSource = '''
class C {
factory ^() => C._()
C._();
}
void f() {
C();
}
''';
// Unfortunately, the refactor doesn't get the AST for the constructor
// declaration, so it doesn't know about the space after `factory`.
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
factory name () => C._()
C._();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_new_named_onKeyword() async {
var originalSource = '''
class C {
new^ name();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_new_named_onName() async {
var originalSource = '''
class C {
new ^name();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_new_noSpace() async {
var originalSource = '''
class C {
new^();
}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
new name();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_new_onKeyword() async {
var originalSource = '''
class C {
new^ ();
}
void f() {
C();
}
''';
// Unfortunately, the refactor doesn't get the AST for the constructor
// declaration, so it doesn't know about the space after `new`.
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
new name ();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_new_onParameterList() async {
var originalSource = '''
class C {
new ^();
}
void f() {
C();
}
''';
// Unfortunately, the refactor doesn't get the AST for the constructor
// declaration, so it doesn't know about the space after `new`.
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
new name ();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_simple() async {
var originalSource = '''
class C {
C^();
}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
C.name();
}
void f() {
C.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_simple_hasConflict() async {
var originalSource = '''
class C {
C^();
String get name => '';
}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C {
C.name1();
String get name => '';
}
void f() {
C.name1();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_simple_named_onClassName() async {
var originalSource = '''
class C {
C^.name();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_simple_named_onConstructorName() async {
var originalSource = '''
class C {
C.na^me();
}
void f() {
C.name();
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
}
@reflectiveTest
class AddConstructorNameInEnumTest extends _AddConstructorNameTest {
Future<void> test_primary() async {
var originalSource = '''
enum E^() {
a
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
enum E.name() {
a.name()
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_primary_named() async {
var originalSource = '''
enum E.n^ame() {
a
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
Future<void> test_secondary_new() async {
var originalSource = '''
enum E {
a;
new^();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
enum E {
a.name();
new name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_secondary_simple() async {
var originalSource = '''
enum E {
a;
E^();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
enum E {
a.name();
E.name();
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
}
@reflectiveTest
class AddConstructorNameInExtensionTypeTest extends _AddConstructorNameTest {
Future<void> test_primary() async {
var originalSource = '''
extension type E^(int x) {}
void f() {
E(1);
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
extension type E.name(int x) {}
void f() {
E.name(1);
}
''';
await _assertRefactoring(
originalSource: originalSource,
expected: expected,
);
}
Future<void> test_primary_named() async {
var originalSource = '''
extension type E.name^(int x) {}
void f() {
E.name(1);
}
''';
await _assertNoRefactoring(originalSource: originalSource);
}
}
abstract class _AddConstructorNameTest extends RefactoringTest
with LspProgressNotificationsMixin {
@override
String get refactoringName => AddConstructorName.commandName;
Future<void> _assertNoRefactoring({required String originalSource}) async {
if (originalSource.contains('>>>>')) {
throw 'File content must not include >>>>>';
}
addTestSource(originalSource);
await initializeServer();
await expectNoCodeActionWithTitle(AddConstructorName.constTitle);
}
Future<void> _assertRefactoring({
required String originalSource,
required String expected,
String? otherFilePath,
String? otherFileContent,
ProgressToken? commandWorkDoneToken,
}) async {
if (originalSource.contains('>>>>') ||
(otherFileContent?.contains('>>>>>') ?? false)) {
throw 'File content must not include >>>>>';
}
addTestSource(originalSource);
if (otherFilePath != null) {
newFile(otherFilePath, otherFileContent!);
}
await initializeServer();
var action = await expectCodeActionWithTitle(AddConstructorName.constTitle);
await verifyCommandEdits(
action.command!,
expected,
workDoneToken: commandWorkDoneToken,
);
}
}
@@ -4,6 +4,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'add_constructor_name_test.dart' as add_constructor_name;
import 'convert_all_formal_parameters_to_named_test.dart'
as convert_all_formal_parameters_to_named;
import 'convert_selected_formal_parameters_to_named_test.dart'
@@ -14,6 +15,7 @@ import 'move_top_level_to_file_test.dart' as move_top_level_to_file;
void main() {
defineReflectiveSuite(() {
add_constructor_name.main();
convert_all_formal_parameters_to_named.main();
convert_selected_formal_parameters_to_named.main();
move_selected_formal_parameters_left.main();
@@ -0,0 +1,496 @@
// Copyright (c) 2026, 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/extensions/selection.dart';
import 'package:analysis_server_plugin/src/utilities/selection.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../abstract_single_unit.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(SelectionConstructorInClassTest);
defineReflectiveTests(SelectionConstructorInEnumTest);
defineReflectiveTests(SelectionConstructorInExtensionTypeTest);
});
}
/// This class has all of the invocation site tests because the structure of the
/// AST at the invocation site doesn't depend on the kind of declaration
/// containing the constructor declaration.
@reflectiveTest
class SelectionConstructorInClassTest extends _SelectionConstructorTestBase {
Future<void> test_declaration_primary_named_onConstructorName() async {
await resolveTestCode('''
class C.n^ame() {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_onContainerName() async {
await resolveTestCode('''
class C^.name() {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_overBoth() async {
await resolveTestCode('''
class [!C.nam!]e() {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_onKeyword() async {
await resolveTestCode('''
cla^ss C() {}
''');
_assertNoConstructor();
}
Future<void> test_declaration_primary_unnamed() async {
await resolveTestCode('''
class C^() {}
''');
_assertHasConstructor();
}
Future<void>
test_declaration_secondary_factory_named_onConstructorName() async {
await resolveTestCode('''
class C {
factory na^me() => C._();
C._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_named_onFactory() async {
await resolveTestCode('''
class C {
facto^ry name() => C._();
C._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_named_overBoth() async {
await resolveTestCode('''
class C {
fact[!ory na!]me() => C._();
C._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_unnamed() async {
await resolveTestCode('''
class C {
facto^ry () => C._();
C._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_onConstructorName() async {
await resolveTestCode('''
class C {
new na^me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_onNew() async {
await resolveTestCode('''
class C {
ne^w name();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_overBoth() async {
await resolveTestCode('''
class C {
ne[!w na!]me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_unnamed() async {
await resolveTestCode('''
class C {
^new ();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_named_onClassName() async {
await resolveTestCode('''
class C {
^C.name();
}
''');
_assertHasConstructor();
}
Future<void>
test_declaration_secondary_simple_named_onConstructorName() async {
await resolveTestCode('''
class C {
C.na^me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_named_overBoth() async {
await resolveTestCode('''
class C {
[!C.na!]me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_unnamed() async {
await resolveTestCode('''
class C {
^C();
}
''');
_assertHasConstructor();
}
Future<void> test_invocation_named_onArgumentList() async {
await resolveTestCode('''
class C {
C.name();
}
void f() {
C.name(^);
}
''');
_assertNoConstructor();
}
Future<void> test_invocation_named_onClassName() async {
await resolveTestCode('''
class C {
C.name();
}
void f() {
^C.name();
}
''');
_assertHasConstructor();
}
Future<void> test_invocation_named_onConstructorName() async {
await resolveTestCode('''
class C {
C.name();
}
void f() {
C.^name();
}
''');
_assertHasConstructor();
}
Future<void> test_invocation_named_overBoth() async {
await resolveTestCode('''
class C {
C.name();
}
void f() {
[!C.nam!]e();
}
''');
_assertHasConstructor();
}
Future<void> test_invocation_unnamed() async {
await resolveTestCode('''
class C() {}
void f() {
^C();
}
''');
_assertHasConstructor();
}
}
@reflectiveTest
class SelectionConstructorInEnumTest extends _SelectionConstructorTestBase {
Future<void> test_declaration_primary_named_onConstructorName() async {
await resolveTestCode('''
enum E.n^ame() { a.name() }
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_onContainerName() async {
await resolveTestCode('''
enum E^.name() { a.name() }
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_overBoth() async {
await resolveTestCode('''
enum [!E.nam!]e() { a.name() }
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_onKeyword() async {
await resolveTestCode('''
en^um E() { a }
''');
_assertNoConstructor();
}
Future<void> test_declaration_primary_unnamed() async {
await resolveTestCode('''
enum E^() { a }
''');
_assertHasConstructor();
}
Future<void>
test_declaration_secondary_factory_named_onConstructorName() async {
await resolveTestCode('''
enum E {
a._();
factory na^me() => a;
const E._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_named_onFactory() async {
await resolveTestCode('''
enum E {
a._();
facto^ry name() => a;
const E._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_named_overBoth() async {
await resolveTestCode('''
enum E {
a._();
fa[!ctory na!]me() => a;
const E._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_factory_unnamed() async {
await resolveTestCode('''
enum E {
a._();
facto^ry () => a;
const E._();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_onConstructorName() async {
await resolveTestCode('''
enum E {
a.name();
const new na^me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_onNew() async {
await resolveTestCode('''
enum E {
a.name();
const new^ name();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_named_overBoth() async {
await resolveTestCode('''
enum E {
a.name();
const ne[!w na!]me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_new_unnamed() async {
await resolveTestCode('''
enum E {
a();
const ^new ();
}
''');
_assertHasConstructor();
}
Future<void>
test_declaration_secondary_simple_named_onConstructorName() async {
await resolveTestCode('''
enum E {
a.name();
const E.na^me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_named_onEnumName() async {
await resolveTestCode('''
enum E {
a.name();
const E^.name();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_named_overBoth() async {
await resolveTestCode('''
enum E {
a.name();
const [!E.na!]me();
}
''');
_assertHasConstructor();
}
Future<void> test_declaration_secondary_simple_unnamed() async {
await resolveTestCode('''
enum E {
a();
const E^();
}
''');
_assertHasConstructor();
}
}
@reflectiveTest
class SelectionConstructorInExtensionTypeTest
extends _SelectionConstructorTestBase {
Future<void> test_declaration_primary_named_onConstructorName() async {
await resolveTestCode('''
extension type E.n^ame(int x) {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_onContainerName() async {
await resolveTestCode('''
extension type E^.name(int x) {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_named_overBoth() async {
await resolveTestCode('''
extension type [!E.nam!]e(int x) {}
''');
_assertHasConstructor();
}
Future<void> test_declaration_primary_onExtensionKeyword() async {
await resolveTestCode('''
exten^sion type E(int x) {}
''');
_assertNoConstructor();
}
Future<void> test_declaration_primary_onTypeKeyword() async {
await resolveTestCode('''
extension ^type E(int x) {}
''');
_assertNoConstructor();
}
Future<void> test_declaration_primary_unnamed() async {
await resolveTestCode('''
extension type E^(int x) {}
''');
_assertHasConstructor();
}
}
abstract class _SelectionConstructorTestBase extends AbstractSingleUnitTest {
void _assertHasConstructor() {
expect(_getConstructor(), isA<ConstructorElement>());
}
void _assertNoConstructor() {
expect(_getConstructor(), isNull);
}
ConstructorElement? _getConstructor() {
var range = _getSourceRange();
var selection = testAnalysisResult.unit.select(
offset: range.offset,
length: range.length,
);
if (selection == null) {
fail('No selection found');
}
return selection.constructor();
}
SourceRange _getSourceRange() {
if (parsedTestCode.positions.length == 1) {
return SourceRange(parsedTestCode.position.offset, 0);
} else if (parsedTestCode.ranges.length == 1) {
return parsedTestCode.range.sourceRange;
} else {
fail('Invalid source range in test code.');
}
}
}
@@ -7,6 +7,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'ast_test.dart' as ast;
import 'numeric_test.dart' as numeric;
import 'range_factory_test.dart' as range_factory;
import 'selection_test.dart' as selection;
import 'string_test.dart' as string;
void main() {
@@ -14,6 +15,7 @@ void main() {
ast.main();
numeric.main();
range_factory.main();
selection.main();
string.main();
});
}