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

Commit cde4648

Browse files
authored
fix: support assert(mounted) for use-setstate-synchronously (#1181)
* fix: support assert(mounted) for use-setstate-synchronously * fix: handle await inside conditions
1 parent 14ffd46 commit cde4648

File tree

6 files changed

+102
-11
lines changed

6 files changed

+102
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* fix: support `assert(mounted)` for [`use-setstate-synchronously`](https://dcm.dev/docs/individuals/rules/flutter/use-setstate-synchronously).
56
* fix: correctly support dartdoc tags for [`format-comment`](https://dcm.dev/docs/individuals/rules/common/format-comment).
67

78
## 5.6.0-dev.1

lib/src/analyzers/lint_analyzer/rules/rules_list/use_setstate_synchronously/use_setstate_synchronously_rule.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import '../../../models/severity.dart';
1111
import '../../models/flutter_rule.dart';
1212
import '../../rule_utils.dart';
1313

14+
part 'config.dart';
1415
part 'fact.dart';
1516
part 'helpers.dart';
16-
part 'config.dart';
1717
part 'visitor.dart';
1818

1919
class UseSetStateSynchronouslyRule extends FlutterRule {
2020
static const ruleId = 'use-setstate-synchronously';
2121

22-
Set<String> methods;
22+
final Set<String> methods;
2323

2424
UseSetStateSynchronouslyRule([Map<String, Object> options = const {}])
2525
: methods = readMethods(options),

lib/src/analyzers/lint_analyzer/rules/rules_list/use_setstate_synchronously/visitor.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ part of 'use_setstate_synchronously_rule.dart';
22

33
class _Visitor extends RecursiveAstVisitor<void> {
44
final Set<String> methods;
5+
56
_Visitor({required this.methods});
67

78
final nodes = <SimpleIdentifier>[];
@@ -43,6 +44,13 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
4344
super.visitAwaitExpression(node);
4445
}
4546

47+
@override
48+
void visitAssertStatement(AssertStatement node) {
49+
final newMounted = _extractMountedCheck(node.condition);
50+
mounted = newMounted.or(mounted);
51+
super.visitAssertStatement(node);
52+
}
53+
4654
@override
4755
void visitMethodInvocation(MethodInvocation node) {
4856
if (!inAsync) {
@@ -65,7 +73,8 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
6573
return node.visitChildren(this);
6674
}
6775

68-
node.condition.visitChildren(this);
76+
node.condition.accept(this);
77+
6978
final newMounted = _extractMountedCheck(node.condition);
7079
mounted = newMounted.or(mounted);
7180

@@ -99,7 +108,7 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
99108
return node.visitChildren(this);
100109
}
101110

102-
node.condition.visitChildren(this);
111+
node.condition.accept(this);
103112

104113
final oldMounted = mounted;
105114
final newMounted = _extractMountedCheck(node.condition);
@@ -121,7 +130,7 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
121130
return node.visitChildren(this);
122131
}
123132

124-
node.forLoopParts.visitChildren(this);
133+
node.forLoopParts.accept(this);
125134

126135
final oldInControlFlow = inControlFlow;
127136
inControlFlow = true;
@@ -153,9 +162,8 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
153162
final oldMounted = mounted;
154163
node.body.visitChildren(this);
155164
final afterBody = mounted;
156-
// ignore: omit_local_variable_types
157-
final MountedFact beforeCatch =
158-
mounted == oldMounted ? oldMounted : false.asFact();
165+
final beforeCatch =
166+
mounted == oldMounted ? oldMounted : false.asFact<BinaryExpression>();
159167
for (final clause in node.catchClauses) {
160168
mounted = beforeCatch;
161169
clause.visitChildren(this);
@@ -176,7 +184,7 @@ class _AsyncSetStateVisitor extends RecursiveAstVisitor<void> {
176184
return node.visitChildren(this);
177185
}
178186

179-
node.expression.visitChildren(this);
187+
node.expression.accept(this);
180188

181189
final oldInControlFlow = inControlFlow;
182190
inControlFlow = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class _FooState extends State<StatefulWidget> {
2+
Widget build(context) {
3+
return FooWidget(
4+
onChange: (value) async {
5+
setState(() {});
6+
await fetchData();
7+
setState(() {}); // LINT
8+
9+
assert(mounted);
10+
setState(() {});
11+
},
12+
);
13+
}
14+
15+
void pathologicalCases() async {
16+
setState(() {});
17+
18+
await fetch();
19+
this.setState(() {}); // LINT
20+
21+
if (1 == 1) {
22+
setState(() {}); // LINT
23+
} else {
24+
assert(mounted);
25+
setState(() {});
26+
return;
27+
}
28+
setState(() {}); // LINT
29+
30+
await fetch();
31+
if (mounted && foo) {
32+
assert(mounted);
33+
} else {
34+
return;
35+
}
36+
setState(() {}); // LINT
37+
38+
assert(mounted);
39+
setState(() {});
40+
}
41+
}
42+
43+
class State {}

test/src/analyzers/lint_analyzer/rules/rules_list/use_setstate_synchronously/examples/example.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,17 @@ class _FooState extends State<StatefulWidget> {
9595
await fetch();
9696
}
9797
setState(() {}); // LINT
98+
99+
if (!mounted) return;
100+
101+
if (await condition()) {
102+
setState(() {}); // LINT
103+
}
98104
}
99105
}
100106

101107
class State {}
108+
109+
Future<void> fetch() {}
110+
111+
Future<bool> condition() {}

test/src/analyzers/lint_analyzer/rules/rules_list/use_setstate_synchronously/use_setstate_synchronously_rule_test.dart

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const _trySwitchPath =
1010
'use_setstate_synchronously/examples/extras_try_switch.dart';
1111
const _contextMountedPath =
1212
'use_setstate_synchronously/examples/context_mounted.dart';
13+
const _assertExample =
14+
'use_setstate_synchronously/examples/assert_example.dart';
1315

1416
void main() {
1517
group('UseSetStateSynchronouslyTest', () {
@@ -30,8 +32,8 @@ void main() {
3032

3133
RuleTestHelper.verifyIssues(
3234
issues: issues,
33-
startLines: [7, 24, 29, 36, 51, 66, 70, 76, 82, 92, 97],
34-
startColumns: [9, 10, 7, 7, 5, 7, 5, 5, 5, 5, 5],
35+
startLines: [7, 24, 29, 36, 51, 66, 70, 76, 82, 92, 97, 102],
36+
startColumns: [9, 10, 7, 7, 5, 7, 5, 5, 5, 5, 5, 7],
3537
locationTexts: [
3638
'setState',
3739
'setState',
@@ -44,6 +46,7 @@ void main() {
4446
'setState',
4547
'setState',
4648
'setState',
49+
'setState',
4750
],
4851
messages: [
4952
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
@@ -57,6 +60,7 @@ void main() {
5760
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
5861
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
5962
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
63+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
6064
],
6165
);
6266
});
@@ -118,6 +122,31 @@ void main() {
118122
);
119123
});
120124

125+
test('reports issues with assert statements', () async {
126+
final unit = await RuleTestHelper.resolveFromFile(_assertExample);
127+
final issues = UseSetStateSynchronouslyRule().check(unit);
128+
129+
RuleTestHelper.verifyIssues(
130+
issues: issues,
131+
startLines: [7, 19, 22, 28, 36],
132+
startColumns: [9, 10, 7, 5, 5],
133+
locationTexts: [
134+
'setState',
135+
'setState',
136+
'setState',
137+
'setState',
138+
'setState',
139+
],
140+
messages: [
141+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
142+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
143+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
144+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
145+
"Avoid calling 'setState' past an await point without checking if the widget is mounted.",
146+
],
147+
);
148+
});
149+
121150
test('reports no issues for context.mounted', () async {
122151
final unit = await RuleTestHelper.resolveFromFile(_contextMountedPath);
123152
final issues = UseSetStateSynchronouslyRule().check(unit);

0 commit comments

Comments
 (0)