From 9f47e3186d8c2d4bbe665828ff2c157a5dd4db0e Mon Sep 17 00:00:00 2001 From: Brian Wilkerson Date: Tue, 9 Jun 2026 11:39:04 -0700 Subject: [PATCH] Add an unnecessary_primary_constructor_body lint The lint will fire even when there is a comment in the body, such as ```dart class C() { this { /* comment */ } } ``` I think this is the right behavior because I can't think of any useful comment that wouldn't be better somewhere else, but let me know if you disagree. This doesn't yet have a fix, but it should. Change-Id: Ia5ffa6b06c75a6a7400ec32a9d22209c08a9c73d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510401 Reviewed-by: Keerti Parthasarathy Commit-Queue: Brian Wilkerson --- .../services/correction/error_fix_status.yaml | 4 ++ pkg/linter/lib/src/diagnostic.g.dart | 10 +++ pkg/linter/lib/src/lint_names.g.dart | 3 + pkg/linter/lib/src/rules.dart | 2 + .../unnecessary_primary_constructor_body.dart | 57 +++++++++++++++ pkg/linter/messages.yaml | 23 ++++++ pkg/linter/test/rules/all.dart | 3 + ...cessary_primary_constructor_body_test.dart | 71 +++++++++++++++++++ 8 files changed, 173 insertions(+) create mode 100644 pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart create mode 100644 pkg/linter/test/rules/unnecessary_primary_constructor_body_test.dart 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 181aaea2749..5eddcd24b97 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 @@ -2583,6 +2583,10 @@ unnecessary_overrides: status: hasFix unnecessary_parenthesis: status: hasFix +unnecessary_primary_constructor_body: + status: needsFix + notes: |- + The fix is to remove the body. unnecessary_raw_strings: status: hasFix unnecessary_statements: diff --git a/pkg/linter/lib/src/diagnostic.g.dart b/pkg/linter/lib/src/diagnostic.g.dart index 0d93560744b..e40ac46f228 100644 --- a/pkg/linter/lib/src/diagnostic.g.dart +++ b/pkg/linter/lib/src/diagnostic.g.dart @@ -3422,6 +3422,16 @@ const LinterLintWithoutArguments unnecessaryParenthesis = expectedTypes: [], ); +/// No parameters. +const LinterLintWithoutArguments unnecessaryPrimaryConstructorBody = + LinterLintWithoutArguments( + name: 'unnecessary_primary_constructor_body', + problemMessage: "Unnecessary primary constructor body.", + correctionMessage: "Try removing the body.", + uniqueName: 'unnecessary_primary_constructor_body', + expectedTypes: [], + ); + /// No parameters. const LinterLintWithoutArguments unnecessaryRawStrings = LinterLintWithoutArguments( diff --git a/pkg/linter/lib/src/lint_names.g.dart b/pkg/linter/lib/src/lint_names.g.dart index 0020b3e276f..f3684cee2f6 100644 --- a/pkg/linter/lib/src/lint_names.g.dart +++ b/pkg/linter/lib/src/lint_names.g.dart @@ -582,6 +582,9 @@ abstract final class LintNames { static const String unnecessary_parenthesis = 'unnecessary_parenthesis'; + static const String unnecessary_primary_constructor_body = + 'unnecessary_primary_constructor_body'; + static const String unnecessary_raw_strings = 'unnecessary_raw_strings'; static const String unnecessary_statements = 'unnecessary_statements'; diff --git a/pkg/linter/lib/src/rules.dart b/pkg/linter/lib/src/rules.dart index 44baad7e547..d1528916e52 100644 --- a/pkg/linter/lib/src/rules.dart +++ b/pkg/linter/lib/src/rules.dart @@ -230,6 +230,7 @@ import 'rules/unnecessary_null_in_if_null_operators.dart'; import 'rules/unnecessary_nullable_for_final_variable_declarations.dart'; import 'rules/unnecessary_overrides.dart'; import 'rules/unnecessary_parenthesis.dart'; +import 'rules/unnecessary_primary_constructor_body.dart'; import 'rules/unnecessary_raw_strings.dart'; import 'rules/unnecessary_statements.dart'; import 'rules/unnecessary_string_escapes.dart'; @@ -497,6 +498,7 @@ void registerLintRules() { ..registerLintRule(UnnecessaryNullableForFinalVariableDeclarations()) ..registerLintRule(UnnecessaryOverrides()) ..registerLintRule(UnnecessaryParenthesis()) + ..registerLintRule(UnnecessaryPrimaryConstructorBody()) ..registerLintRule(UnnecessaryRawStrings()) ..registerLintRule(UnnecessaryStatements()) ..registerLintRule(UnnecessaryStringEscapes()) diff --git a/pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart b/pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart new file mode 100644 index 00000000000..8fafacdc93c --- /dev/null +++ b/pkg/linter/lib/src/rules/unnecessary_primary_constructor_body.dart @@ -0,0 +1,57 @@ +// 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/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/type_system.dart'; +import 'package:analyzer/error/error.dart'; + +import '../analyzer.dart'; +import '../diagnostic.dart' as diag; + +const _desc = r'Unnecessary primary constructor bodies can be removed.'; + +class UnnecessaryPrimaryConstructorBody extends AnalysisRule { + new() + : super( + name: LintNames.unnecessary_primary_constructor_body, + description: _desc, + ); + + @override + DiagnosticCode get diagnosticCode => diag.unnecessaryPrimaryConstructorBody; + + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + var visitor = _Visitor(this, context.typeSystem); + registry.addPrimaryConstructorBody(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final AnalysisRule rule; + final TypeSystem typeSystem; + + new(this.rule, this.typeSystem); + + @override + void visitPrimaryConstructorBody(PrimaryConstructorBody node) { + var body = node.body; + if (node.metadata.isNotEmpty) return; + if (node.documentationComment != null) return; + if (node.initializers.isNotEmpty) return; + + if (body is EmptyFunctionBody) { + rule.reportAtToken(node.thisKeyword); + } else if (body is BlockFunctionBody && body.block.statements.isEmpty) { + rule.reportAtToken(node.thisKeyword); + } + } +} diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml index 8b915b3273d..b9aec420330 100644 --- a/pkg/linter/messages.yaml +++ b/pkg/linter/messages.yaml @@ -15380,6 +15380,29 @@ LinterLintCode: * logical expressions - parentheses can improve the readability of the implicit grouping defined by precedence. For example, the expression `(a && b) || c && d`. + unnecessaryPrimaryConstructorBody: + type: lint + parameters: none + problemMessage: "Unnecessary primary constructor body." + correctionMessage: "Try removing the body." + state: + experimental: "3.13" + categories: [brevity, style] + hasPublishedDocs: false + deprecatedDetails: |- + Don't include an empty primary constructor body. + + **BAD:** + ```dart + class C() { + this; + } + ``` + + **GOOD:** + ```dart + class C(); + ``` unnecessaryRawStrings: type: lint parameters: none diff --git a/pkg/linter/test/rules/all.dart b/pkg/linter/test/rules/all.dart index 20b83214206..44eaaa4b216 100644 --- a/pkg/linter/test/rules/all.dart +++ b/pkg/linter/test/rules/all.dart @@ -295,6 +295,8 @@ import 'unnecessary_nullable_for_final_variable_declarations_test.dart' as unnecessary_nullable_for_final_variable_declarations; import 'unnecessary_overrides_test.dart' as unnecessary_overrides; import 'unnecessary_parenthesis_test.dart' as unnecessary_parenthesis; +import 'unnecessary_primary_constructor_body_test.dart' + as unnecessary_primary_constructor_body; import 'unnecessary_raw_strings_test.dart' as unnecessary_raw_strings; import 'unnecessary_statements_test.dart' as unnecessary_statements; import 'unnecessary_string_escapes_test.dart' as unnecessary_string_escapes; @@ -562,6 +564,7 @@ void main() { unnecessary_nullable_for_final_variable_declarations.main(); unnecessary_overrides.main(); unnecessary_parenthesis.main(); + unnecessary_primary_constructor_body.main(); unnecessary_raw_strings.main(); unnecessary_statements.main(); unnecessary_string_escapes.main(); diff --git a/pkg/linter/test/rules/unnecessary_primary_constructor_body_test.dart b/pkg/linter/test/rules/unnecessary_primary_constructor_body_test.dart new file mode 100644 index 00000000000..cfb5d7a4e13 --- /dev/null +++ b/pkg/linter/test/rules/unnecessary_primary_constructor_body_test.dart @@ -0,0 +1,71 @@ +// 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(UnnecessaryPrimaryConstructorBodyTest); + }); +} + +@reflectiveTest +class UnnecessaryPrimaryConstructorBodyTest extends LintRuleTest { + @override + String get lintRule => LintNames.unnecessary_primary_constructor_body; + + test_emptyBody_block() async { + await assertDiagnosticsFromMarkdown(r''' +class C() { + [!this!] {} +} +'''); + } + + test_emptyBody_semicolon() async { + await assertDiagnosticsFromMarkdown(r''' +class C() { + [!this!]; +} +'''); + } + + test_hasDocComment() async { + await assertNoDiagnostics(r''' +class C() { + /// comment + this; +} +'''); + } + + test_hasInitializer() async { + await assertNoDiagnostics(r''' +class C(int i) { + this : assert(i >= 0); +} +'''); + } + + test_hasMetadata() async { + await assertNoDiagnostics(r''' +class C() { + @deprecated + this; +} +'''); + } + + test_hasNonEmptyBody() async { + await assertNoDiagnostics(r''' +class C(int i) { + this { + print(i); + } +} +'''); + } +}