Skip to content

Commit 259bf24

Browse files
committed
[lint] unnecessary_underscores core implementation
(TODO: follow-up w/ improved documentation.) Bug: #56595 Change-Id: I000ccf9012c659d7fbc942d8bc0fafce87dafc5a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403201 Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 125ef43 commit 259bf24

File tree

11 files changed

+308
-1
lines changed

11 files changed

+308
-1
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,6 +2455,8 @@ LintCode.unnecessary_this:
24552455
status: hasFix
24562456
LintCode.unnecessary_to_list_in_spreads:
24572457
status: hasFix
2458+
LintCode.unnecessary_underscores:
2459+
status: needsFix
24582460
LintCode.unreachable_from_main:
24592461
status: hasFix
24602462
LintCode.unrelated_type_equality_checks_in_expression:

pkg/analyzer/tool/diagnostics/diagnostics.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29211,6 +29211,32 @@ List<String> toLowercase(List<String> strings) {
2921129211
}
2921229212
```
2921329213

29214+
### unnecessary_underscores
29215+
29216+
_Unnecessary use of multiple underscores._
29217+
29218+
#### Description
29219+
29220+
The analyzer produces this diagnostic when an unused variable is named
29221+
with mutiple underscores (for example `__`). A single `_` wildcard variable
29222+
can be used instead.
29223+
29224+
#### Example
29225+
29226+
The following code produces this diagnostic because the `__` parameter is unused:
29227+
29228+
```dart
29229+
void function(int [!__!]) { }
29230+
```
29231+
29232+
#### Common fixes
29233+
29234+
Replace the name with a single underscore:
29235+
29236+
```dart
29237+
void function(int _) { }
29238+
```
29239+
2921429240
### unrelated_type_equality_checks
2921529241

2921629242
_The type of the operand ('{0}') isn't a subtype or a supertype of the value

pkg/linter/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 3.7.0-wip
22

3+
- new lint: `unnecessary_underscores`
34
- removed lint: `package_api_docs`
45
- removed lint: `unsafe_html`
56
- _(soon to be)_ deprecated lint: `avoid_null_checks_in_equality_operators`

pkg/linter/example/all.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ linter:
205205
- unnecessary_string_interpolations
206206
- unnecessary_this
207207
- unnecessary_to_list_in_spreads
208+
- unnecessary_underscores
208209
- unreachable_from_main
209210
- unrelated_type_equality_checks
210211
- unsafe_variance

pkg/linter/lib/src/lint_codes.g.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,12 @@ class LinterLintCode extends LintCode {
17571757
hasPublishedDocs: true,
17581758
);
17591759

1760+
static const LintCode unnecessary_underscores = LinterLintCode(
1761+
LintNames.unnecessary_underscores,
1762+
"Unnecessary use of multiple underscores.",
1763+
correctionMessage: "Try using '_'.",
1764+
);
1765+
17601766
static const LintCode unreachable_from_main = LinterLintCode(
17611767
LintNames.unreachable_from_main,
17621768
"Unreachable member '{0}' in an executable library.",

pkg/linter/lib/src/lint_names.g.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,8 @@ abstract final class LintNames {
541541
static const String unnecessary_to_list_in_spreads =
542542
'unnecessary_to_list_in_spreads';
543543

544+
static const String unnecessary_underscores = 'unnecessary_underscores';
545+
544546
static const String unreachable_from_main = 'unreachable_from_main';
545547

546548
static const String unrelated_type_equality_checks =

pkg/linter/lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ import 'rules/unnecessary_string_escapes.dart';
223223
import 'rules/unnecessary_string_interpolations.dart';
224224
import 'rules/unnecessary_this.dart';
225225
import 'rules/unnecessary_to_list_in_spreads.dart';
226+
import 'rules/unnecessary_underscores.dart';
226227
import 'rules/unreachable_from_main.dart';
227228
import 'rules/unrelated_type_equality_checks.dart';
228229
import 'rules/unsafe_html.dart';
@@ -470,6 +471,7 @@ void registerLintRules() {
470471
..registerLintRule(UnnecessaryStringInterpolations())
471472
..registerLintRule(UnnecessaryThis())
472473
..registerLintRule(UnnecessaryToListInSpreads())
474+
..registerLintRule(UnnecessaryUnderscores())
473475
..registerLintRule(UnreachableFromMain())
474476
..registerLintRule(UnrelatedTypeEqualityChecks())
475477
..registerLintRule(UnsafeHtml())
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/analysis/features.dart';
6+
import 'package:analyzer/dart/ast/ast.dart';
7+
import 'package:analyzer/dart/ast/visitor.dart';
8+
import 'package:analyzer/dart/element/element2.dart';
9+
// ignore: implementation_imports
10+
import 'package:analyzer/src/utilities/extensions/collection.dart';
11+
12+
import '../analyzer.dart';
13+
import '../extensions.dart';
14+
import '../util/ascii_utils.dart';
15+
16+
const _desc = r'Unnecessary underscores can be removed.';
17+
18+
class UnnecessaryUnderscores extends LintRule {
19+
UnnecessaryUnderscores()
20+
: super(
21+
name: LintNames.unnecessary_underscores,
22+
description: _desc,
23+
);
24+
25+
@override
26+
LintCode get lintCode => LinterLintCode.unnecessary_underscores;
27+
28+
@override
29+
void registerNodeProcessors(
30+
NodeLintRegistry registry, LinterContext context) {
31+
if (!context.isEnabled(Feature.wildcard_variables)) return;
32+
var visitor = _Visitor(this);
33+
registry.addFormalParameterList(this, visitor);
34+
registry.addVariableDeclaration(this, visitor);
35+
}
36+
}
37+
38+
class _BodyVisitor extends RecursiveAstVisitor<void> {
39+
final Set<Element2> referencedElements = <Element2>{};
40+
41+
_BodyVisitor();
42+
43+
@override
44+
void visitSimpleIdentifier(SimpleIdentifier node) {
45+
referencedElements.addIfNotNull(node.element);
46+
}
47+
}
48+
49+
class _Visitor extends SimpleAstVisitor<void> {
50+
final LintRule rule;
51+
52+
_Visitor(this.rule);
53+
54+
@override
55+
void visitFormalParameterList(FormalParameterList node) {
56+
late Set<Element2> referencedElements = collectReferences(node.parent);
57+
58+
for (var parameter in node.parameters) {
59+
var element = parameter.declaredFragment?.element;
60+
var name = element?.name3;
61+
if (isJustUnderscores(name)) {
62+
if (!referencedElements.contains(element)) {
63+
rule.reportLintForToken(parameter.name);
64+
}
65+
}
66+
}
67+
}
68+
69+
@override
70+
void visitVariableDeclaration(VariableDeclaration node) {
71+
var element = node.declaredFragment?.element;
72+
if (element is FieldElement2 || element is TopLevelVariableElement2) return;
73+
74+
if (isJustUnderscores(node.name.lexeme)) {
75+
var parent = node.thisOrAncestorOfType<FunctionBody>();
76+
if (!collectReferences(parent).contains(node.declaredFragment?.element)) {
77+
rule.reportLintForToken(node.name);
78+
}
79+
}
80+
}
81+
82+
static Set<Element2> collectReferences(AstNode? node) {
83+
if (node == null) return {};
84+
var visitor = _BodyVisitor();
85+
node.accept(visitor);
86+
return visitor.referencedElements;
87+
}
88+
89+
static bool isJustUnderscores(String? name) =>
90+
name != null && name.length > 1 && name.isJustUnderscores;
91+
}

pkg/linter/messages.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13005,6 +13005,49 @@ LintCode:
1300513005
...['foo', 'bar', 'baz'].map((String s) => Text(s)),
1300613006
]
1300713007
```
13008+
unnecessary_underscores:
13009+
problemMessage: "Unnecessary use of multiple underscores."
13010+
correctionMessage: "Try using '_'."
13011+
state:
13012+
experimental: "3.7"
13013+
categories: [brevity, style]
13014+
hasPublishedDocs: false
13015+
documentation: |-
13016+
#### Description
13017+
13018+
The analyzer produces this diagnostic when an unused variable is named
13019+
with mutiple underscores (for example `__`). A single `_` wildcard variable
13020+
can be used instead.
13021+
13022+
#### Example
13023+
13024+
The following code produces this diagnostic because the `__` parameter is unused:
13025+
13026+
```dart
13027+
void function(int [!__!]) { }
13028+
```
13029+
13030+
#### Common fixes
13031+
13032+
Replace the name with a single underscore:
13033+
13034+
```dart
13035+
void function(int _) { }
13036+
```
13037+
deprecatedDetails: |-
13038+
**AVOID** using multiple underscores when a single wildcard will do.
13039+
13040+
**BAD:**
13041+
```dart
13042+
void function(int __) { }
13043+
```
13044+
13045+
**GOOD:**
13046+
```dart
13047+
void function(int _) { }
13048+
```
13049+
13050+
1300813051
unreachable_from_main:
1300913052
problemMessage: "Unreachable member '{0}' in an executable library."
1301013053
correctionMessage: "Try referencing the member or removing it."

