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:
committed by
Commit Queue
parent
62e11ac90a
commit
f62c82aaf8
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user