Skip to content

Commit 10e1e0c

Browse files
committed
fix(no-ondone-outside-compound-state): allow the use of onDone within an array of invoke blocks
Fix a problem where `onDone` was incorrectly reported as invalid when used within an array of `invoke` blocks fix #18
1 parent a3adedf commit 10e1e0c

File tree

4 files changed

+123
-5
lines changed

4 files changed

+123
-5
lines changed

docs/rules/no-ondone-outside-compound-state.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,36 @@ createMachine({
7171
},
7272
},
7373
})
74+
75+
// ✅ onDone transition inside an invoke block
76+
createMachine({
77+
states: {
78+
active: {
79+
invoke: {
80+
src: 'fetchSomething',
81+
onDone: 'idle',
82+
},
83+
},
84+
},
85+
})
86+
87+
// ✅ onDone transition inside an array of invoke blocks
88+
createMachine({
89+
states: {
90+
active: {
91+
invoke: [
92+
{
93+
src: 'fetchSomething',
94+
onDone: 'done',
95+
},
96+
{
97+
src: 'listen',
98+
onDone: 'done',
99+
},
100+
],
101+
},
102+
},
103+
})
74104
```
75105

76106
## Further Reading

lib/rules/no-ondone-outside-compound-state.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@ const getDocsUrl = require('../utils/getDocsUrl')
44
const { getTypeProperty } = require('../utils/selectors')
55
const { hasProperty, isWithinInvoke } = require('../utils/predicates')
66

7-
function isWithinAtomicStateNode(node) {
7+
function isWithinCompoundStateNode(node) {
88
const stateNode = node.parent
99
const type = getTypeProperty(stateNode)
1010
return (
11-
!isWithinInvoke(node) &&
12-
((type != null && type.value.value === 'atomic') ||
13-
(type == null && !hasProperty('initial', stateNode)))
11+
hasProperty('initial', stateNode) ||
12+
(type != null && type.value.value === 'compound')
1413
)
1514
}
1615

16+
function isWithinParallelStateNode(node) {
17+
const stateNode = node.parent
18+
const type = getTypeProperty(stateNode)
19+
return type != null && type.value.value === 'parallel'
20+
}
21+
22+
function isWithinAtomicStateNode(node) {
23+
const stateNode = node.parent
24+
const type = getTypeProperty(stateNode)
25+
return type != null && type.value.value === 'atomic'
26+
}
27+
1728
function isWithinHistoryStateNode(node) {
1829
const stateNode = node.parent
1930
const type = getTypeProperty(stateNode)
@@ -44,13 +55,24 @@ module.exports = {
4455
'History state nodes cannot have an "onDone" transition. The "onDone" transition has effect only in compound/parallel state nodes or in service invocations.',
4556
onDoneOnFinalStateForbidden:
4657
'Final state nodes cannot have an "onDone" transition. The "onDone" transition has effect only in compound/parallel state nodes or in service invocations.',
58+
onDoneUsedIncorrectly:
59+
'The "onDone" transition cannot be used here. The "onDone" transition has effect only in compound/parallel state nodes or in service invocations.',
4760
},
4861
},
4962

5063
create: function (context) {
5164
return {
5265
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name="onDone"]':
5366
function (node) {
67+
if (isWithinInvoke(node)) {
68+
return
69+
}
70+
if (isWithinCompoundStateNode(node)) {
71+
return
72+
}
73+
if (isWithinParallelStateNode(node)) {
74+
return
75+
}
5476
if (isWithinAtomicStateNode(node)) {
5577
context.report({
5678
node,
@@ -70,7 +92,12 @@ module.exports = {
7092
node,
7193
messageId: 'onDoneOnFinalStateForbidden',
7294
})
95+
return
7396
}
97+
context.report({
98+
node,
99+
messageId: 'onDoneUsedIncorrectly',
100+
})
74101
},
75102
}
76103
},

lib/utils/predicates.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,19 @@ function isKnownActionCreatorCall(node) {
105105

106106
function isWithinInvoke(property) {
107107
const parentProp = property.parent.parent
108-
return parentProp.type === 'Property' && parentProp.key.name === 'invoke'
108+
if (
109+
parentProp &&
110+
parentProp.type === 'Property' &&
111+
parentProp.key.name === 'invoke'
112+
) {
113+
return true
114+
}
115+
return (
116+
parentProp.type === 'ArrayExpression' &&
117+
parentProp.parent &&
118+
parentProp.parent.type === 'Property' &&
119+
parentProp.parent.key.name === 'invoke'
120+
)
109121
}
110122

111123
// list of property names which have special meaning to XState in some contexts (they are

tests/lib/rules/no-ondone-outside-compound-state.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,59 @@ const tests = {
3333
},
3434
})
3535
`,
36+
`
37+
createMachine({
38+
initial: 'loading',
39+
states: {
40+
loading: {
41+
invoke: {
42+
src: 'fetchData',
43+
onDone: 'ready',
44+
},
45+
},
46+
},
47+
})
48+
`,
49+
`
50+
createMachine({
51+
initial: 'loading',
52+
states: {
53+
loading: {
54+
invoke: [{
55+
src: 'fetchData',
56+
onDone: 'ready',
57+
}],
58+
},
59+
},
60+
})
61+
`,
3662
],
3763
invalid: [
3864
{
3965
code: `
4066
createMachine({
4167
states: {
4268
active: {
69+
type: 'atomic',
4370
onDone: 'idle',
4471
},
4572
},
4673
})
4774
`,
4875
errors: [{ messageId: 'onDoneOnAtomicStateForbidden' }],
4976
},
77+
{
78+
code: `
79+
createMachine({
80+
states: {
81+
active: {
82+
onDone: 'idle',
83+
},
84+
},
85+
})
86+
`,
87+
errors: [{ messageId: 'onDoneUsedIncorrectly' }],
88+
},
5089
{
5190
code: `
5291
createMachine({
@@ -75,6 +114,16 @@ const tests = {
75114
`,
76115
errors: [{ messageId: 'onDoneOnHistoryStateForbidden' }],
77116
},
117+
{
118+
code: `
119+
createMachine({
120+
on: {
121+
onDone: 'idle',
122+
},
123+
})
124+
`,
125+
errors: [{ messageId: 'onDoneUsedIncorrectly' }],
126+
},
78127
],
79128
}
80129

0 commit comments

Comments
 (0)