Issue 52601. Extend 'Create constructor for final fields' to support enums.

Bug: https://github.com/dart-lang/sdk/issues/52601
Change-Id: I3e8dcc288864ca7c68992cb1218e66b2d34c5aa8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/307669
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Konstantin Shcheglov
2023-06-07 02:11:27 +00:00
committed by Commit Queue
parent 5cb4093613
commit bdd145a39a
6 changed files with 208 additions and 96 deletions
@@ -329,10 +329,6 @@ class CheckNameResponse {
} else if (node is EnumDeclaration) {
var utils = CorrectionUtils(resolvedUnit);
var location = utils.prepareEnumNewConstructorLocation(node);
if (location == null) {
return null;
}
var header = 'const ${interfaceElement.name}.$newName();';
return CiderReplaceMatch(libraryPath, [
ReplaceInfo(location.prefix + header + location.suffix,
@@ -132,9 +132,6 @@ class CreateConstructor extends CorrectionProducer {
// prepare location
var targetLocation = CorrectionUtils(targetUnit)
.prepareEnumNewConstructorLocation(targetNode);
if (targetLocation == null) {
return;
}
var arguments = parent.arguments;
_constructorName =
@@ -5,6 +5,7 @@
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server/src/utilities/extensions/object.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -17,32 +18,50 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
@override
FixKind get fixKind => DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS;
FieldDeclaration? get _errorFieldDeclaration {
if (node is VariableDeclaration) {
final fieldDeclaration = node.parent?.parent;
return fieldDeclaration?.ifTypeOrNull();
}
return null;
}
@override
Future<void> compute(ChangeBuilder builder) async {
if (node is! VariableDeclaration) {
final fieldDeclaration = _errorFieldDeclaration;
if (fieldDeclaration == null) {
return;
}
var classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();
if (classDeclaration == null) {
return;
final containerDeclaration = fieldDeclaration.parent;
switch (containerDeclaration) {
case ClassDeclaration():
await _classDeclaration(
builder: builder,
classDeclaration: containerDeclaration,
);
case EnumDeclaration():
await _enumDeclaration(
builder: builder,
enumDeclaration: containerDeclaration,
);
}
}
Future<void> _classDeclaration({
required ChangeBuilder builder,
required ClassDeclaration classDeclaration,
}) async {
var className = classDeclaration.name.lexeme;
var superType = classDeclaration.declaredElement?.supertype;
if (superType == null) {
return;
}
var variableLists = <VariableDeclarationList>[];
for (var member in classDeclaration.members) {
if (member is FieldDeclaration) {
var variableList = member.fields;
if (variableList.isFinal && !variableList.isLate) {
variableLists.add(variableList);
}
}
}
final variableLists = _interestingVariableLists(
classDeclaration.members,
);
// prepare location for a new constructor
var targetLocation = utils.prepareNewConstructorLocation(
resolvedResult.session, classDeclaration);
@@ -50,52 +69,101 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
return;
}
final fixContext = _FixContext(
builder: builder,
containerName: className,
location: targetLocation,
variableLists: variableLists,
);
if (flutter.isExactlyStatelessWidgetType(superType) ||
flutter.isExactlyStatefulWidgetType(superType)) {
// Specialize for Flutter widgets.
var keyClass = await sessionHelper.getClass(flutter.widgetsUri, 'Key');
if (keyClass == null) {
return;
}
if (unit.featureSet.isEnabled(Feature.super_parameters)) {
await _withSuperParameters(
builder, targetLocation, className, variableLists);
} else {
await _withoutSuperParameters(
builder, targetLocation, className, keyClass, variableLists);
}
await _forFlutterClass(fixContext);
} else {
var fieldNames = <String>[];
for (var variableList in variableLists) {
fieldNames.addAll(variableList.variables
.where((v) => v.initializer == null)
.map((v) => v.name.lexeme));
}
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(targetLocation.offset, (builder) {
builder.write(targetLocation.prefix);
builder.writeConstructorDeclaration(className,
fieldNames: fieldNames);
builder.write(targetLocation.suffix);
});
});
await _notFlutter(
fixContext: fixContext,
isConst: false,
);
}
}
Future<void> _withoutSuperParameters(
ChangeBuilder builder,
InsertionLocation targetLocation,
String className,
ClassElement keyClass,
List<VariableDeclarationList> variableLists) async {
Future<void> _enumDeclaration({
required ChangeBuilder builder,
required EnumDeclaration enumDeclaration,
}) async {
final enumName = enumDeclaration.name.lexeme;
final variableLists = _interestingVariableLists(
enumDeclaration.members,
);
final targetLocation =
utils.prepareEnumNewConstructorLocation(enumDeclaration);
await _notFlutter(
fixContext: _FixContext(
builder: builder,
containerName: enumName,
location: targetLocation,
variableLists: variableLists,
),
isConst: true,
);
}
Future<void> _forFlutterClass(_FixContext fixContext) async {
// Specialize for Flutter widgets.
var keyClass = await sessionHelper.getClass(flutter.widgetsUri, 'Key');
if (keyClass == null) {
return;
}
if (unit.featureSet.isEnabled(Feature.super_parameters)) {
await _withSuperParameters(fixContext);
} else {
await _withoutSuperParameters(
fixContext: fixContext,
keyClass: keyClass,
);
}
}
Future<void> _notFlutter({
required _FixContext fixContext,
required bool isConst,
}) async {
final fieldNames = fixContext.variableLists
.expand((variableList) => variableList.variables)
.where((variable) => variable.initializer == null)
.map((variable) => variable.name.lexeme)
.toList();
final location = fixContext.location;
await fixContext.builder.addDartFileEdit(file, (builder) {
builder.addInsertion(location.offset, (builder) {
builder.write(location.prefix);
if (isConst) {
builder.write('const ');
}
builder.writeConstructorDeclaration(
fixContext.containerName,
fieldNames: fieldNames,
);
builder.write(location.suffix);
});
});
}
Future<void> _withoutSuperParameters({
required _FixContext fixContext,
required ClassElement keyClass,
}) async {
final location = fixContext.location;
var isNonNullable = unit.featureSet.isEnabled(Feature.non_nullable);
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(targetLocation.offset, (builder) {
builder.write(targetLocation.prefix);
await fixContext.builder.addDartFileEdit(file, (builder) {
builder.addInsertion(location.offset, (builder) {
builder.write(location.prefix);
builder.write('const ');
builder.write(className);
builder.write(fixContext.containerName);
builder.write('({');
builder.writeType(
keyClass.instantiate(
@@ -107,37 +175,45 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
);
builder.write(' key');
_writeParameters(builder, variableLists, isNonNullable);
_writeParameters(
builder: builder,
variableLists: fixContext.variableLists,
isNonNullable: isNonNullable,
);
builder.write('}) : super(key: key);');
builder.write(targetLocation.suffix);
builder.write(location.suffix);
});
});
}
Future<void> _withSuperParameters(
ChangeBuilder builder,
InsertionLocation targetLocation,
String className,
List<VariableDeclarationList> variableLists) async {
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(targetLocation.offset, (builder) {
builder.write(targetLocation.prefix);
Future<void> _withSuperParameters(_FixContext fixContext) async {
await fixContext.builder.addDartFileEdit(file, (builder) {
final location = fixContext.location;
builder.addInsertion(location.offset, (builder) {
builder.write(location.prefix);
builder.write('const ');
builder.write(className);
builder.write(fixContext.containerName);
builder.write('({');
builder.write('super.key');
_writeParameters(builder, variableLists, true);
_writeParameters(
builder: builder,
variableLists: fixContext.variableLists,
isNonNullable: true,
);
builder.write('});');
builder.write(targetLocation.suffix);
builder.write(location.suffix);
});
});
}
void _writeParameters(DartEditBuilder builder,
List<VariableDeclarationList> variableLists, bool isNonNullable) {
void _writeParameters({
required DartEditBuilder builder,
required List<VariableDeclarationList> variableLists,
required bool isNonNullable,
}) {
var childrenFields = <String>[];
var childrenNullables = <bool>[];
for (var variableList in variableLists) {
@@ -145,17 +221,17 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
.where((v) => v.initializer == null)
.map((v) => v.name.lexeme);
final hasNullableType = variableList.type?.type?.nullabilitySuffix ==
NullabilitySuffix.question;
for (var fieldName in fieldNames) {
if (fieldName == 'child' || fieldName == 'children') {
childrenFields.add(fieldName);
childrenNullables.add(variableList.type?.type?.nullabilitySuffix ==
NullabilitySuffix.question);
childrenNullables.add(hasNullableType);
continue;
}
builder.write(', ');
if (isNonNullable &&
variableList.type?.type?.nullabilitySuffix !=
NullabilitySuffix.question) {
if (isNonNullable && !hasNullableType) {
builder.write('required ');
}
builder.write('this.');
@@ -173,4 +249,28 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
builder.write(fieldName);
}
}
static List<VariableDeclarationList> _interestingVariableLists(
List<ClassMember> members,
) {
return members
.whereType<FieldDeclaration>()
.map((e) => e.fields)
.where((e) => e.isFinal && !e.isLate)
.toList();
}
}
class _FixContext {
final ChangeBuilder builder;
final String containerName;
final InsertionLocation location;
final List<VariableDeclarationList> variableLists;
_FixContext({
required this.builder,
required this.containerName,
required this.location,
required this.variableLists,
});
}
@@ -1108,7 +1108,7 @@ class CorrectionUtils {
return null;
}
InsertionLocation? prepareEnumNewConstructorLocation(
InsertionLocation prepareEnumNewConstructorLocation(
EnumDeclaration enumDeclaration,
) {
var indent = getIndent(1);
@@ -207,10 +207,6 @@ class RenameConstructorRefactoringImpl extends RenameRefactoringImpl {
} else if (node is EnumDeclaration) {
var utils = CorrectionUtils(resolvedUnit);
var location = utils.prepareEnumNewConstructorLocation(node);
if (location == null) {
return;
}
var header = 'const ${classElement.name}.$newName();';
doSourceChange_addElementEdit(
change,
@@ -23,7 +23,7 @@ class CreateConstructorForFinalFieldsTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS;
Future<void> test_excludesLate() async {
Future<void> test_class_excludesLate() async {
await resolveTestCode('''
class Test {
final int a;
@@ -40,7 +40,7 @@ class Test {
''');
}
Future<void> test_flutter() async {
Future<void> test_class_flutter() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -66,7 +66,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childLast() async {
Future<void> test_class_flutter_childLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -92,7 +92,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childrenLast() async {
Future<void> test_class_flutter_childrenLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -118,7 +118,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_inTopLevelMethod() async {
Future<void> test_class_inTopLevelMethod() async {
await resolveTestCode('''
void f() {
final int v;
@@ -128,7 +128,7 @@ void f() {
await assertNoFix();
}
Future<void> test_lint_sortConstructorsFirst() async {
Future<void> test_class_lint_sortConstructorsFirst() async {
createAnalysisOptionsFile(lints: [LintNames.sort_constructors_first]);
await resolveTestCode('''
class Test {
@@ -150,7 +150,7 @@ class Test {
});
}
Future<void> test_simple() async {
Future<void> test_class_simple() async {
await resolveTestCode('''
class Test {
final int a;
@@ -171,6 +171,29 @@ class Test {
});
}
Future<void> test_enum_simple() async {
await resolveTestCode('''
enum E {
v(0, 2);
final int a;
final int b = 1;
final int c;
}
''');
await assertHasFix('''
enum E {
v(0, 2);
final int a;
final int b = 1;
final int c;
const E(this.a, this.c);
}
''', errorFilter: (error) {
return error.message.contains("'a'");
});
}
Future<void> test_topLevelField() async {
await resolveTestCode('''
final int v;
@@ -188,7 +211,7 @@ class CreateConstructorForFinalFieldsWithoutNullSafetyTest
@override
String get testPackageLanguageVersion => '2.9';
Future<void> test_flutter() async {
Future<void> test_class_flutter() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -214,7 +237,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childLast() async {
Future<void> test_class_flutter_childLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -240,7 +263,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childrenLast() async {
Future<void> test_class_flutter_childrenLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -276,7 +299,7 @@ class CreateConstructorForFinalFieldsWithoutSuperParametersTest
@override
String get testPackageLanguageVersion => '2.16';
Future<void> test_flutter() async {
Future<void> test_class_flutter() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -302,7 +325,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childLast() async {
Future<void> test_class_flutter_childLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';
@@ -328,7 +351,7 @@ class MyWidget extends StatelessWidget {
});
}
Future<void> test_flutter_childrenLast() async {
Future<void> test_class_flutter_childrenLast() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
import 'package:flutter/widgets.dart';