-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add no-invalid-state-props rule
- Loading branch information
Showing
3 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
'use strict' | ||
|
||
// TODO this rule supersedes no-root-ondone | ||
const getDocsUrl = require('../utils/getDocsUrl') | ||
const { propertyHasName, propertyHasValue } = require('../utils/predicates') | ||
const { allPass } = require('../utils/combinators') | ||
|
||
const validProperties = [ | ||
'after', | ||
'always', | ||
'entry', | ||
'exit', | ||
'history', // only when type=history | ||
'id', | ||
'initial', | ||
'meta', | ||
'on', | ||
'onDone', | ||
'states', | ||
'tags', | ||
'target', // only when type=history | ||
'type', | ||
] | ||
function isValidStateProperty(property) { | ||
return validProperties.includes(property.key.name) | ||
} | ||
|
||
const validRootProperties = [ | ||
'after', | ||
'context', | ||
'entry', | ||
'history', // only when type=history | ||
'id', | ||
'initial', | ||
'meta', | ||
'on', | ||
'states', | ||
'tags', | ||
'target', // only when type=history | ||
'type', | ||
] | ||
function isValidRootStateProperty(property) { | ||
return validRootProperties.includes(property.key.name) | ||
} | ||
|
||
function hasHistoryTypeProperty(node) { | ||
return ( | ||
node.type === 'ObjectExpression' && | ||
node.properties.some( | ||
allPass([propertyHasName('type'), propertyHasValue('history')]) | ||
) | ||
) | ||
} | ||
|
||
const validTypes = ['atomic', 'compound', 'parallel', 'history', 'final'] | ||
function isValidTypePropertyValue(node) { | ||
return node.type === 'Literal' && validTypes.includes(node.value) | ||
} | ||
|
||
const validHistoryTypes = ['shallow', 'deep'] | ||
function isValidHistoryPropertyValue(node) { | ||
return node.type === 'Literal' && validHistoryTypes.includes(node.value) | ||
} | ||
|
||
function validateTypePropertyValue(prop, context) { | ||
if (prop.key.name === 'type' && !isValidTypePropertyValue(prop.value)) { | ||
context.report({ | ||
node: prop, | ||
messageId: 'invalidTypeValue', | ||
data: { | ||
value: | ||
prop.value.type === 'Literal' ? prop.value.value : prop.value.type, | ||
}, | ||
}) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
function validateHistorySpecificProperty(prop, context, isHistoryNode) { | ||
if ( | ||
(prop.key.name === 'history' || prop.key.name === 'target') && | ||
!isHistoryNode | ||
) { | ||
context.report({ | ||
node: prop, | ||
messageId: 'propAllowedOnHistoryStateOnly', | ||
data: { propName: prop.key.name }, | ||
}) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
function validateHistoryPropertyValue(prop, context) { | ||
if (prop.key.name === 'history' && !isValidHistoryPropertyValue(prop.value)) { | ||
context.report({ | ||
node: prop, | ||
messageId: 'invalidHistoryValue', | ||
data: { | ||
value: | ||
prop.value.type === 'Literal' ? prop.value.value : prop.value.type, | ||
}, | ||
}) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
const stateDeclaration = | ||
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name="states"] > ObjectExpression > Property > ObjectExpression' | ||
|
||
const rootStateDeclaration = | ||
'CallExpression[callee.name=/^createMachine$|^Machine$/] > ObjectExpression:first-child' | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'forbid invalid properties in state node declarations', | ||
category: 'Possible Errors', | ||
url: getDocsUrl('no-invalid-state-props'), | ||
recommended: true, | ||
}, | ||
schema: [], | ||
messages: { | ||
invalidStateProperty: | ||
'"{{propName}}" is not a valid property for a state declaration.', | ||
invalidRootStateProperty: | ||
'"{{propName}}" is not a valid property for the root state node.', | ||
propAllowedOnHistoryStateOnly: | ||
'Property "{{propName}}" is valid only on a "history" type state node.', | ||
invalidTypeValue: | ||
'Type "{{value}}" is invalid. Use one of: "atomic", "compound", "parallel", "history", "final".', | ||
invalidHistoryValue: | ||
'The history type of "{{value}}" is invalid. Use one of: "shallow", "deep".', | ||
contextAllowedOnlyOnRootNodes: | ||
'The "context" property cannot be declared on non-root state nodes.', | ||
}, | ||
}, | ||
|
||
create: function (context) { | ||
return { | ||
[stateDeclaration]: function (node) { | ||
const isHistoryNode = hasHistoryTypeProperty(node) | ||
node.properties.forEach((prop) => { | ||
if (!validateHistorySpecificProperty(prop, context, isHistoryNode)) { | ||
return | ||
} | ||
|
||
if (prop.key.name === 'context') { | ||
context.report({ | ||
node: prop, | ||
messageId: 'contextAllowedOnlyOnRootNodes', | ||
}) | ||
return | ||
} | ||
|
||
if (!isValidStateProperty(prop)) { | ||
context.report({ | ||
node: prop, | ||
messageId: 'invalidStateProperty', | ||
data: { propName: prop.key.name }, | ||
}) | ||
return | ||
} | ||
|
||
if (!validateTypePropertyValue(prop, context)) { | ||
return | ||
} | ||
|
||
validateHistoryPropertyValue(prop, context) | ||
}) | ||
}, | ||
|
||
[rootStateDeclaration]: function (node) { | ||
const isHistoryNode = hasHistoryTypeProperty(node) | ||
node.properties.forEach((prop) => { | ||
if (!validateHistorySpecificProperty(prop, context, isHistoryNode)) { | ||
return | ||
} | ||
|
||
if (!isValidRootStateProperty(prop)) { | ||
context.report({ | ||
node: prop, | ||
messageId: 'invalidRootStateProperty', | ||
data: { propName: prop.key.name }, | ||
}) | ||
return | ||
} | ||
|
||
if (!validateTypePropertyValue(prop, context)) { | ||
return | ||
} | ||
|
||
validateHistoryPropertyValue(prop, context) | ||
}) | ||
}, | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters