create `eslint` config and plugin for v5
I've been thinking about how cool it would be to provide tooling to assist with (eventual) user migration to v5. One of the things that could be quite helpful would be an eslint plugin and config defining rules that help the user work through all of the breaking changes or utilize new functionality.
Here's what I think those rules might look like, using the [email protected] release notes as a starting point.
| rule | description |
|---|---|
avoid-context-spread |
Since machine.withContext now permits partial context, no need to spread machine.context |
no-atomic-internal |
internal property no longer has an effect on atomic state nodes |
no-cond |
cond has been renamed to guard |
no-deprecated-config-properties |
onEntry, onExit, parallel, and forward have been removed |
no-eventless-on-transition |
Eventless transitions must now be specified using always instead of on['']. |
no-emitted-from |
EmittedFrom has been renamed to SnapshotFrom |
no-factory-context-arg |
The 3rd arg (context) has been removed from and createMachine() |
no-generic-state-schema |
StateSchema has been removed from all generics |
no-guard-in |
The in property for transitions has been removed and replaced with guards. Prefer stateIn() or not(StateIn()) |
no-machine-factory |
Machine factory has been removed; use createMachine instead |
no-machine-transition-context-arg |
3rd arg (context) to machine.transition has been removed. Can use State.from('state', {}) as 1st argument instead. |
no-service-batch |
service.batch(events) has been removed |
no-service-children |
children can only be accessed from state.children |
no-service-execute |
service.execute has been removed |
no-service-onChange |
service.onChange has been removed in favor of service.onTransition or service.subscribe |
no-service-onEvent |
service.onEvent has been removed in favor of service.onTransition or service.subscribe |
no-service-onSend |
service.onSend has been removed in favor of service.onTransition or service.subscribe |
no-service-send-type-payload |
service.send(type, payload) is no longer supported. use service.send({ type, …payload }) instead |
no-spawn-import |
spawn is now available in 3rd arg to assign |
no-state-activities |
state.activites has been removed |
no-state-children-direct-reference |
state.children is now a mapping of invoked actor IDs to their ActorRef and should never be referenced directly. Not really sure about the name or what this rule would even do, but wanted to make a note of it |
no-state-events |
state.events has been removed |
no-state-history |
state.history has been removed |
no-state-historyValue |
state.historyValue is considered internal |
no-state-node-isTransient |
stateNode.isTransient has been removed |
no-state-node-version |
stateNode.version has been removed. version is only available on root machine node |
prefer-spawn-implementation-name |
Prefer referencing actors defined in config.services by name (spawn('promiseActor') instead of spawn(promiseActor)) |
prefer-wildcard-event-descriptors |
Detects multiple namespaced actions with the same config object that could be combined (?) |
rename-machine-withConfig-to-provide |
machine.withConfig(…) -> machine.provide(…) |
require-compound-state-initial-key |
Compound states now require an initial key (did they not already?) |
require-object-context |
machine.context is now required to be an object |
require-parameterized-actions-params |
Parameterized actions now require a params property ({ message: 'Hello' } -> { params: { message: 'Hello' }}) |
require-parameterized-guards-params |
Guard parameters should now be placed in object at params key ({ minQueryLength: 3 } -> { params: { minQueryLength: 3 }}) |
This is an excellent list, thanks for making it! I think it's a good idea, and an eslint plugin for XState is generally a good idea as well.
Thanks for the encouragement @davidkpiano! Already have a few rules working 😁
I need to think about how this would behave but after talking to @Andarist earlier, we likely need a rule to encourage static configs. Bidirectional editing between code and Studio requires semi-static configs, so the more we can encourage static configs, the better things will be.
Basically we want users to avoid evaluating/computing things in their machine definitions:
const IDLE = 'idle'
const a = 'action'
const b = 1
const machine = createMachine(
{
// no vars as values
initial: IDLE,
// no `TemplateLiteral`s that require evaluation
entry: `${a}${b}`,
states: {
// no computed properties
[IDLE]: {}
}
},
{
actions: {
action1: () => {},
}
}
)
I need to think about how this would behave but after talking to @Andarist earlier, we likely need a rule to encourage static configs. Bidirectional editing between code and Studio requires semi-static configs, so the more we can encourage static configs, the better things will be.
Basically we want users to avoid evaluating/computing things in their machine definitions:
const IDLE = 'idle' const a = 'action' const b = 1 const machine = createMachine( { // no vars as values initial: IDLE, // no `TemplateLiteral`s that require evaluation entry: `${a}${b}`, states: { // no computed properties [IDLE]: {} } }, { actions: { action1: () => {}, } } )
In the future, I feel like it should be possible to even have some of these work with bidirectional editing. If we treat the AST like a directed graph, then it's a matter of taking one extra "step" to find the source of the value, and modifying it there (as long as it's not shared anywhere non-machine-related).
Computed properties are not uncommon, so I'm wondering if we can't at least support that.
as long as it's not shared anywhere non-machine-related
Can you clarify what that means?
If we treat the AST like a directed graph, then it's a matter of taking one extra "step" to find the source of the value,
I agree that it's possible to resolve (and even modify) a lot of pattern.
(as long as it's not shared anywhere non-machine-related).
This is a major blocker though - and how do you make this intuitive so people would know where the line between supported patterns and unsupported ones is?