Fix "Convert to a primary constructor" on generic classes.

It would incorrectly put the primary constructor parameter list before
the type's type parameter list, like:

```dart
// Before:
class C<T> {
  C();
}

// After:
class C()<T> {
}
```

This fixes it to follow the type parameter list if there is one.

Change-Id: Ib49c7df7923e9feed11f87579fefc8300c56c4a6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/506760
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Auto-Submit: Bob Nystrom <rnystrom@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Bob Nystrom <rnystrom@google.com>
This commit is contained in:
Robert Nystrom
2026-05-27 13:59:34 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 9bd89a769d
commit 2cd3ec552c
2 changed files with 59 additions and 40 deletions
@@ -68,13 +68,16 @@ class ConvertToPrimaryConstructor extends ResolvedCorrectionProducer {
if (constructor.constKeyword != null && !containerData.isEnum) {
builder.addSimpleInsertion(containerData.name.offset, 'const ');
}
builder.addInsertion(containerData.name.end, (builder) {
if (constructor.name case var name?) {
builder.write('.');
builder.write(name.lexeme);
}
builder.write(_parameterText(constructor.parameters));
});
builder.addInsertion(
containerData.typeParameters?.end ?? containerData.name.end,
(builder) {
if (constructor.name case var name?) {
builder.write('.');
builder.write(name.lexeme);
}
builder.write(_parameterText(constructor.parameters));
},
);
// Remove the constructor that was converted.
//
@@ -104,39 +107,24 @@ class ConvertToPrimaryConstructor extends ResolvedCorrectionProducer {
/// Returns information about the declaration in which the [constructor] is
/// declared.
_ContainerData? _getContainerData(ConstructorDeclaration constructor) {
Token name;
bool hasPrimaryConstructor;
int nonRedirectingGenerativeConstructorCount;
bool isEnum = false;
var parent = constructor.parent;
switch (parent) {
case BlockClassBody body:
parent = body.parent;
case BlockEnumBody body:
parent = body.parent;
}
switch (parent) {
case ClassDeclaration(:var namePart):
name = namePart.typeName;
hasPrimaryConstructor = namePart is PrimaryConstructorDeclaration;
nonRedirectingGenerativeConstructorCount =
_nonRedirectingGenerativeConstructorCount(parent.classMembers);
case EnumDeclaration(:var namePart):
name = namePart.typeName;
hasPrimaryConstructor = namePart is PrimaryConstructorDeclaration;
nonRedirectingGenerativeConstructorCount =
_nonRedirectingGenerativeConstructorCount(parent.classMembers);
isEnum = true;
default:
return null;
}
return _ContainerData(
name: name,
hasPrimaryConstructor: hasPrimaryConstructor,
hasMultipleNonRedirectingGenerativeConstructors:
nonRedirectingGenerativeConstructorCount > 1,
isEnum: isEnum,
);
var parent = switch (constructor.parent) {
BlockClassBody body => body.parent,
BlockEnumBody body => body.parent,
var parent => parent,
};
return switch (parent) {
ClassDeclaration(:var namePart) ||
EnumDeclaration(:var namePart) => _ContainerData(
name: namePart.typeName,
typeParameters: namePart.typeParameters,
hasPrimaryConstructor: namePart is PrimaryConstructorDeclaration,
hasMultipleNonRedirectingGenerativeConstructors:
_nonRedirectingGenerativeConstructorCount(parent.classMembers) > 1,
isEnum: parent is EnumDeclaration,
),
_ => null,
};
}
/// Returns the number of non-redirecting generative constructors in the list
@@ -163,6 +151,10 @@ class _ContainerData {
/// The name of the class or enum.
Token name;
/// The type parameters on the class or enum or `null` if the type isn't
/// generic.
TypeParameterList? typeParameters;
/// Whether the class or enum already has a primary constructor.
bool hasPrimaryConstructor;
@@ -175,6 +167,7 @@ class _ContainerData {
_ContainerData({
required this.name,
required this.typeParameters,
required this.hasPrimaryConstructor,
required this.hasMultipleNonRedirectingGenerativeConstructors,
required this.isEnum,
@@ -20,6 +20,18 @@ class ConvertToPrimaryConstructorClassTest extends AssistProcessorTest {
@override
AssistKind get kind => DartAssistKind.convertToPrimaryConstructor;
Future<void> test_genericClass() async {
await resolveTestCode('''
class C<T> {
C^();
}
''');
await assertHasAssist('''
class C<T>() {
}
''');
}
Future<void> test_noParameters_noBody_named() async {
await resolveTestCode('''
class C {
@@ -483,6 +495,20 @@ class ConvertToPrimaryConstructorEnumTest extends AssistProcessorTest {
@override
AssistKind get kind => DartAssistKind.convertToPrimaryConstructor;
Future<void> test_genericEnum() async {
await resolveTestCode('''
enum C<T> {
a;
C^();
}
''');
await assertHasAssist('''
enum C<T>() {
a;
}
''');
}
Future<void> test_noParameters_noBody() async {
await resolveTestCode('''
enum E {