Add new unnecessary_const_in_enum_constructor lint

This adds a new lint that was requested in order to support the primary
constructors feature.

This also enables the existing fix to remove the keyword and adds some
tests for the new use of the fix.

Change-Id: Iae3c86ca87a2af6abb82488e218d839a06699778
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496741
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson
2026-04-21 09:27:51 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 291d8ae07d
commit 78412eaeb8
11 changed files with 304 additions and 0 deletions
@@ -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:
@@ -439,6 +439,7 @@ final _builtInLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
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],
@@ -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<void> 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<void> 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<void> 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
+1
View File
@@ -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
+10
View File
@@ -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(
+3
View File
@@ -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';
+2
View File
@@ -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())
@@ -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<void> {
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);
}
}
}
+100
View File
@@ -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
+3
View File
@@ -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();
@@ -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;
}
''');
}
}