From f62c82aaf80ea1b12da503e5025985e3dba4bf7a Mon Sep 17 00:00:00 2001 From: Brian Wilkerson Date: Wed, 25 Mar 2026 06:51:51 -0700 Subject: [PATCH] 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 Commit-Queue: Brian Wilkerson --- .../refactoring/add_constructor_name.dart | 90 +++ .../framework/refactoring_processor.dart | 2 + .../legacy/rename_constructor.dart | 1 - .../src/utilities/extensions/selection.dart | 76 +++ .../add_constructor_name_test.dart | 525 ++++++++++++++++++ .../src/services/refactoring/test_all.dart | 2 + .../utilities/extensions/selection_test.dart | 496 +++++++++++++++++ .../src/utilities/extensions/test_all.dart | 2 + 8 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart create mode 100644 pkg/analysis_server/lib/src/utilities/extensions/selection.dart create mode 100644 pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart create mode 100644 pkg/analysis_server/test/src/utilities/extensions/selection_test.dart diff --git a/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart b/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart new file mode 100644 index 00000000000..325fe020d5b --- /dev/null +++ b/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart @@ -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 get parameters => const []; + + @override + String get title => constTitle; + + @override + Future compute( + List 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 = {}; + 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, + ); + } +} diff --git a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart index 8c7874ff831..f92f3da133e 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart @@ -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 generators = { + AddConstructorName.commandName: AddConstructorName.new, ConvertAllFormalParametersToNamed.commandName: ConvertAllFormalParametersToNamed.new, ConvertSelectedFormalParametersToNamed.commandName: diff --git a/pkg/analysis_server/lib/src/services/refactoring/legacy/rename_constructor.dart b/pkg/analysis_server/lib/src/services/refactoring/legacy/rename_constructor.dart index fd8dd8da629..8cbc92babac 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/legacy/rename_constructor.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/legacy/rename_constructor.dart @@ -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( diff --git a/pkg/analysis_server/lib/src/utilities/extensions/selection.dart b/pkg/analysis_server/lib/src/utilities/extensions/selection.dart new file mode 100644 index 00000000000..10dd9a3b960 --- /dev/null +++ b/pkg/analysis_server/lib/src/utilities/extensions/selection.dart @@ -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; + } +} diff --git a/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart b/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart new file mode 100644 index 00000000000..f9946a7c290 --- /dev/null +++ b/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart @@ -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 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 test_primary_named_onClassName() async { + var originalSource = ''' +class C^.name() {} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future test_primary_named_onConstructorName() async { + var originalSource = ''' +class C.n^ame() {} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future test_secondary_factory_named_onKeyword() async { + var originalSource = ''' +class C { + factory^ name() => C._() + C._(); +} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future test_secondary_factory_named_onName() async { + var originalSource = ''' +class C { + factory nam^e() => C._() + C._(); +} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future 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 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 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 test_secondary_new_named_onKeyword() async { + var originalSource = ''' +class C { + new^ name(); +} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future test_secondary_new_named_onName() async { + var originalSource = ''' +class C { + new ^name(); +} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future 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 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 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 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 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 test_secondary_simple_named_onClassName() async { + var originalSource = ''' +class C { + C^.name(); +} + +void f() { + C.name(); +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future 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 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 test_primary_named() async { + var originalSource = ''' +enum E.n^ame() { + a +} +'''; + await _assertNoRefactoring(originalSource: originalSource); + } + + Future 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 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 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 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 _assertNoRefactoring({required String originalSource}) async { + if (originalSource.contains('>>>>')) { + throw 'File content must not include >>>>>'; + } + addTestSource(originalSource); + + await initializeServer(); + + await expectNoCodeActionWithTitle(AddConstructorName.constTitle); + } + + Future _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, + ); + } +} diff --git a/pkg/analysis_server/test/src/services/refactoring/test_all.dart b/pkg/analysis_server/test/src/services/refactoring/test_all.dart index e6cb9c3c1e8..6c01215791f 100644 --- a/pkg/analysis_server/test/src/services/refactoring/test_all.dart +++ b/pkg/analysis_server/test/src/services/refactoring/test_all.dart @@ -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(); diff --git a/pkg/analysis_server/test/src/utilities/extensions/selection_test.dart b/pkg/analysis_server/test/src/utilities/extensions/selection_test.dart new file mode 100644 index 00000000000..b0f33b3e1a6 --- /dev/null +++ b/pkg/analysis_server/test/src/utilities/extensions/selection_test.dart @@ -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 test_declaration_primary_named_onConstructorName() async { + await resolveTestCode(''' +class C.n^ame() {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_onContainerName() async { + await resolveTestCode(''' +class C^.name() {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_overBoth() async { + await resolveTestCode(''' +class [!C.nam!]e() {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_onKeyword() async { + await resolveTestCode(''' +cla^ss C() {} +'''); + _assertNoConstructor(); + } + + Future test_declaration_primary_unnamed() async { + await resolveTestCode(''' +class C^() {} +'''); + _assertHasConstructor(); + } + + Future + test_declaration_secondary_factory_named_onConstructorName() async { + await resolveTestCode(''' +class C { + factory na^me() => C._(); + C._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_named_onFactory() async { + await resolveTestCode(''' +class C { + facto^ry name() => C._(); + C._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_named_overBoth() async { + await resolveTestCode(''' +class C { + fact[!ory na!]me() => C._(); + C._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_unnamed() async { + await resolveTestCode(''' +class C { + facto^ry () => C._(); + C._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_onConstructorName() async { + await resolveTestCode(''' +class C { + new na^me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_onNew() async { + await resolveTestCode(''' +class C { + ne^w name(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_overBoth() async { + await resolveTestCode(''' +class C { + ne[!w na!]me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_unnamed() async { + await resolveTestCode(''' +class C { + ^new (); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_named_onClassName() async { + await resolveTestCode(''' +class C { + ^C.name(); +} +'''); + _assertHasConstructor(); + } + + Future + test_declaration_secondary_simple_named_onConstructorName() async { + await resolveTestCode(''' +class C { + C.na^me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_named_overBoth() async { + await resolveTestCode(''' +class C { + [!C.na!]me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_unnamed() async { + await resolveTestCode(''' +class C { + ^C(); +} +'''); + _assertHasConstructor(); + } + + Future test_invocation_named_onArgumentList() async { + await resolveTestCode(''' +class C { + C.name(); +} + +void f() { + C.name(^); +} +'''); + _assertNoConstructor(); + } + + Future test_invocation_named_onClassName() async { + await resolveTestCode(''' +class C { + C.name(); +} + +void f() { + ^C.name(); +} +'''); + _assertHasConstructor(); + } + + Future test_invocation_named_onConstructorName() async { + await resolveTestCode(''' +class C { + C.name(); +} + +void f() { + C.^name(); +} +'''); + _assertHasConstructor(); + } + + Future test_invocation_named_overBoth() async { + await resolveTestCode(''' +class C { + C.name(); +} + +void f() { + [!C.nam!]e(); +} +'''); + _assertHasConstructor(); + } + + Future test_invocation_unnamed() async { + await resolveTestCode(''' +class C() {} + +void f() { + ^C(); +} +'''); + _assertHasConstructor(); + } +} + +@reflectiveTest +class SelectionConstructorInEnumTest extends _SelectionConstructorTestBase { + Future test_declaration_primary_named_onConstructorName() async { + await resolveTestCode(''' +enum E.n^ame() { a.name() } +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_onContainerName() async { + await resolveTestCode(''' +enum E^.name() { a.name() } +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_overBoth() async { + await resolveTestCode(''' +enum [!E.nam!]e() { a.name() } +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_onKeyword() async { + await resolveTestCode(''' +en^um E() { a } +'''); + _assertNoConstructor(); + } + + Future test_declaration_primary_unnamed() async { + await resolveTestCode(''' +enum E^() { a } +'''); + _assertHasConstructor(); + } + + Future + test_declaration_secondary_factory_named_onConstructorName() async { + await resolveTestCode(''' +enum E { + a._(); + + factory na^me() => a; + + const E._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_named_onFactory() async { + await resolveTestCode(''' +enum E { + a._(); + + facto^ry name() => a; + + const E._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_named_overBoth() async { + await resolveTestCode(''' +enum E { + a._(); + + fa[!ctory na!]me() => a; + + const E._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_factory_unnamed() async { + await resolveTestCode(''' +enum E { + a._(); + + facto^ry () => a; + + const E._(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_onConstructorName() async { + await resolveTestCode(''' +enum E { + a.name(); + + const new na^me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_onNew() async { + await resolveTestCode(''' +enum E { + a.name(); + + const new^ name(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_named_overBoth() async { + await resolveTestCode(''' +enum E { + a.name(); + + const ne[!w na!]me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_new_unnamed() async { + await resolveTestCode(''' +enum E { + a(); + + const ^new (); +} +'''); + _assertHasConstructor(); + } + + Future + test_declaration_secondary_simple_named_onConstructorName() async { + await resolveTestCode(''' +enum E { + a.name(); + + const E.na^me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_named_onEnumName() async { + await resolveTestCode(''' +enum E { + a.name(); + + const E^.name(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_named_overBoth() async { + await resolveTestCode(''' +enum E { + a.name(); + + const [!E.na!]me(); +} +'''); + _assertHasConstructor(); + } + + Future test_declaration_secondary_simple_unnamed() async { + await resolveTestCode(''' +enum E { + a(); + + const E^(); +} +'''); + _assertHasConstructor(); + } +} + +@reflectiveTest +class SelectionConstructorInExtensionTypeTest + extends _SelectionConstructorTestBase { + Future test_declaration_primary_named_onConstructorName() async { + await resolveTestCode(''' +extension type E.n^ame(int x) {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_onContainerName() async { + await resolveTestCode(''' +extension type E^.name(int x) {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_named_overBoth() async { + await resolveTestCode(''' +extension type [!E.nam!]e(int x) {} +'''); + _assertHasConstructor(); + } + + Future test_declaration_primary_onExtensionKeyword() async { + await resolveTestCode(''' +exten^sion type E(int x) {} +'''); + _assertNoConstructor(); + } + + Future test_declaration_primary_onTypeKeyword() async { + await resolveTestCode(''' +extension ^type E(int x) {} +'''); + _assertNoConstructor(); + } + + Future test_declaration_primary_unnamed() async { + await resolveTestCode(''' +extension type E^(int x) {} +'''); + _assertHasConstructor(); + } +} + +abstract class _SelectionConstructorTestBase extends AbstractSingleUnitTest { + void _assertHasConstructor() { + expect(_getConstructor(), isA()); + } + + 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.'); + } + } +} diff --git a/pkg/analysis_server/test/src/utilities/extensions/test_all.dart b/pkg/analysis_server/test/src/utilities/extensions/test_all.dart index 8bf2fbc6181..ebc896c6f8f 100644 --- a/pkg/analysis_server/test/src/utilities/extensions/test_all.dart +++ b/pkg/analysis_server/test/src/utilities/extensions/test_all.dart @@ -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(); }); }