[analyzer] CreateConstructor to handle undefined enum constructor
Fixes #48481
Change-Id: I237bcbf1cb3202435c9bda31467a76f27e3dc896
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245169
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
index 439f0f4..769ed53 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor.dart
@@ -6,6 +6,7 @@
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@@ -14,7 +15,7 @@
class CreateConstructor extends CorrectionProducer {
/// The name of the constructor being created.
/// TODO(migration) We set this node when we have the change.
- late ConstructorName _constructorName;
+ late String _constructorName;
@override
List<Object> get fixArguments => [_constructorName];
@@ -24,6 +25,7 @@
@override
Future<void> compute(ChangeBuilder builder) async {
+ var node = this.node;
final argumentList = node.parent is ArgumentList ? node.parent : node;
if (argumentList is ArgumentList) {
var instanceCreation = argumentList.parent;
@@ -31,28 +33,32 @@
await _proposeFromInstanceCreation(builder, instanceCreation);
}
} else {
- await _proposeFromConstructorName(builder);
+ if (node is SimpleIdentifier) {
+ var parent = node.parent;
+ if (parent is ConstructorName) {
+ await _proposeFromConstructorName(builder, node, parent);
+ return;
+ }
+ }
+ var parent = node.thisOrAncestorOfType<EnumConstantDeclaration>();
+ if (parent != null) {
+ await _proposeFromEnumConstantDeclaration(builder, parent.name, parent);
+ }
}
}
- Future<void> _proposeFromConstructorName(ChangeBuilder builder) async {
- var name = node;
- if (name is! SimpleIdentifier) {
- return;
- }
-
+ Future<void> _proposeFromConstructorName(ChangeBuilder builder,
+ SimpleIdentifier name, ConstructorName constructorName) async {
InstanceCreationExpression? instanceCreation;
- if (name.parent is ConstructorName) {
- _constructorName = name.parent as ConstructorName;
- if (_constructorName.name == name) {
- // Type.name
- if (_constructorName.parent is InstanceCreationExpression) {
- instanceCreation =
- _constructorName.parent as InstanceCreationExpression;
- // new Type.name()
- if (instanceCreation.constructorName != _constructorName) {
- return;
- }
+ _constructorName = constructorName.toSource();
+ if (constructorName.name == name) {
+ var grandParent = constructorName.parent;
+ // Type.name
+ if (grandParent is InstanceCreationExpression) {
+ instanceCreation = grandParent;
+ // new Type.name()
+ if (grandParent.constructorName != constructorName) {
+ return;
}
}
}
@@ -63,7 +69,7 @@
}
// prepare target interface type
- var targetType = _constructorName.type.type;
+ var targetType = constructorName.type.type;
if (targetType is! InterfaceType) {
return;
}
@@ -91,28 +97,65 @@
return;
}
- var targetFile = targetElement.source.fullName;
- final instanceCreation_final = instanceCreation;
- await builder.addDartFileEdit(targetFile, (builder) {
- builder.addInsertion(targetLocation.offset, (builder) {
- builder.write(targetLocation.prefix);
- builder.writeConstructorDeclaration(targetElement.name,
- argumentList: instanceCreation_final.argumentList,
- constructorName: name,
- constructorNameGroupName: 'NAME');
- builder.write(targetLocation.suffix);
- });
- if (targetFile == file) {
- builder.addLinkedPosition(range.node(name), 'NAME');
- }
- });
+ await _write(builder, name, targetElement, targetLocation,
+ constructorName: name, argumentList: instanceCreation.argumentList);
+ }
+
+ Future<void> _proposeFromEnumConstantDeclaration(ChangeBuilder builder,
+ SimpleIdentifier name, EnumConstantDeclaration parent) async {
+ var grandParent = parent.parent;
+ if (grandParent is! EnumDeclaration) {
+ return;
+ }
+ var targetElement = grandParent.declaredElement;
+ if (targetElement == null) {
+ return;
+ }
+
+ // prepare target interface type
+ var targetResult = await sessionHelper.getElementDeclaration(targetElement);
+ if (targetResult == null) {
+ return;
+ }
+
+ var targetNode = targetResult.node;
+ if (targetNode is! EnumDeclaration) {
+ return;
+ }
+
+ var targetUnit = targetResult.resolvedUnit;
+ if (targetUnit == null) {
+ return;
+ }
+
+ // prepare location
+ var targetLocation = CorrectionUtils(targetUnit)
+ .prepareEnumNewConstructorLocation(targetNode);
+ if (targetLocation == null) {
+ return;
+ }
+
+ var arguments = parent.arguments;
+ _constructorName =
+ '${targetNode.name}${arguments?.constructorSelector ?? ''}';
+
+ await _write(
+ builder,
+ name,
+ targetElement,
+ targetLocation,
+ isConst: true,
+ constructorName: arguments?.constructorSelector?.name,
+ argumentList: arguments?.argumentList,
+ );
}
Future<void> _proposeFromInstanceCreation(ChangeBuilder builder,
InstanceCreationExpression instanceCreation) async {
- _constructorName = instanceCreation.constructorName;
+ var constructorName = instanceCreation.constructorName;
+ _constructorName = constructorName.toSource();
// should be synthetic default constructor
- var constructorElement = _constructorName.staticElement;
+ var constructorElement = constructorName.staticElement;
if (constructorElement == null ||
!constructorElement.isDefaultConstructor ||
!constructorElement.isSynthetic) {
@@ -153,4 +196,30 @@
});
});
}
+
+ Future<void> _write(
+ ChangeBuilder builder,
+ SimpleIdentifier name,
+ ClassElement targetElement,
+ InsertionLocation targetLocation, {
+ SimpleIdentifier? constructorName,
+ bool isConst = false,
+ ArgumentList? argumentList,
+ }) async {
+ var targetFile = targetElement.source.fullName;
+ await builder.addDartFileEdit(targetFile, (builder) {
+ builder.addInsertion(targetLocation.offset, (builder) {
+ builder.write(targetLocation.prefix);
+ builder.writeConstructorDeclaration(targetElement.name,
+ isConst: isConst,
+ argumentList: argumentList,
+ constructorName: constructorName,
+ constructorNameGroupName: 'NAME');
+ builder.write(targetLocation.suffix);
+ });
+ if (targetFile == file) {
+ builder.addLinkedPosition(range.node(name), 'NAME');
+ }
+ });
+ }
}
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 2faa043..5ceef0d 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -327,7 +327,7 @@
status: needsEvaluation
CompileTimeErrorCode.ENUM_CONSTANT_SAME_NAME_AS_ENCLOSING:
status: needsEvaluation
-CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR:
+CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR:
status: noFix
since: 2.17
CompileTimeErrorCode.ENUM_INSTANTIATED_TO_BOUNDS_IS_NOT_WELL_BOUNDED:
@@ -956,15 +956,12 @@
status: hasFix
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTANT:
status: hasFix
- issue: https://github.com/dart-lang/sdk/issues/47643
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED:
- status: needsFix
+ status: hasFix
since: 2.17
- issue: https://github.com/dart-lang/sdk/issues/48481
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED:
- status: needsFix
+ status: hasFix
since: 2.17
- issue: https://github.com/dart-lang/sdk/issues/48481
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER:
status: hasFix
CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD:
@@ -1881,7 +1878,7 @@
LintCode.use_decorated_box:
status: needsEvaluation
LintCode.use_enums:
- status: hasFix
+ status: hasFix
LintCode.use_full_hex_values_for_flutter_colors:
status: hasFix
LintCode.use_function_type_syntax_for_parameters:
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index dcb96bf..69727ef 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -230,6 +230,7 @@
/// Computer for Dart "fix all in file" fixes.
class FixInFileProcessor {
final DartFixContext context;
+
FixInFileProcessor(this.context);
Future<List<Fix>> compute() async {
@@ -1090,6 +1091,12 @@
AddEnumConstant.new,
ChangeTo.getterOrSetter,
],
+ CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED: [
+ CreateConstructor.new,
+ ],
+ CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED: [
+ CreateConstructor.new,
+ ],
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER: [
ChangeTo.getterOrSetter,
CreateGetter.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
index 196a197..c00ab29 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_constructor_test.dart
@@ -168,4 +168,56 @@
''');
assertLinkedGroup(change.linkedEditGroups[0], ['named(int ', 'named(1']);
}
+
+ Future<void> test_undefined_enum_constructor_named() async {
+ await resolveTestCode('''
+enum E {
+ c.x();
+ const E.y();
+}
+''');
+ await assertHasFix('''
+enum E {
+ c.x();
+ const E.y();
+
+ const E.x();
+}
+''', matchFixMessage: "Create constructor 'E.x'");
+ }
+
+ Future<void> test_undefined_enum_constructor_unnamed() async {
+ await resolveTestCode('''
+enum E {
+ c;
+ const E.x();
+}
+''');
+ await assertHasFix('''
+enum E {
+ c;
+ const E.x();
+
+ const E();
+}
+''', matchFixMessage: "Create constructor 'E'");
+ }
+
+ @FailingTest(reason: 'parameter types should be inferred')
+ Future<void> test_undefined_enum_constructor_unnamed_parameters() async {
+ await resolveTestCode('''
+enum E {
+ c(1);
+ const E.x();
+}
+''');
+ await assertHasFix('''
+enum E {
+ c(1);
+ const E.x();
+
+ const E(int i);
+}
+''');
+ }
}