Skip to content

Commit 3136344

Browse files
committed
fix(entry-exit-action): recognize "guard" in entry/exit actions with xstate v5
1 parent 4c7c201 commit 3136344

File tree

4 files changed

+123
-24
lines changed

4 files changed

+123
-24
lines changed

lib/rules/entry-exit-action.js

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ const {
77
isFunctionExpression,
88
isCallExpression,
99
} = require('../utils/predicates')
10+
const getSelectorPrefix = require('../utils/getSelectorPrefix')
11+
const getSettings = require('../utils/getSettings')
1012

11-
function isObjectWithGuard(node) {
12-
return node.type === 'ObjectExpression' && hasProperty('cond', node)
13+
function isObjectWithGuard(node, version) {
14+
return (
15+
node.type === 'ObjectExpression' &&
16+
hasProperty(version > 4 ? 'guard' : 'cond', node)
17+
)
1318
}
1419

1520
function isValidAction(node) {
@@ -21,17 +26,25 @@ function isValidAction(node) {
2126
)
2227
}
2328

24-
const entryActionDeclaration =
25-
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]'
29+
const entryActionDeclaration = (prefix) =>
30+
prefix === ''
31+
? 'Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]'
32+
: `${prefix}Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]`
2633

27-
const rootEntryActionDeclaration =
28-
'CallExpression[callee.name=/^createMachine$|^Machine$/] > ObjectExpression:nth-child(1) > Property[key.name="entry"]'
34+
const rootEntryActionDeclaration = (prefix) =>
35+
prefix === ''
36+
? 'Property[key.name="entry"]'
37+
: `${prefix}> ObjectExpression:nth-child(1) > Property[key.name="entry"]`
2938

30-
const exitActionDeclaration =
31-
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]'
39+
const exitActionDeclaration = (prefix) =>
40+
prefix === ''
41+
? 'Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]'
42+
: `${prefix}Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]`
3243

33-
const rootExitActionDeclaration =
34-
'CallExpression[callee.name=/^createMachine$|^Machine$/] > ObjectExpression:nth-child(1) > Property[key.name="exit"]'
44+
const rootExitActionDeclaration = (prefix) =>
45+
prefix === ''
46+
? 'Property[key.name="exit"]'
47+
: `${prefix}> ObjectExpression:nth-child(1) > Property[key.name="exit"]`
3548

