Skip to content

Commit

Permalink
feat(no-auto-forward): add rule no-auto-forward
Browse files Browse the repository at this point in the history
  • Loading branch information
rlaffers committed Apr 25, 2021
1 parent 84b38c9 commit 03a58f6
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Then configure the rules you want to use under the rules section.
"xstate/prefer-always": "error",
"xstate/event-names": ["warn", "macroCase"],
"xstate/state-names": ["warn", "camelCase"],
"xstate/no-inline-implementation": "warn"
"xstate/no-inline-implementation": "warn",
"xstate/no-auto-forward": "warn"
}
}
```
Expand Down Expand Up @@ -88,6 +89,7 @@ There is also an `all` configuration which includes every available rule. It enf
| ------------------------------------------------------------------ | ----------------------------------------------------------------------- | ------------------ |
| [no-inline-implementation](docs/rules/no-inline-implementation.md) | Suggest refactoring guards, actions and services into machine options | |
| [prefer-always](docs/rules/prefer-always.md) | Suggest using the `always` syntax for transient (eventless) transitions | :heavy_check_mark: |
| [no-auto-forward](docs/rules/no-auto-forward.md) | Forbid auto-forwarding events to invoked services or spawned actors | |

### Stylistic Issues

Expand Down
60 changes: 60 additions & 0 deletions docs/rules/no-auto-forward.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Forbid auto-forwarding events

Prefer sending events explicitly to child actors/services.

## Rule Details

Avoid blindly forwarding all events to invoked services or spawned actors - it may lead to unexpected behavior or infinite loops. The official documentation [suggests sending events explicitly](https://xstate.js.org/docs/guides/communication.html#the-invoke-property) with the [`forwardTo`](https://xstate.js.org/docs/guides/actions.html#forward-to-action) or `send` action creators.

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

```javascript
// ❌ auto-forwarding events to an invoked service
createMachine({
states: {
playing: {
invoke: {
src: 'game',
autoForward: true,
},
},
},
})

// ❌ auto-forwarding events to a spawned actor
createMachine({
states: {
initializing: {
entry: assign({
gameRef: () => spawn(game, { autoForward: true }),
}),
},
},
})
```

Examples of **correct** code for this rule:

```javascript
// ✅ no auto-forward
createMachine{{
states: {
playing: {
invoke: {
src: 'game',
},
},
},
}}

// ✅ autoForward set to false
createMachine({
states: {
initializing: {
entry: assign({
gameRef: () => spawn(game, { autoForward: false }),
}),
},
},
})
```
2 changes: 2 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
'invoke-usage': require('./rules/invoke-usage'),
'entry-exit-action': require('./rules/entry-exit-action'),
'prefer-always': require('./rules/prefer-always'),
'no-auto-forward': require('./rules/no-auto-forward'),
},
configs: {
recommended: {
Expand Down Expand Up @@ -50,6 +51,7 @@ module.exports = {
'xstate/event-names': ['warn', 'macroCase'],
'xstate/state-names': ['warn', 'camelCase'],
'xstate/no-inline-implementation': 'warn',
'xstate/no-auto-forward': 'warn',
'xstate/prefer-always': 'error',
},
},
Expand Down
46 changes: 46 additions & 0 deletions lib/rules/no-auto-forward.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict'

const getDocsUrl = require('../utils/getDocsUrl')

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Forbid auto-forwarding events to child actors',
category: 'Best Practices',
url: getDocsUrl('no-auto-forward'),
recommended: 'warn',
},
schema: [],
messages: {
noAutoForward:
'Forwarding all events may lead to unexpected behavior and/or infinite loops. Prefer using `forwardTo` action creator to send events explicitly.',
},
},

create: function (context) {
return {
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name="invoke"] > ObjectExpression > Property[key.name="autoForward"]': function (
node
) {
if (node.value.value === true) {
context.report({
node,
messageId: 'noAutoForward',
})
}
},

'CallExpression[callee.name=/^createMachine$|^Machine$/] CallExpression[callee.name="spawn"] > ObjectExpression > Property[key.name="autoForward"]': function (
node
) {
if (node.value.value === true) {
context.report({
node,
messageId: 'noAutoForward',
})
}
},
}
},
}
67 changes: 67 additions & 0 deletions tests/lib/rules/no-auto-forward.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-auto-forward')

const tests = {
valid: [
`
createMachine({
states: {
playing: {
invoke: {
src: 'game',
},
},
},
})
`,
`
createMachine({
states: {
initializing: {
entry: assign({
gameRef: () => spawn(game, { autoForward: false }),
}),
},
},
})
`,
],
invalid: [
{
code: `
createMachine({
states: {
playing: {
invoke: {
src: 'game',
autoForward: true,
},
},
},
})
`,
errors: [{ messageId: 'noAutoForward' }],
},
{
code: `
createMachine({
states: {
initializing: {
entry: assign({
gameRef: () => spawn(game, { autoForward: true }),
}),
},
},
})
`,
errors: [{ messageId: 'noAutoForward' }],
},
],
}

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
},
})
ruleTester.run('no-auto-forward', rule, tests)

0 comments on commit 03a58f6

Please sign in to comment.