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 7a88c8f3533..11ab49ff566 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 @@ -2467,6 +2467,8 @@ unnecessary_breaks: status: hasFix unnecessary_const: status: hasFix +unnecessary_const_in_enum_constructor: + status: hasFix unnecessary_constructor_name: status: hasFix unnecessary_final_with_type: 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 be9bddf1189..13ee8d6c580 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -439,6 +439,7 @@ final _builtInLintGenerators = >{ diag.unnecessaryBraceInStringInterps: [RemoveInterpolationBraces.new], diag.unnecessaryBreaks: [RemoveBreak.new], diag.unnecessaryConst: [RemoveUnnecessaryConst.new], + diag.unnecessaryConstInEnumConstructor: [RemoveUnnecessaryConst.new], diag.unnecessaryConstructorName: [RemoveConstructorName.new], diag.unnecessaryFinalWithType: [ReplaceFinalWithVar.new], diag.unnecessaryFinalWithoutType: [ReplaceFinalWithVar.new], diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_const_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_const_test.dart index 8136b7b44ef..234b559a7f6 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_const_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_const_test.dart @@ -11,6 +11,8 @@ import 'fix_processor.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(RemoveUnnecessaryConstBulkTest); + defineReflectiveTests(RemoveUnnecessaryConstInEnumConstructorBulkTest); + defineReflectiveTests(RemoveUnnecessaryConstInEnumConstructorTest); defineReflectiveTests(RemoveUnnecessaryConstTest); }); } @@ -38,6 +40,83 @@ var d = const D(C()); } } +@reflectiveTest +class RemoveUnnecessaryConstInEnumConstructorBulkTest + extends BulkFixProcessorTest { + @override + String get lintCode => LintNames.unnecessary_const_in_enum_constructor; + + Future test_singleFile() async { + await resolveTestCode(''' +enum const E1(final int i) { + a(1), b(2); +} +enum E2 { + a(1), b(2); + + const E2(this.i); + + final int i; +} +'''); + await assertHasFix(''' +enum E1(final int i) { + a(1), b(2); +} +enum E2 { + a(1), b(2); + + E2(this.i); + + final int i; +} +'''); + } +} + +@reflectiveTest +class RemoveUnnecessaryConstInEnumConstructorTest extends FixProcessorLintTest { + @override + FixKind get kind => DartFixKind.removeUnnecessaryConst; + + @override + String get lintCode => LintNames.unnecessary_const_in_enum_constructor; + + Future test_enumConstructor_primary() async { + await resolveTestCode(''' +enum const E(final int i) { + a(1), b(2); +} +'''); + await assertHasFix(''' +enum E(final int i) { + a(1), b(2); +} +'''); + } + + Future test_enumConstructor_secondary() async { + await resolveTestCode(''' +enum E { + a(1), b(2); + + const E(this.i); + + final int i; +} +'''); + await assertHasFix(''' +enum E { + a(1), b(2); + + E(this.i); + + final int i; +} +'''); + } +} + @reflectiveTest class RemoveUnnecessaryConstTest extends FixProcessorLintTest { @override diff --git a/pkg/linter/example/all.yaml b/pkg/linter/example/all.yaml index d7fd566440b..6ff050166e2 100644 --- a/pkg/linter/example/all.yaml +++ b/pkg/linter/example/all.yaml @@ -187,6 +187,7 @@ linter: - unnecessary_brace_in_string_interps - unnecessary_breaks - unnecessary_const + - unnecessary_const_in_enum_constructor - unnecessary_constructor_name - unnecessary_final - unnecessary_getters_setters diff --git a/pkg/linter/lib/src/diagnostic.g.dart b/pkg/linter/lib/src/diagnostic.g.dart index 27a1921b5f2..adf2947db25 100644 --- a/pkg/linter/lib/src/diagnostic.g.dart +++ b/pkg/linter/lib/src/diagnostic.g.dart @@ -3108,6 +3108,16 @@ const LinterLintWithoutArguments unnecessaryConst = LinterLintWithoutArguments( expectedTypes: [], ); +/// No parameters. +const LinterLintWithoutArguments unnecessaryConstInEnumConstructor = + LinterLintWithoutArguments( + name: 'unnecessary_const_in_enum_constructor', + problemMessage: "Unnecessary 'const' keyword in an enum constructor.", + correctionMessage: "Try removing the keyword.", + uniqueName: 'unnecessary_const_in_enum_constructor', + expectedTypes: [], + ); + /// No parameters. const LinterLintWithoutArguments unnecessaryConstructorName = LinterLintWithoutArguments( diff --git a/pkg/linter/lib/src/lint_names.g.dart b/pkg/linter/lib/src/lint_names.g.dart index 23d1fcd4122..04de8571be0 100644 --- a/pkg/linter/lib/src/lint_names.g.dart +++ b/pkg/linter/lib/src/lint_names.g.dart @@ -529,6 +529,9 @@ abstract final class LintNames { static const String unnecessary_const = 'unnecessary_const'; + static const String unnecessary_const_in_enum_constructor = + 'unnecessary_const_in_enum_constructor'; + static const String unnecessary_constructor_name = 'unnecessary_constructor_name'; diff --git a/pkg/linter/lib/src/rules.dart b/pkg/linter/lib/src/rules.dart index 011063805c3..1822d2ac151 100644 --- a/pkg/linter/lib/src/rules.dart +++ b/pkg/linter/lib/src/rules.dart @@ -208,6 +208,7 @@ import 'rules/unnecessary_await_in_return.dart'; import 'rules/unnecessary_brace_in_string_interps.dart'; import 'rules/unnecessary_breaks.dart'; import 'rules/unnecessary_const.dart'; +import 'rules/unnecessary_const_in_enum_constructor.dart'; import 'rules/unnecessary_constructor_name.dart'; import 'rules/unnecessary_final.dart'; import 'rules/unnecessary_getters_setters.dart'; @@ -466,6 +467,7 @@ void registerLintRules() { ..registerLintRule(UnnecessaryBraceInStringInterps()) ..registerLintRule(UnnecessaryBreaks()) ..registerLintRule(UnnecessaryConst()) + ..registerLintRule(UnnecessaryConstInEnumConstructor()) ..registerLintRule(UnnecessaryConstructorName()) ..registerLintRule(UnnecessaryFinal()) ..registerLintRule(UnnecessaryGettersSetters()) diff --git a/pkg/linter/lib/src/rules/unnecessary_const_in_enum_constructor.dart b/pkg/linter/lib/src/rules/unnecessary_const_in_enum_constructor.dart new file mode 100644 index 00000000000..a1682b05d9e --- /dev/null +++ b/pkg/linter/lib/src/rules/unnecessary_const_in_enum_constructor.dart @@ -0,0 +1,64 @@ +// 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:analyzer/analysis_rule/analysis_rule.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/dart/analysis/features.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/error/error.dart'; + +import '../analyzer.dart'; +import '../diagnostic.dart' as diag; + +const _desc = "Don't use an explicit `const` in a generative enum constructor."; + +class UnnecessaryConstInEnumConstructor extends AnalysisRule { + UnnecessaryConstInEnumConstructor() + : super( + name: LintNames.unnecessary_const_in_enum_constructor, + description: _desc, + ); + + @override + bool get canUseParsedResult => true; + + @override + DiagnosticCode get diagnosticCode => diag.unnecessaryConstInEnumConstructor; + + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + if (!context.isFeatureEnabled(Feature.primary_constructors)) { + return; + } + var visitor = _Visitor(this); + registry.addConstructorDeclaration(this, visitor); + registry.addPrimaryConstructorDeclaration(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final AnalysisRule rule; + _Visitor(this.rule); + + @override + void visitConstructorDeclaration(ConstructorDeclaration node) { + var constKeyword = node.constKeyword; + if (constKeyword != null) { + rule.reportAtToken(constKeyword); + } + } + + @override + void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) { + var constKeyword = node.constKeyword; + if (constKeyword != null) { + rule.reportAtToken(constKeyword); + } + } +} diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml index b83a3407fd4..6041714e3ea 100644 --- a/pkg/linter/messages.yaml +++ b/pkg/linter/messages.yaml @@ -13759,6 +13759,106 @@ LinterLintCode: final b = const [A()]; } ``` + unnecessaryConstInEnumConstructor: + type: lint + parameters: none + problemMessage: "Unnecessary 'const' keyword in an enum constructor." + correctionMessage: "Try removing the keyword." + state: + experimental: "3.14" + categories: [brevity, style] + hasPublishedDocs: false + documentation: |- + #### Description + + The analyzer produces this diagnostic when the keyword `const` is used in + a generative enum constructor. Generative num constructors are implicitly + `const`. + + #### Examples + + The following code produces this diagnostic because the keyword `const` in + the enum's primary constructor isn't needed: + + ```dart + %experiments=primary-constructors + enum [!const!] E(final int i) { + a(1), b(2); + } + ``` + + The following code produces this diagnostic because the keyword `const` in + the enum's secondary constructor isn't needed: + + ```dart + %experiments=primary-constructors + enum E { + a(1), b(2); + + [!const!] E(this.i); + + final int i; + } + ``` + + #### Common fixes + + Remove the unnecessary keyword: + + ```dart + %experiments=primary-constructors + enum E(final int i) { + a(1), b(2); + } + ``` + + ```dart + %experiments=primary-constructors + enum E { + a(1), b(2); + + E(this.i); + + final int i; + } + ``` + deprecatedDetails: |- + Don't use an explicit `const` in a generative enum constructor. Generative + enum constructors are implicitly `const`. + + **BAD:** + ```dart + enum const E(final int i) { + a(1), b(2); + } + ``` + + ```dart + enum E { + a(1), b(2); + + const E(this.i); + + final int i; + } + ``` + + **GOOD:** + ```dart + enum E(final int i) { + a(1), b(2); + } + ``` + + ```dart + enum E { + a(1), b(2); + + E(this.i); + + final int i; + } + ``` unnecessaryConstructorName: type: lint parameters: none diff --git a/pkg/linter/test/rules/all.dart b/pkg/linter/test/rules/all.dart index c604b702ab9..99fabb432fd 100644 --- a/pkg/linter/test/rules/all.dart +++ b/pkg/linter/test/rules/all.dart @@ -265,6 +265,8 @@ import 'unnecessary_await_in_return_test.dart' as unnecessary_await_in_return; import 'unnecessary_brace_in_string_interps_test.dart' as unnecessary_brace_in_string_interps; import 'unnecessary_breaks_test.dart' as unnecessary_breaks; +import 'unnecessary_const_in_enum_constructor_test.dart' + as unnecessary_const_in_enum_constructor; import 'unnecessary_const_test.dart' as unnecessary_const; import 'unnecessary_constructor_name_test.dart' as unnecessary_constructor_name; import 'unnecessary_final_test.dart' as unnecessary_final; @@ -528,6 +530,7 @@ void main() { unnecessary_brace_in_string_interps.main(); unnecessary_breaks.main(); unnecessary_const.main(); + unnecessary_const_in_enum_constructor.main(); unnecessary_constructor_name.main(); unnecessary_final.main(); unnecessary_getters_setters.main(); diff --git a/pkg/linter/test/rules/unnecessary_const_in_enum_constructor_test.dart b/pkg/linter/test/rules/unnecessary_const_in_enum_constructor_test.dart new file mode 100644 index 00000000000..dc007d7ebfc --- /dev/null +++ b/pkg/linter/test/rules/unnecessary_const_in_enum_constructor_test.dart @@ -0,0 +1,39 @@ +// 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:test_reflective_loader/test_reflective_loader.dart'; + +import '../rule_test_support.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(UnnecessaryConstInEnumConstructorTest); + }); +} + +@reflectiveTest +class UnnecessaryConstInEnumConstructorTest extends LintRuleTest { + @override + String get lintRule => LintNames.unnecessary_const_in_enum_constructor; + + test_primary() async { + await assertDiagnosticsFromMarkdown(r''' +enum [!const!] E(final int i) { + a(1), b(2); +} +'''); + } + + test_secondary() async { + await assertDiagnosticsFromMarkdown(r''' +enum E { + a(1), b(2); + + [!const!] E(this.i); + + final int i; +} +'''); + } +}