Skip to content

Commit 4b9ed5e

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[lint] Add lint for using null-aware elements
The lint suggests rewriting explicit null-checks, such as: * [if (x != null) x] * {if (x case var key?) key: "value"} The suggested rewrite is to use the null-aware marker '?': * [?x] * {?x: "value"} Change-Id: If37d096755fee14b3d9dee0f730d3302176ad0f4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411685 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 1d4ebf7 commit 4b9ed5e

File tree

11 files changed

+321
-2
lines changed

11 files changed

+321
-2
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
@@ -2512,6 +2512,8 @@ LintCode.use_late_for_private_fields_and_variables:
25122512
status: needsFix
25132513
LintCode.use_named_constants:
25142514
status: hasFix
2515+
LintCode.use_null_aware_elements:
2516+
status: needsFix
25152517
LintCode.use_raw_strings:
25162518
status: hasFix
25172519
LintCode.use_rethrow_when_possible:

pkg/analyzer/test/verify_diagnostics_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,8 @@ class _SnippetTest extends PubPackageResolutionTest {
503503
void _createAnalysisOptionsFile() {
504504
var lintCode = snippet.lintCode;
505505
if (lintCode != null) {
506-
writeTestPackageAnalysisOptionsFile(
507-
analysisOptionsContent(rules: [lintCode]));
506+
writeTestPackageAnalysisOptionsFile(analysisOptionsContent(
507+
rules: [lintCode], experiments: snippet.experiments));
508508
}
509509
}
510510

pkg/linter/CHANGELOG.md

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

3+
- new lint: `use_null_aware_elements`
34
- new _(experimental)_ lint: `unnecessary_ignore`
45

56
# 3.7.0

pkg/linter/example/all.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ linter:
222222
- use_key_in_widget_constructors
223223
- use_late_for_private_fields_and_variables
224224
- use_named_constants
225+
- use_null_aware_elements
225226
- use_raw_strings
226227
- use_rethrow_when_possible
227228
- use_setters_to_change_properties

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,12 @@ class LinterLintCode extends LintCode {
19341934
hasPublishedDocs: true,
19351935
);
19361936

1937+
static const LintCode use_null_aware_elements = LinterLintCode(
1938+
LintNames.use_null_aware_elements,
1939+
"Use the null-aware marker '?' rather than a null check via an 'if'.",
1940+
correctionMessage: "Try using '?'.",
1941+
);
1942+
19371943
static const LintCode use_raw_strings = LinterLintCode(
19381944
LintNames.use_raw_strings,
19391945
"Use a raw string to avoid using escapes.",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ abstract final class LintNames {
585585

586586
static const String use_named_constants = 'use_named_constants';
587587

588+
static const String use_null_aware_elements = 'use_null_aware_elements';
589+
588590
static const String use_raw_strings = 'use_raw_strings';
589591

590592
static const String use_rethrow_when_possible = 'use_rethrow_when_possible';

pkg/linter/lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ import 'rules/use_is_even_rather_than_modulo.dart';
242242
import 'rules/use_key_in_widget_constructors.dart';
243243
import 'rules/use_late_for_private_fields_and_variables.dart';
244244
import 'rules/use_named_constants.dart';
245+
import 'rules/use_null_aware_elements.dart';
245246
import 'rules/use_raw_strings.dart';
246247
import 'rules/use_rethrow_when_possible.dart';
247248
import 'rules/use_setters_to_change_properties.dart';
@@ -493,6 +494,7 @@ void registerLintRules() {
493494
..registerLintRule(UseKeyInWidgetConstructors())
494495
..registerLintRule(UseLateForPrivateFieldsAndVariables())
495496
..registerLintRule(UseNamedConstants())
497+
..registerLintRule(UseNullAwareElements())
496498
..registerLintRule(UseRawStrings())
497499
..registerLintRule(UseRethrowWhenPossible())
498500
..registerLintRule(UseSettersToChangeProperties())
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/dart/element/element.dart';
11+
12+
import '../analyzer.dart';
13+
import '../extensions.dart';
14+
15+
const _desc =
16+
r'If-elements testing for null can be replaced with null-aware elements.';
17+
18+
class UseNullAwareElements extends LintRule {
19+
UseNullAwareElements()
20+
: super(name: LintNames.use_null_aware_elements, description: _desc);
21+
22+
@override
23+
LintCode get lintCode => LinterLintCode.use_null_aware_elements;
24+
25+
@override
26+
void registerNodeProcessors(
27+
NodeLintRegistry registry,
28+
LinterContext context,
29+
) {
30+
if (!context.isEnabled(Feature.null_aware_elements)) return;
31+
var visitor = _Visitor(this);
32+
registry.addIfElement(this, visitor);
33+
}
34+
}
35+
36+
class _Visitor extends SimpleAstVisitor<void> {
37+
final LintRule rule;
38+
39+
_Visitor(this.rule);
40+
41+
@override
42+
void visitIfElement(IfElement node) {
43+
if (node case IfElement(:var thenElement, elseKeyword: null)) {
44+
Element2? nullCheckTarget;
45+
if (node.expression case BinaryExpression(
46+
:var operator,
47+
:var leftOperand,
48+
:var rightOperand,
49+
) when operator.isOperator && operator.lexeme == '!=') {
50+
// Case of non-pattern null checks of the form `if (x != null) x`.
51+
if (leftOperand is NullLiteral) {
52+
// Cases of the form `if (null != x) x`.
53+
nullCheckTarget = rightOperand.canonicalElement;
54+
} else if (rightOperand is NullLiteral) {
55+
// Cases of the form `if (x != null) x`.
56+
nullCheckTarget = leftOperand.canonicalElement;
57+
}
58+
} else if (node.caseClause?.guardedPattern.pattern case NullCheckPattern(
59+
pattern: DeclaredVariablePattern(:var declaredElement2),
60+
)) {
61+
// Case of pattern null checks of the form `if (x case var y?) y`.
62+
nullCheckTarget = declaredElement2;
63+
}
64+
65+
if (nullCheckTarget is PromotableElementImpl2) {
66+
if (thenElement is SimpleIdentifier &&
67+
nullCheckTarget == thenElement.canonicalElement) {
68+
// List and set elements, such as the following:
69+
//
70+
// [if (x != null) x]
71+
// {if (x != null) x}
72+
rule.reportLintForToken(node.ifKeyword);
73+
} else if (thenElement case MapLiteralEntry(:var key, :var value)) {
74+
if (key is SimpleIdentifier &&
75+
nullCheckTarget == key.canonicalElement) {
76+
// Map keys, such as the following:
77+
//
78+
// {if (x != null) x: value}
79+
rule.reportLintForToken(node.ifKeyword);
80+
} else if (value is SimpleIdentifier &&
81+
nullCheckTarget == value.canonicalElement) {
82+
// Map keys, such as the following:
83+
//
84+
// {if (x != null) key: x}
85+
rule.reportLintForToken(node.ifKeyword);
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}

pkg/linter/messages.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14220,6 +14220,50 @@ LintCode:
1422014220
```dart
1422114221
Duration.zero;
1422214222
```
14223+
use_null_aware_elements:
14224+
problemMessage: "Use the null-aware marker '?' rather than a null check via an 'if'."
14225+
correctionMessage: "Try using '?'."
14226+
state:
14227+
stable: "3.8"
14228+
categories: [brevity, style]
14229+
hasPublishedDocs: false
14230+
documentation: |-
14231+
#### Description
14232+
14233+
The analyzer produces this diagnostic when a null check is used instead
14234+
of a null-aware marker inside of a collection literal.
14235+
14236+
#### Example
14237+
14238+
The following code produces this diagnostic because a null check is used
14239+
to decide whether `x` should be inserted into the list, while the
14240+
null-aware marker '?' would be less brittle and less verbose.
14241+
14242+
```dart
14243+
%experiments=null-aware-elements
14244+
f(int? x) => [[!if!] (x != null) x];
14245+
```
14246+
14247+
#### Common fixes
14248+
14249+
Replace the null-check with the null-aware marker '?':
14250+
14251+
```dart
14252+
%experiments=null-aware-elements
14253+
f(int? x) => [?x];
14254+
```
14255+
deprecatedDetails: |-
14256+
Where possible, use null-aware elements in collection literals.
14257+
14258+
**BAD:**
14259+
```dart
14260+
f(String? key) => {if (key != null) key: "value"};
14261+
```
14262+
14263+
**GOOD:**
14264+
```dart
14265+
f(String? key) => {?key: "value"};
14266+
```
1422314267
use_raw_strings:
1422414268
problemMessage: "Use a raw string to avoid using escapes."
1422514269
correctionMessage: "Try making the string a raw string and removing the escapes."

pkg/linter/test/rules/all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ import 'use_key_in_widget_constructors_test.dart'
312312
import 'use_late_for_private_fields_and_variables_test.dart'
313313
as use_late_for_private_fields_and_variables;
314314
import 'use_named_constants_test.dart' as use_named_constants;
315+
import 'use_null_aware_elements_test.dart' as use_null_aware_elements;
315316
import 'use_raw_strings_test.dart' as use_raw_strings;
316317
import 'use_rethrow_when_possible_test.dart' as use_rethrow_when_possible;
317318
import 'use_setters_to_change_properties_test.dart'
@@ -552,6 +553,7 @@ void main() {
552553
use_key_in_widget_constructors.main();
553554
use_late_for_private_fields_and_variables.main();
554555
use_named_constants.main();
556+
use_null_aware_elements.main();
555557
use_raw_strings.main();
556558
use_rethrow_when_possible.main();
557559
use_setters_to_change_properties.main();

0 commit comments

Comments
 (0)