pkg/linter/test/rules/all.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ import 'unnecessary_string_interpolations_test.dart'
286286
import 'unnecessary_this_test.dart' as unnecessary_this;
287287
import 'unnecessary_to_list_in_spreads_test.dart'
288288
as unnecessary_to_list_in_spreads;
289+
import 'unnecessary_underscores_test.dart' as unnecessary_underscores;
289290
import 'unreachable_from_main_test.dart' as unreachable_from_main;
290291
import 'unrelated_type_equality_checks_test.dart'
291292
as unrelated_type_equality_checks;
@@ -358,8 +359,8 @@ void main() {
358359
avoid_relative_lib_imports.main();
359360
avoid_renaming_method_parameters.main();
360361
avoid_return_types_on_setters.main();
361-
avoid_returning_null.main();
362362
avoid_returning_null_for_void.main();
363+
avoid_returning_null.main();
363364
avoid_returning_this.main();
364365
avoid_setters_without_getters.main();
365366
avoid_shadowing_type_parameters.main();
@@ -530,6 +531,7 @@ void main() {
530531
unnecessary_string_interpolations.main();
531532
unnecessary_this.main();
532533
unnecessary_to_list_in_spreads.main();
534+
unnecessary_underscores.main();
533535
unreachable_from_main.main();
534536
unrelated_type_equality_checks.main();
535537
unsafe_variance.main();
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:test_reflective_loader/test_reflective_loader.dart';
6+
7+
import '../rule_test_support.dart';
8+
9+
main() {
10+
defineReflectiveSuite(() {
11+
defineReflectiveTests(UnnecessaryUnderscoresTest);
12+
});
13+
}
14+
15+
@reflectiveTest
16+
class UnnecessaryUnderscoresTest extends LintRuleTest {
17+
@override
18+
String get lintRule => 'unnecessary_underscores';
19+
20+
test_field_unused() async {
21+
await assertDiagnostics(r'''
22+
class C {
23+
int __ = 0;
24+
}
25+
''', [
26+
// No lint.
27+
error(WarningCode.UNUSED_FIELD, 16, 2),
28+
]);
29+
}
30+
31+
test_forPart_unused() async {
32+
await assertDiagnostics(r'''
33+
void f() {
34+
for (var __ = 0; ; ) {}
35+
}
36+
''', [
37+
lint(22, 2),
38+
]);
39+
}
40+
41+
test_forPart_used() async {
42+
await assertNoDiagnostics(r'''
43+
void f() {
44+
for (var __ = 0; ; ++__) {}
45+
}
46+
''');
47+
}
48+
49+
test_function_parameter_unused() async {
50+
await assertDiagnostics(r'''
51+
void f(int _, int __) {}
52+
''', [
53+
lint(18, 2),
54+
]);
55+
}
56+
57+
test_function_parameter_unused_preWildcards() async {
58+
await assertNoDiagnostics(r'''
59+
// @dart = 3.6
60+
61+
void f(int _, int __) {}
62+
''');
63+
}
64+
65+
test_function_parameter_used() async {
66+
await assertNoDiagnostics(r'''
67+
void f(int _, int __) {
68+
print(__);
69+
}
70+
''');
71+
}
72+
73+
test_local_unused() async {
74+
await assertDiagnostics(r'''
75+
void f() {
76+
var __ = 0;
77+
}
78+
''', [
79+
lint(17, 2),
80+
]);
81+
}
82+
83+
test_local_used() async {
84+
await assertNoDiagnostics(r'''
85+
void f() {
86+
var __ = 0;
87+
print(__);
88+
}
89+
''');
90+
}
91+
92+
test_localFunction_unused() async {
93+
await assertDiagnostics(r'''
94+
f() {
95+
__() {}
96+
}
97+
''', [
98+
// No lint.
99+
error(WarningCode.UNUSED_ELEMENT, 8, 2),
100+
]);
101+
}
102+
103+
test_method_unused() async {
104+
await assertDiagnostics(r'''
105+
class A {
106+
__() {}
107+
}
108+
''', [
109+
// No lint.
110+
error(WarningCode.UNUSED_ELEMENT, 12, 2),
111+
]);
112+
}
113+
114+
test_topLevelFunction_unused() async {
115+
await assertDiagnostics(r'''
116+
__() {}
117+
''', [
118+
// No lint.
119+
error(WarningCode.UNUSED_ELEMENT, 0, 2),
120+
]);
121+
}
122+
123+
test_topLevelVariable_unused() async {
124+
await assertDiagnostics(r'''
125+
int __ = 0;
126+
''', [
127+
// No lint.
128+
error(WarningCode.UNUSED_ELEMENT, 4, 2),
129+
]);
130+
}
131+
}

0 commit comments

Comments
 (0)