Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit d547a4b

Browse files
authored
Merge branch 'master' into export-cli-runner
2 parents 0e24dd2 + 0aec13e commit d547a4b

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* feat: make CliRunner a part of public API in order to support transitive executable calls use-case.
6+
* feat: add static code diagnostic [`avoid-cascade-after-if-null`](https://dartcodemetrics.dev/docs/rules/common/avoid-cascade-after-if-null).
67

78
## 4.21.2
89

lib/src/analyzers/lint_analyzer/rules/rules_factory.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'models/rule.dart';
22
import 'rules_list/always_remove_listener/always_remove_listener_rule.dart';
33
import 'rules_list/avoid_banned_imports/avoid_banned_imports_rule.dart';
44
import 'rules_list/avoid_border_all/avoid_border_all_rule.dart';
5+
import 'rules_list/avoid_cascade_after_if_null/avoid_cascade_after_if_null_rule.dart';
56
import 'rules_list/avoid_collection_methods_with_unrelated_types/avoid_collection_methods_with_unrelated_types_rule.dart';
67
import 'rules_list/avoid_duplicate_exports/avoid_duplicate_exports_rule.dart';
78
import 'rules_list/avoid_dynamic/avoid_dynamic_rule.dart';
@@ -70,6 +71,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
7071
AlwaysRemoveListenerRule.ruleId: AlwaysRemoveListenerRule.new,
7172
AvoidBannedImportsRule.ruleId: AvoidBannedImportsRule.new,
7273
AvoidBorderAllRule.ruleId: AvoidBorderAllRule.new,
74+
AvoidCascadeAfterIfNullRule.ruleId: AvoidCascadeAfterIfNullRule.new,
7375
AvoidCollectionMethodsWithUnrelatedTypesRule.ruleId:
7476
AvoidCollectionMethodsWithUnrelatedTypesRule.new,
7577
AvoidDuplicateExportsRule.ruleId: AvoidDuplicateExportsRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
import 'package:analyzer/dart/ast/token.dart';
5+
import 'package:analyzer/dart/ast/visitor.dart';
6+
7+
import '../../../../../utils/node_utils.dart';
8+
import '../../../lint_utils.dart';
9+
import '../../../models/internal_resolved_unit_result.dart';
10+
import '../../../models/issue.dart';
11+
import '../../../models/severity.dart';
12+
import '../../models/common_rule.dart';
13+
import '../../rule_utils.dart';
14+
15+
part 'visitor.dart';
16+
17+
class AvoidCascadeAfterIfNullRule extends CommonRule {
18+
static const String ruleId = 'avoid-cascade-after-if-null';
19+
20+
static const _warning =
21+
'Avoid using cascade after ?? without precedence. It might lead to unexpected errors.';
22+
23+
AvoidCascadeAfterIfNullRule([Map<String, Object> config = const {}])
24+
: super(
25+
id: ruleId,
26+
severity: readSeverity(config, Severity.warning),
27+
excludes: readExcludes(config),
28+
);
29+
30+
@override
31+
Iterable<Issue> check(InternalResolvedUnitResult source) {
32+
final visitor = _Visitor();
33+
34+
source.unit.visitChildren(visitor);
35+
36+
return visitor.expressions
37+
.map((expression) => createIssue(
38+
rule: this,
39+
location: nodeLocation(
40+
node: expression,
41+
source: source,
42+
),
43+
message: _warning,
44+
))
45+
.toList(growable: false);
46+
}
47+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
part of 'avoid_cascade_after_if_null_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _expressions = <Expression>[];
5+
6+
Iterable<Expression> get expressions => _expressions;
7+
8+
@override
9+
void visitBinaryExpression(BinaryExpression node) {
10+
super.visitBinaryExpression(node);
11+
12+
if (node.operator.type != TokenType.QUESTION_QUESTION) {
13+
return;
14+
}
15+
16+
final parent = node.parent;
17+
if (parent is CascadeExpression) {
18+
_expressions.add(parent);
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_cascade_after_if_null/avoid_cascade_after_if_null_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_cascade_after_if_null/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidCascadeAfterIfNullRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidCascadeAfterIfNullRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-cascade-after-if-null',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidCascadeAfterIfNullRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [9, 16],
29+
startColumns: [16, 15],
30+
locationTexts: [
31+
'cow ?? Cow()\n'
32+
' ..moo()',
33+
'nullableCow ?? Cow()\n'
34+
' ..moo()',
35+
],
36+
messages: [
37+
'Avoid using cascade after ?? without precedence. It might lead to unexpected errors.',
38+
'Avoid using cascade after ?? without precedence. It might lead to unexpected errors.',
39+
],
40+
);
41+
});
42+
});
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class Cow {
2+
void moo() {}
3+
}
4+
5+
class Ranch {
6+
final Cow? _cow;
7+
8+
Ranch([Cow? cow])
9+
: _cow = cow ?? Cow()
10+
..moo(); // LINT
11+
}
12+
13+
void main() {
14+
final Cow? nullableCow;
15+
16+
final cow = nullableCow ?? Cow()
17+
..moo(); // LINT
18+
final cow = (nullableCow ?? Cow())..moo();
19+
final cow = nullableCow ?? (Cow()..moo());
20+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import RuleDetails from '@site/src/components/RuleDetails';
2+
3+
<RuleDetails version="5.0.0" severity="warning" />
4+
5+
Warns when a cascade expression is used after if null (??) binary expression
6+
without parentheses.
7+
8+
Not adding parentheses might lead to unexpected results since cascade will be executed **after** if null expression.
9+
10+
Additional resources:
11+
12+
- <https://github.com/dart-lang/sdk/issues/45667>
13+
14+
### Example {#example}
15+
16+
**❌ Bad:**
17+
18+
```dart
19+
class Cow {
20+
void moo() {}
21+
}
22+
23+
class Ranch {
24+
final Cow? _cow;
25+
26+
Ranch([Cow? cow])
27+
: _cow = cow ?? Cow()
28+
..moo(); // LINT
29+
}
30+
31+
void main() {
32+
final Cow? nullableCow;
33+
34+
final cow = nullableCow ?? Cow()
35+
..moo(); // LINT
36+
}
37+
```
38+
39+
**✅ Good:**
40+
41+
```dart
42+
void main() {
43+
final Cow? nullableCow;
44+
45+
final cow = (nullableCow ?? Cow())..moo();
46+
final cow = nullableCow ?? (Cow()..moo());
47+
}
48+
```

website/docs/rules/index.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
2626
Configure some imports that you want to ban.
2727
</RuleEntry>
2828

29+
<RuleEntry
30+
name="avoid-cascade-after-if-null"
31+
type="common"
32+
severity="warning"
33+
version="5.0.0"
34+
>
35+
Warns when a cascade expression is used after if null (??) binary expression
36+
without parentheses.
37+
</RuleEntry>
38+
2939
<RuleEntry
3040
name="avoid-collection-methods-with-unrelated-types"
3141
type="common"

0 commit comments

Comments
 (0)