Skip to content

Commit 2b0346d

Browse files
committed
feat(no-async-guard): support the "guard" prop with xstate v5
1 parent 409fd67 commit 2b0346d

File tree

3 files changed

+143
-28
lines changed

3 files changed

+143
-28
lines changed

docs/rules/no-async-guard.md

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
## Rule Details
66

7-
Async functions return a promise which is a truthy value. Therefore, async guard functions always pass. Transitions guarded by such functions will always be taken as if no `cond` was specified.
7+
Async functions return a promise which is a truthy value. Therefore, async guard functions always pass. Transitions guarded by such functions will always be taken as if no `cond` (XState v4) or `guard` (XState v5) was specified.
88

99
Examples of **incorrect** code for this rule:
1010

1111
```javascript
12-
// ❌ async guard in an event transition
12+
// ❌ async guard in an event transition (XState v4)
1313
createMachine({
1414
on: {
1515
EVENT: {
@@ -19,37 +19,47 @@ createMachine({
1919
},
2020
})
2121

22-
// ❌ async guard in an onDone transition
22+
// ❌ async guard in an event transition (XState v5)
23+
createMachine({
24+
on: {
25+
EVENT: {
26+
guard: async () => {},
27+
target: 'active',
28+
},
29+
},
30+
})
31+
32+
// ❌ async guard in an onDone transition (XState v5)
2333
createMachine({
2434
states: {
2535
active: {
2636
invoke: {
2737
src: 'myService',
2838
onDone: {
29-
cond: async function () {},
39+
guard: async function () {},
3040
target: 'finished',
3141
},
3242
},
3343
},
3444
},
3545
})
3646

37-
// ❌ async guard in the choose action creator
47+
// ❌ async guard in the choose action creator (XState v5)
3848
createMachine({
3949
entry: choose([
4050
{
41-
cond: async () => {},
51+
guard: async () => {},
4252
actions: 'myAction',
4353
},
4454
]),
4555
})
4656

47-
// ❌ async guards in machine options
57+
// ❌ async guards in machine options (XState v5)
4858
createMachine(
4959
{
5060
on: {
5161
EVENT: {
52-
cond: 'myGuard',
62+
guard: 'myGuard',
5363
target: 'active',
5464
},
5565
},
@@ -67,7 +77,7 @@ createMachine(
6777
Examples of **correct** code for this rule:
6878

6979
```javascript
70-
// ✅ guard is synchronous
80+
// ✅ guard is synchronous (XState v4)
7181
createMachine({
7282
on: {
7383
EVENT: {
@@ -77,27 +87,37 @@ createMachine({
7787
},
7888
})
7989

80-
// ✅ guard is synchronous
90+
// ✅ guard is synchronous (XState v5)
91+
createMachine({
92+
on: {
93+
EVENT: {
94+
guard: () => {},
95+
target: 'active',
96+
},
97+
},
98+
})
99+
100+
// ✅ guard is synchronous (XState v5)
81101
createMachine({
82102
states: {
83103
active: {
84104
invoke: {
85105
src: 'myService',
86106
onDone: {
87-
cond: function () {},
107+
guard: function () {},
88108
target: 'finished',
89109
},
90110
},
91111
},
92112
},
93113
})
94114

95-
// ✅ all guards in machine options are synchronous
115+
// ✅ all guards in machine options are synchronous (XState v5)
96116
createMachine(
97117
{
98118
on: {
99119
EVENT: {
100-
cond: 'myGuard',
120+
guard: 'myGuard',
101121
target: 'active',
102122
},
103123
},

lib/rules/no-async-guard.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const getDocsUrl = require('../utils/getDocsUrl')
44
const { isFunctionExpression } = require('../utils/predicates')
5+
const getSettings = require('../utils/getSettings')
56

67
function isAsyncFunctionExpression(node) {
78
return isFunctionExpression(node) && node.async
@@ -23,9 +24,16 @@ module.exports = {
2324
},
2425

2526
create: function (context) {
27+
const { version } = getSettings(context)
2628
return {
27-
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name="cond"]':
29+
'CallExpression[callee.name=/^createMachine$|^Machine$/] > ObjectExpression:first-child Property[key.name=/^cond|guard$/]':
2830
function (node) {
31+
if (version === 4 && node.key.name !== 'cond') {
32+
return
33+
}
34+
if (version > 4 && node.key.name !== 'guard') {
35+
return
36+
}
2937
if (isAsyncFunctionExpression(node.value)) {
3038
context.report({
3139
node: node.value,

tests/lib/rules/no-async-guards.js

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
const RuleTester = require('eslint').RuleTester
22
const rule = require('../../../lib/rules/no-async-guard')
3+
const { withVersion } = require('../utils/settings')
34

45
const tests = {
56
valid: [
6-
`
7+
withVersion(
8+
4,
9+
`
710
createMachine({
811
on: {
912
EVENT: {
@@ -12,8 +15,24 @@ const tests = {
1215
},
1316
},
1417
})
15-
`,
1618
`
19+
),
20+
withVersion(
21+
5,
22+
`
23+
createMachine({
24+
on: {
25+
EVENT: {
26+
guard: () => {},
27+
target: 'active',
28+
},
29+
},
30+
})
31+
`
32+
),
33+
withVersion(
34+
4,
35+
`
1736
createMachine({
1837
states: {
1938
active: {
@@ -27,17 +46,31 @@ const tests = {
2746
},
2847
},
2948
})
30-
`,
3149
`
32-
createMachine(
33-
{
34-
on: {
35-
EVENT: {
36-
cond: 'myGuard',
37-
target: 'active',
50+
),
51+
withVersion(
52+
5,
53+
`
54+
createMachine({
55+
states: {
56+
active: {
57+
invoke: {
58+
src: 'myService',
59+
onDone: {
60+
guard: function () {},
61+
target: 'finished',
62+
},
3863
},
3964
},
4065
},
66+
})
67+
`
68+
),
69+
withVersion(
70+
4,
71+
`
72+
createMachine(
73+
{},
4174
{
4275
guards: {
4376
myGuard: () => {},
@@ -46,10 +79,11 @@ const tests = {
4679
},
4780
}
4881
)
49-
`,
82+
`
83+
),
5084
],
5185
invalid: [
52-
{
86+
withVersion(4, {
5387
code: `
5488
createMachine({
5589
entry: choose([
@@ -82,9 +116,43 @@ const tests = {
82116
{ messageId: 'guardCannotBeAsync' },
83117
{ messageId: 'guardCannotBeAsync' },
84118
],
85-
},
119+
}),
120+
withVersion(5, {
121+
code: `
122+
createMachine({
123+
entry: choose([
124+
{
125+
guard: async () => {},
126+
actions: 'myAction',
127+
},
128+
]),
129+
states: {
130+
active: {
131+
invoke: {
132+
src: 'myService',
133+
onDone: {
134+
guard: async function () {},
135+
target: 'finished',
136+
},
137+
},
138+
},
139+
},
140+
on: {
141+
EVENT: {
142+
guard: async () => {},
143+
target: 'active',
144+
},
145+
},
146+
})
147+
`,
148+
errors: [
149+
{ messageId: 'guardCannotBeAsync' },
150+
{ messageId: 'guardCannotBeAsync' },
151+
{ messageId: 'guardCannotBeAsync' },
152+
],
153+
}),
86154
// async guard in machine options
87-
{
155+
withVersion(4, {
88156
code: `
89157
createMachine(
90158
{
@@ -109,7 +177,26 @@ const tests = {
109177
{ messageId: 'guardCannotBeAsync' },
110178
{ messageId: 'guardCannotBeAsync' },
111179
],
112-
},
180+
}),
181+
withVersion(5, {
182+
code: `
183+
createMachine(
184+
{},
185+
{
186+
guards: {
187+
myGuard: async () => {},
188+
myGuard2: async function () {},
189+
async myGuard3() {},
190+
},
191+
}
192+
)
193+
`,
194+
errors: [
195+
{ messageId: 'guardCannotBeAsync' },
196+
{ messageId: 'guardCannotBeAsync' },
197+
{ messageId: 'guardCannotBeAsync' },
198+
],
199+
}),
113200
],
114201
}
115202

0 commit comments

Comments
 (0)