Skip to content

Commit

Permalink
feat(event-names): add regex option
Browse files Browse the repository at this point in the history
  • Loading branch information
rlaffers committed Apr 24, 2021
1 parent 4b1c715 commit 84b38c9
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 6 deletions.
16 changes: 11 additions & 5 deletions docs/rules/event-names.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Suggest consistent formatting of event names

Suggest using event names formatted with the preconfigured style (MACRO_CASE, snake_case or camelCase).
Suggest using event names formatted with the preconfigured style (MACRO_CASE, snake_case, camelCase or regex).

# Rule Details

Expand All @@ -9,8 +9,9 @@ While the XState library neither enforces nor recommends any particular format f
- MACRO_CASE (a.k.a. SCREAMING_SNAKE_CASE) [*default*]
- snake_case
- camelCase
- regular expression

The default MACRO_CASE for event names is an _unofficial_ convention, typically used within the XState community.
The default MACRO*CASE for event names is an \_unofficial* convention, typically used within the XState community.

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

Expand Down Expand Up @@ -155,14 +156,19 @@ const obj = {

## Options

| Option | Required | Default | Details |
| -------- | -------- | ----------- | ------------------------------------------------------------------------------------- |
| [string] | No | `macroCase` | Selects one of the available formatting styles: `macroCase`, `snakeCase`, `camelCase` |
| Option | Required | Default | Details |
| -------- | -------- | ----------- | ---------------------------------------------------------------------------------------------- |
| [string] | No | `macroCase` | Selects one of the available formatting styles: `macroCase`, `snakeCase`, `camelCase`, `regex` |
| [object] | No | `undefined` | The second option is an object with properties: `regex` (string) |

## Example

```json
{
"xstate/event-names": ["warn", "camelCase"]
}

{
"xstate/state-names": ["warn", "regex", { "regex": "^\\w+:\\w+$" }]
}
```
57 changes: 56 additions & 1 deletion lib/rules/event-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ function containsWildcardOrDot(name) {
const selectorSendEvent =
'CallExpression[callee.name=/^createMachine$|^Machine$/] CallExpression[callee.name=/^send$|^sendParent$|^respond$|^raise$/]'

/**
* Default regular expression for the regex option.
* @type {string}
*/
const defaultRegex = '^[a-z]*$'

module.exports = {
meta: {
type: 'suggestion',
Expand All @@ -74,19 +80,38 @@ module.exports = {
fixable: 'code',
schema: [
{
enum: ['macroCase', 'camelCase', 'snakeCase'],
enum: ['macroCase', 'camelCase', 'snakeCase', 'regex'],
},
{
type: 'object',
properties: {
regex: {
type: 'string',
format: 'regex',
default: defaultRegex,
},
},
additionalProperties: false,
},
],
messages: {
invalidEventName:
'Prefer "{{fixedEventName}}" over "{{eventName}}" event name',
invalidSendEventName:
'Wildcards in event names cannot be used when sending events.',
eventNameViolatesRegex:
'Event name "{{eventName}}" violates regular expression /{{regex}}/',
},
},

create: function (context) {
const mode = context.options[0] || 'macroCase'
const regexOption =
mode === 'regex'
? (context.options[1] && context.options[1].regex) || defaultRegex
: null
const regex = regexOption !== null ? new RegExp(regexOption) : null

return {
'CallExpression[callee.name=/^createMachine$|^Machine$/] Property[key.name="on"] > ObjectExpression > Property': function (
node
Expand All @@ -97,6 +122,16 @@ module.exports = {
}
const eventName =
node.key.type === 'Identifier' ? node.key.name : node.key.value

if (regex && !regex.test(eventName)) {
context.report({
node,
data: { eventName, regex: regexOption },
messageId: 'eventNameViolatesRegex',
})
return
}

const fixedEventName = fixEventName(eventName, mode)
// quotes are needed only if the event name contains "." or "*"
const quote = containsWildcardOrDot(eventName) ? "'" : ''
Expand Down Expand Up @@ -129,6 +164,16 @@ module.exports = {
})
return
}

if (regex && !regex.test(eventName)) {
context.report({
node,
data: { eventName, regex: regexOption },
messageId: 'eventNameViolatesRegex',
})
return
}

const fixedEventName = fixEventName(eventName, mode)
if (eventName !== fixedEventName) {
const quote = eventArg.raw[0]
Expand Down Expand Up @@ -158,6 +203,16 @@ module.exports = {
})
return
}

if (regex && !regex.test(eventName)) {
context.report({
node,
data: { eventName, regex: regexOption },
messageId: 'eventNameViolatesRegex',
})
return
}

const fixedEventName = fixEventName(eventName, mode)
if (eventName !== fixedEventName) {
const quote = type.value.raw[0]
Expand Down
30 changes: 30 additions & 0 deletions tests/lib/rules/event-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ const tests = {
}
}
`,
`
/* eslint event-names: [ "warn", "regex", { "regex": "^[a-z]+:[a-z0-9]+$" } ] */
createMachine({
on: {
'power:on': {},
'click:1': {},
},
})
`,
],

invalid: [
Expand Down Expand Up @@ -315,6 +324,27 @@ const tests = {
},
],
},
{
code: `
/* eslint event-names: [ "warn", "regex", { "regex": "^[a-z]+:[a-z0-9]+$" } ] */
createMachine({
on: {
PowerOn: {},
power_on: {},
},
})
`,
errors: [
{
messageId: 'eventNameViolatesRegex',
data: { eventName: 'PowerOn', regex: '^[a-z]+:[a-z0-9]+$' },
},
{
messageId: 'eventNameViolatesRegex',
data: { eventName: 'power_on', regex: '^[a-z]+:[a-z0-9]+$' },
},
],
},
],
}

Expand Down

0 comments on commit 84b38c9

Please sign in to comment.