diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_field.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_field.dart index e151f165c37a..1b65214291a3 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_field.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_field.dart @@ -65,7 +65,7 @@ class CreateField extends CreateFieldOrGetter { if (targetElement.library.isInSdk) { return; } - // Prepare target ClassDeclaration. + // Prepare target `ClassDeclaration`. var targetDeclarationResult = await sessionHelper.getElementDeclaration(targetElement); if (targetDeclarationResult == null) { @@ -75,7 +75,9 @@ class CreateField extends CreateFieldOrGetter { if (targetNode is! CompilationUnitMember) { return; } - if (!(targetNode is ClassDeclaration || targetNode is MixinDeclaration)) { + if (!(targetNode is ClassDeclaration || + targetNode is EnumDeclaration || + targetNode is MixinDeclaration)) { return; } // Build field source. @@ -83,19 +85,29 @@ class CreateField extends CreateFieldOrGetter { var targetFile = targetSource.fullName; await builder.addDartFileEdit(targetFile, (builder) { builder.insertField(targetNode, (builder) { - builder.writeFieldDeclaration(_fieldName, - isStatic: staticModifier, - nameGroupName: 'NAME', - type: fieldType, - typeGroupName: 'TYPE'); + builder.writeFieldDeclaration( + _fieldName, + isFinal: targetNode is EnumDeclaration, + isStatic: staticModifier, + nameGroupName: 'NAME', + type: fieldType, + typeGroupName: 'TYPE', + ); }); }); } Future _proposeFromFieldFormalParameter( ChangeBuilder builder, FieldFormalParameter parameter) async { - var targetClassNode = parameter.thisOrAncestorOfType(); - if (targetClassNode == null) { + var constructor = parameter.thisOrAncestorOfType(); + if (constructor == null) { + return; + } + var container = constructor.thisOrAncestorOfType(); + if (container == null) { + return; + } + if (container is! ClassDeclaration && container is! EnumDeclaration) { return; } @@ -103,10 +115,14 @@ class CreateField extends CreateFieldOrGetter { // Add proposal. await builder.addDartFileEdit(file, (builder) { - var fieldType = parameter.type?.type; - builder.insertField(targetClassNode, (builder) { - builder.writeFieldDeclaration(_fieldName, - nameGroupName: 'NAME', type: fieldType, typeGroupName: 'TYPE'); + builder.insertField(container, (builder) { + builder.writeFieldDeclaration( + _fieldName, + isFinal: constructor.constKeyword != null, + nameGroupName: 'NAME', + type: parameter.declaredElement?.type, + typeGroupName: 'TYPE', + ); }); }); } @@ -117,22 +133,18 @@ class CreateField extends CreateFieldOrGetter { return; } _fieldName = nameNode.name; - // prepare target Expression - Expression? target; - { - var nameParent = nameNode.parent; - if (nameParent is PrefixedIdentifier) { - target = nameParent.prefix; - } else if (nameParent is PropertyAccess) { - target = nameParent.realTarget; - } - } - // prepare target ClassElement + // Prepare target `Expression`. + var target = switch (nameNode.parent) { + PrefixedIdentifier(:var prefix) => prefix, + PropertyAccess(:var realTarget) => realTarget, + _ => null, + }; + // Prepare target `ClassElement`. var staticModifier = false; InterfaceElement? targetClassElement; if (target != null) { targetClassElement = getTargetInterfaceElement(target); - // maybe static + // Maybe static. if (target is Identifier) { var targetIdentifier = target; var targetElement = targetIdentifier.staticElement; @@ -147,6 +159,14 @@ class CreateField extends CreateFieldOrGetter { } var fieldTypeNode = climbPropertyAccess(nameNode); + var fieldTypeParent = fieldTypeNode.parent; + if (targetClassElement is EnumElement && + fieldTypeParent is AssignmentExpression && + fieldTypeNode == fieldTypeParent.leftHandSide) { + // Any field on an enum must be final; creating a final field does not + // make sense when seen in an assignment expression. + return; + } var fieldType = inferUndefinedExpressionType(fieldTypeNode); await _addDeclaration( diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart index 3d0fd50b6fc4..d32009bb5f1a 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart @@ -6,6 +6,7 @@ import 'package:analysis_server/src/services/correction/dart/abstract_producer.d import 'package:analysis_server/src/services/correction/fix.dart'; import 'package:analysis_server/src/utilities/strings.dart'; import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/src/error/inheritance_override.dart'; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; @@ -22,13 +23,17 @@ class CreateMissingOverrides extends ResolvedCorrectionProducer { @override Future compute(ChangeBuilder builder) async { - final targetClass = node; - if (targetClass is! ClassDeclaration) { + final targetDeclaration = node; + if (targetDeclaration is! NamedCompilationUnitMember) { + return; + } + if (targetDeclaration is! ClassDeclaration && + targetDeclaration is! EnumDeclaration) { return; } var signatures = [ - ...InheritanceOverrideVerifier.missingOverrides(targetClass), - ...InheritanceOverrideVerifier.missingMustBeOverridden(targetClass) + ...InheritanceOverrideVerifier.missingOverrides(targetDeclaration), + ...InheritanceOverrideVerifier.missingMustBeOverridden(targetDeclaration) ]; // Sort by name, getters before setters. signatures.sort((ExecutableElement a, ExecutableElement b) { @@ -45,7 +50,7 @@ class CreateMissingOverrides extends ResolvedCorrectionProducer { var prefix = utils.oneIndent; await builder.addDartFileEdit(file, (builder) { - builder.insertIntoUnitMember(targetClass, (builder) { + builder.insertIntoUnitMember(targetDeclaration, (builder) { // Separator management. var numOfMembersWritten = 0; void addSeparatorBetweenDeclarations() { @@ -80,6 +85,10 @@ class CreateMissingOverrides extends ResolvedCorrectionProducer { builder.write(eol); // Add field. builder.write(prefix); + if (targetDeclaration is EnumDeclaration) { + builder.write(Keyword.FINAL.lexeme); + builder.write(' '); + } builder.writeType(element.returnType, required: true); builder.write(' '); builder.write(element.name); diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart index 1b5d1e675164..8070b265d8ea 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart @@ -10,11 +10,111 @@ import 'fix_processor.dart'; void main() { defineReflectiveSuite(() { - defineReflectiveTests(CreateFieldTest); + defineReflectiveTests(CreateFieldEnumTest); defineReflectiveTests(CreateFieldMixinTest); + defineReflectiveTests(CreateFieldTest); }); } +@reflectiveTest +class CreateFieldEnumTest extends FixProcessorTest { + @override + FixKind get kind => DartFixKind.CREATE_FIELD; + + Future test_initializingFormal_dynamic() async { + await resolveTestCode(''' +enum E { + one(1), two(2); + + const E(dynamic this.f); +} +'''); + await assertHasFix(''' +enum E { + one(1), two(2); + + final dynamic f; + + const E(dynamic this.f); +} +'''); + } + + Future test_initializingFormal_withoutType() async { + await resolveTestCode(''' +enum E { + one(1), two(2); + + const E(this.f); +} +'''); + await assertHasFix(''' +enum E { + one(1), two(2); + + final dynamic f; + + const E(this.f); +} +'''); + } + + Future test_initializingFormal_withType() async { + await resolveTestCode(''' +enum E { + one(1), two(2); + + const E(int this.f); +} +'''); + await assertHasFix(''' +enum E { + one(1), two(2); + + final int f; + + const E(int this.f); +} +'''); + } + + Future test_usedAsGetter() async { + await resolveTestCode(''' +enum E { + one, two; +} + +int f(E e) { + return e.a; +} +'''); + await assertHasFix(''' +enum E { + one, two; + + final int a; +} + +int f(E e) { + return e.a; +} +'''); + } + + Future test_usedAsSetter() async { + await resolveTestCode(''' +enum E { + one, two; +} + +void f(E e) { + e.a = 1; +} +'''); + await assertNoFix(); + } +} + @reflectiveTest class CreateFieldMixinTest extends FixProcessorTest { @override @@ -363,6 +463,81 @@ void f() { await assertNoFix(); } + Future test_initializingFormal_functionTyped() async { + await resolveTestCode(''' +class C { + C(String this.text()); +} +'''); + await assertHasFix(''' +class C { + String Function() text; + + C(String this.text()); +} +'''); + } + + Future test_initializingFormal_typeVariable() async { + await resolveTestCode(''' +class C { + C(T this.text); +} +'''); + await assertHasFix(''' +class C { + T text; + + C(T this.text); +} +'''); + } + + Future test_initializingFormal_withDefaultValue() async { + await resolveTestCode(''' +class C { + C([String this.text = '']); +} +'''); + await assertHasFix(''' +class C { + String text; + + C([String this.text = '']); +} +'''); + } + + Future test_initializingFormal_withType() async { + await resolveTestCode(''' +class C { + C(String this.text); +} +'''); + await assertHasFix(''' +class C { + String text; + + C(String this.text); +} +'''); + } + + Future test_initializingFormal_withType_constConstuctor() async { + await resolveTestCode(''' +class C { + const C(String this.text); +} +'''); + await assertHasFix(''' +class C { + final String text; + + const C(String this.text); +} +'''); + } + Future test_inPart_self() async { await resolveTestCode(''' part of lib; @@ -400,21 +575,6 @@ class C { '''); } - Future test_invalidInitializer_withType() async { - await resolveTestCode(''' -class C { - C(String this.text); -} -'''); - await assertHasFix(''' -class C { - String text; - - C(String this.text); -} -'''); - } - Future test_objectPattern_explicitName_variablePattern_typed() async { await resolveTestCode(''' void f(Object? x) { diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart index 319979267b9c..44e4d851f378 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart @@ -99,6 +99,32 @@ class B implements A { }); } + Future test_field_inEnum() async { + await resolveTestCode(''' +abstract class A { + int get foo; + set foo(int value); +} + +enum E implements A { + one, two; +} +'''); + await assertHasFix(''' +abstract class A { + int get foo; + set foo(int value); +} + +enum E implements A { + one, two; + + @override + final int foo; +} +'''); + } + Future test_field_untyped() async { await resolveTestCode(''' class A { @@ -516,6 +542,62 @@ class X implements B { '''); } + Future test_method_inEnum() async { + await resolveTestCode(''' +abstract class A { + void foo(); +} + +enum E implements A { + one, two; +} +'''); + await assertHasFix(''' +abstract class A { + void foo(); +} + +enum E implements A { + one, two; + + @override + void foo() { + // TODO: implement foo + } +} +'''); + } + + Future test_method_inEnumWithMembers() async { + await resolveTestCode(''' +abstract class A { + void foo(); +} + +enum E implements A { + one, two; + + void bar() {} +} +'''); + await assertHasFix(''' +abstract class A { + void foo(); +} + +enum E implements A { + one, two; + + void bar() {} + + @override + void foo() { + // TODO: implement foo + } +} +'''); + } + Future test_method_multiple() async { await resolveTestCode(''' abstract class A { diff --git a/pkg/analyzer/lib/src/error/inheritance_override.dart b/pkg/analyzer/lib/src/error/inheritance_override.dart index 4d7e693d3697..f3c60fd136be 100644 --- a/pkg/analyzer/lib/src/error/inheritance_override.dart +++ b/pkg/analyzer/lib/src/error/inheritance_override.dart @@ -124,13 +124,14 @@ class InheritanceOverrideVerifier { /// Returns [Element] members that are in the interface of the /// given class with `@mustBeOverridden`, but don't have implementations. static List missingMustBeOverridden( - ClassDeclaration node) { + NamedCompilationUnitMember node) { return _missingMustBeOverridden[node.name] ?? const []; } /// Returns [ExecutableElement] members that are in the interface of the /// given class, but don't have concrete implementations. - static List missingOverrides(ClassDeclaration node) { + static List missingOverrides( + NamedCompilationUnitMember node) { return _missingOverrides[node.name] ?? const []; } } diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart index e3c4cb644910..8ae0f84442c5 100644 --- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart +++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart @@ -198,7 +198,13 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { typeRequired = false; } if (type != null) { - writeType(type, groupName: typeGroupName, required: true); + // `writeType` will write `var` for `dynamic`, which we cannot use after + // `final`. + if (isFinal && type is DynamicType) { + write(Keyword.DYNAMIC.lexeme); + } else { + writeType(type, groupName: typeGroupName, required: !isFinal); + } write(' '); } else if (typeRequired) { write(Keyword.VAR.lexeme); @@ -721,11 +727,13 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { } @override - bool writeType(DartType? type, - {bool addSupertypeProposals = false, - String? groupName, - ExecutableElement? methodBeingCopied, - bool required = false}) { + bool writeType( + DartType? type, { + bool addSupertypeProposals = false, + String? groupName, + ExecutableElement? methodBeingCopied, + bool required = false, + }) { var wroteType = false; if (type != null && type is! DynamicType) { if (groupName != null) {