3649
module.exports = {
3750
meta: {
@@ -48,7 +61,7 @@ module.exports = {
4861
invalidGuardedEntryAction:
4962
'Invalid declaration of an "entry" action. Use the "choose" or "pure" action creators to specify a conditional entry action.',
5063
invalidGuardedExitAction:
51-
'Invalid declaration of an "entry" action. Use the "choose" or "pure" action creators to specify a conditional entry action.',
64+
'Invalid declaration of an "exit" action. Use the "choose" or "pure" action creators to specify a conditional exit action.',
5265
invalidEntryAction:
5366
'The "entry" action has an invalid value. Specify a function, string, variable, action creator call, action object, or an array of those.',
5467
invalidExitAction:
@@ -57,8 +70,10 @@ module.exports = {
5770
},
5871

5972
create: function (context) {
73+
const { version } = getSettings(context)
74+
const prefix = getSelectorPrefix(context.sourceCode)
6075
const validateAction = (actionType) => (node) => {
61-
if (isObjectWithGuard(node.value)) {
76+
if (isObjectWithGuard(node.value, version)) {
6277
context.report({
6378
node,
6479
messageId:
@@ -80,7 +95,7 @@ module.exports = {
8095

8196
if (node.value.type === 'ArrayExpression') {
8297
node.value.elements.forEach((element) => {
83-
if (isObjectWithGuard(element)) {
98+
if (isObjectWithGuard(element, version)) {
8499
context.report({
85100
node: element,
86101
messageId:
@@ -101,10 +116,10 @@ module.exports = {
101116
}
102117
}
103118
return {
104-
[entryActionDeclaration]: validateAction('entry'),
105-
[rootEntryActionDeclaration]: validateAction('entry'),
106-
[exitActionDeclaration]: validateAction('exit'),
107-
[rootExitActionDeclaration]: validateAction('exit'),
119+
[entryActionDeclaration(prefix)]: validateAction('entry'),
120+
[rootEntryActionDeclaration(prefix)]: validateAction('entry'),
121+
[exitActionDeclaration(prefix)]: validateAction('exit'),
122+
[rootExitActionDeclaration(prefix)]: validateAction('exit'),
108123
}
109124
},
110125
}

lib/utils/getSelectorPrefix.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const isXStateLintingEnforced = require('./isXStateLintingEnforced')
2+
3+
module.exports = function getSelectorPrefix(sourceCode) {
4+
return isXStateLintingEnforced(sourceCode)
5+
? ''
6+
: 'CallExpression[callee.name=/^createMachine$|^Machine$/] '
7+
}

lib/utils/isXStateLintingEnforced.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function isXStateLintingEnforced(sourceCode) {
2+
return sourceCode.getAllComments().some(x => x.type === 'Block' && x.value === ' eslint-plugin-xstate-include ')
3+
}

tests/lib/rules/entry-exit-action.js

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
const RuleTester = require('eslint').RuleTester
22
const rule = require('../../../lib/rules/entry-exit-action')
3+
const { withVersion } = require('../utils/settings')
34

45
const tests = {
56
valid: [
6-
`
7+
withVersion(
8+
4,
9+
`
710
createMachine({
811
entry: 'someAction',
912
exit: ['someAction', () => {}, assign({ foo: true }), someAction],
1013
})
11-
`,
1214
`
15+
),
16+
withVersion(
17+
4,
18+
`
1319
createMachine({
1420
entry: choose([
1521
{
@@ -21,10 +27,36 @@ const tests = {
2127
},
2228
]),
2329
})
24-
`,
30+
`
31+
),
32+
withVersion(
33+
5,
34+
`
35+
createMachine({
36+
entry: 'someAction',
37+
exit: ['someAction', () => {}, assign({ foo: true }), someAction],
38+
})
39+
`
40+
),
41+
withVersion(
42+
5,
43+
`
44+
createMachine({
45+
entry: choose([
46+
{
47+
guard: 'someGuard',
48+
actions: 'someAction',
49+
},
50+
{
51+
actions: 'defaultAction',
52+
},
53+
]),
54+
})
55+
`
56+
),
2557
],
2658
invalid: [
27-
{
59+
withVersion(4, {
2860
code: `
2961
createMachine({
3062
entry: [
@@ -53,8 +85,50 @@ const tests = {
5385
{ messageId: 'invalidGuardedExitAction' },
5486
{ messageId: 'invalidExitAction' },
5587
],
56-
},
57-
{
88+
}),
89+
withVersion(4, {
90+
code: `
91+
createMachine({
92+
entry: 123, // numbers are invalid
93+
exit: {}, // objects without a "type" property are invalid
94+
})
95+
`,
96+
errors: [
97+
{ messageId: 'invalidEntryAction' },
98+
{ messageId: 'invalidExitAction' },
99+
],
100+
}),
101+
withVersion(5, {
102+
code: `
103+
createMachine({
104+
entry: [
105+
{
106+
guard: 'someGuard',
107+
actions: 'someAction',
108+
},
109+
{
110+
actions: 'defaultAction',
111+
},
112+
],
113+
exit: [
114+
{
115+
guard: 'someGuard',
116+
actions: 'someAction',
117+
},
118+
{
119+
actions: 'defaultAction',
120+
},
121+
],
122+
})
123+
`,
124+
errors: [
125+
{ messageId: 'invalidGuardedEntryAction' },
126+
{ messageId: 'invalidEntryAction' },
127+
{ messageId: 'invalidGuardedExitAction' },
128+
{ messageId: 'invalidExitAction' },
129+
],
130+
}),
131+
withVersion(5, {
58132
code: `
59133
createMachine({
60134
entry: 123, // numbers are invalid
@@ -65,7 +139,7 @@ const tests = {
65139
{ messageId: 'invalidEntryAction' },
66140
{ messageId: 'invalidExitAction' },
67141
],
68-
},
142+
}),
69143
],
70144
}
71145

0 commit comments

Comments
 (0)