Skip to content

Commit

Permalink
fix(entry-exit-action): recognize "guard" in entry/exit actions with …
Browse files Browse the repository at this point in the history
…xstate v5
  • Loading branch information
rlaffers committed Aug 24, 2023
1 parent 4c7c201 commit 3136344
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 24 deletions.
49 changes: 32 additions & 17 deletions lib/rules/entry-exit-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ const {
isFunctionExpression,
isCallExpression,
} = require('../utils/predicates')
const getSelectorPrefix = require('../utils/getSelectorPrefix')
const getSettings = require('../utils/getSettings')

function isObjectWithGuard(node) {
return node.type === 'ObjectExpression' && hasProperty('cond', node)
function isObjectWithGuard(node, version) {
return (
node.type === 'ObjectExpression' &&
hasProperty(version > 4 ? 'guard' : 'cond', node)
)
}

function isValidAction(node) {
Expand All @@ -21,17 +26,25 @@ function isValidAction(node) {
)
}

const entryActionDeclaration =
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]'
const entryActionDeclaration = (prefix) =>
prefix === ''
? 'Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]'
: `${prefix}Property[key.name!="states"] > ObjectExpression > Property[key.name="entry"]`

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

const exitActionDeclaration =
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]'
const exitActionDeclaration = (prefix) =>
prefix === ''
? 'Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]'
: `${prefix}Property[key.name!="states"] > ObjectExpression > Property[key.name="exit"]`

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

module.exports = {
meta: {
Expand All @@ -48,7 +61,7 @@ module.exports = {
invalidGuardedEntryAction:
'Invalid declaration of an "entry" action. Use the "choose" or "pure" action creators to specify a conditional entry action.',
invalidGuardedExitAction:
'Invalid declaration of an "entry" action. Use the "choose" or "pure" action creators to specify a conditional entry action.',
'Invalid declaration of an "exit" action. Use the "choose" or "pure" action creators to specify a conditional exit action.',
invalidEntryAction:
'The "entry" action has an invalid value. Specify a function, string, variable, action creator call, action object, or an array of those.',
invalidExitAction:
Expand All @@ -57,8 +70,10 @@ module.exports = {
},

create: function (context) {
const { version } = getSettings(context)
const prefix = getSelectorPrefix(context.sourceCode)
const validateAction = (actionType) => (node) => {
if (isObjectWithGuard(node.value)) {
if (isObjectWithGuard(node.value, version)) {
context.report({
node,
messageId:
Expand All @@ -80,7 +95,7 @@ module.exports = {

if (node.value.type === 'ArrayExpression') {
node.value.elements.forEach((element) => {
if (isObjectWithGuard(element)) {
if (isObjectWithGuard(element, version)) {
context.report({
node: element,
messageId:
Expand All @@ -101,10 +116,10 @@ module.exports = {
}
}
return {
[entryActionDeclaration]: validateAction('entry'),
[rootEntryActionDeclaration]: validateAction('entry'),
[exitActionDeclaration]: validateAction('exit'),
[rootExitActionDeclaration]: validateAction('exit'),
[entryActionDeclaration(prefix)]: validateAction('entry'),
[rootEntryActionDeclaration(prefix)]: validateAction('entry'),
[exitActionDeclaration(prefix)]: validateAction('exit'),
[rootExitActionDeclaration(prefix)]: validateAction('exit'),
}
},
}
7 changes: 7 additions & 0 deletions lib/utils/getSelectorPrefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const isXStateLintingEnforced = require('./isXStateLintingEnforced')

module.exports = function getSelectorPrefix(sourceCode) {
return isXStateLintingEnforced(sourceCode)
? ''
: 'CallExpression[callee.name=/^createMachine$|^Machine$/] '
}
3 changes: 3 additions & 0 deletions lib/utils/isXStateLintingEnforced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function isXStateLintingEnforced(sourceCode) {
return sourceCode.getAllComments().some(x => x.type === 'Block' && x.value === ' eslint-plugin-xstate-include ')
}
88 changes: 81 additions & 7 deletions tests/lib/rules/entry-exit-action.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/entry-exit-action')
const { withVersion } = require('../utils/settings')

const tests = {
valid: [
`
withVersion(
4,
`
createMachine({
entry: 'someAction',
exit: ['someAction', () => {}, assign({ foo: true }), someAction],
})
`,
`
),
withVersion(
4,
`
createMachine({
entry: choose([
{
Expand All @@ -21,10 +27,36 @@ const tests = {
},
]),
})
`,
`
),
withVersion(
5,
`
createMachine({
entry: 'someAction',
exit: ['someAction', () => {}, assign({ foo: true }), someAction],
})
`
),
withVersion(
5,
`
createMachine({
entry: choose([
{
guard: 'someGuard',
actions: 'someAction',
},
{
actions: 'defaultAction',
},
]),
})
`
),
],
invalid: [
{
withVersion(4, {
code: `
createMachine({
entry: [
Expand Down Expand Up @@ -53,8 +85,50 @@ const tests = {
{ messageId: 'invalidGuardedExitAction' },
{ messageId: 'invalidExitAction' },
],
},
{
}),
withVersion(4, {
code: `
createMachine({
entry: 123, // numbers are invalid
exit: {}, // objects without a "type" property are invalid
})
`,
errors: [
{ messageId: 'invalidEntryAction' },
{ messageId: 'invalidExitAction' },
],
}),
withVersion(5, {
code: `
createMachine({
entry: [
{
guard: 'someGuard',
actions: 'someAction',
},
{
actions: 'defaultAction',
},
],
exit: [
{
guard: 'someGuard',
actions: 'someAction',
},
{
actions: 'defaultAction',
},
],
})
`,
errors: [
{ messageId: 'invalidGuardedEntryAction' },
{ messageId: 'invalidEntryAction' },
{ messageId: 'invalidGuardedExitAction' },
{ messageId: 'invalidExitAction' },
],
}),
withVersion(5, {
code: `
createMachine({
entry: 123, // numbers are invalid
Expand All @@ -65,7 +139,7 @@ const tests = {
{ messageId: 'invalidEntryAction' },
{ messageId: 'invalidExitAction' },
],
},
}),
],
}

Expand Down

0 comments on commit 3136344

Please sign in to comment.