Skip to content

Commit

Permalink
Add ignore option to vue/multi-word-component-names rule (#1681)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Oct 28, 2021
1 parent 166dfbf commit ecc1ae5
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 62 deletions.
41 changes: 39 additions & 2 deletions docs/rules/multi-word-component-names.md
Expand Up @@ -65,6 +65,7 @@ export default {
<eslint-code-block filename="src/Todo.vue" :rules="{'vue/multi-word-component-names': ['error']}">

```vue
<!-- filename: Todo.vue -->
<script>
/* ✗ BAD */
export default {
Expand All @@ -77,11 +78,47 @@ export default {

## :wrench: Options

Nothing.
```json
{
"vue/multi-word-component-names": ["error", {
"ignores": []
}]
}
```

- `ignores` (`string[]`) ... The component names to ignore. Sets the component name to allow.

### `ignores: ["Todo"]`

<eslint-code-block fix :rules="{'vue/multi-word-component-names': ['error', {ignores: ['Todo']}]}">

```vue
<script>
export default {
/* ✓ GOOD */
name: 'Todo'
}
</script>
```

</eslint-code-block>

<eslint-code-block fix :rules="{'vue/multi-word-component-names': ['error', {ignores: ['Todo']}]}">

```vue
<script>
export default {
/* ✗ BAD */
name: 'Item'
}
</script>
```

</eslint-code-block>

## :books: Further Reading

- [Style guide - Multi-word component names](https://vuejs.org/v2/style-guide/#Multi-word-component-names-essential)
- [Style guide - Multi-word component names](https://v3.vuejs.org/style-guide/#multi-word-component-names-essential)

## :rocket: Version

Expand Down
136 changes: 76 additions & 60 deletions lib/rules/multi-word-component-names.js
Expand Up @@ -15,23 +15,6 @@ const RESERVED_NAMES_IN_VUE3 = new Set(
require('../utils/vue3-builtin-components')
)

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Returns true if the given component name is valid, otherwise false.
* @param {string} name
* */
function isValidComponentName(name) {
if (name.toLowerCase() === 'app' || RESERVED_NAMES_IN_VUE3.has(name)) {
return true
} else {
const elements = casing.kebabCase(name).split('-')
return elements.length > 1
}
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand All @@ -44,22 +27,92 @@ module.exports = {
categories: ['vue3-essential', 'essential'],
url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
},
schema: [],
schema: [
{
type: 'object',
properties: {
ignores: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
}
],
messages: {
unexpected: 'Component name "{{value}}" should always be multi-word.'
}
},
/** @param {RuleContext} context */
create(context) {
const fileName = context.getFilename()
let componentName = fileName.replace(/\.[^/.]+$/, '')
/** @type {Set<string>} */
const ignores = new Set()
ignores.add('App')
ignores.add('app')
for (const ignore of (context.options[0] && context.options[0].ignores) ||
[]) {
ignores.add(ignore)
if (casing.isPascalCase(ignore)) {
// PascalCase
ignores.add(casing.kebabCase(ignore))
}
}
let hasVue = false
let hasName = false

/**
* Returns true if the given component name is valid, otherwise false.
* @param {string} name
* */
function isValidComponentName(name) {
if (ignores.has(name) || RESERVED_NAMES_IN_VUE3.has(name)) {
return true
}
const elements = casing.kebabCase(name).split('-')
return elements.length > 1
}

/**
* @param {Expression | SpreadElement} nameNode
*/
function validateName(nameNode) {
if (nameNode.type !== 'Literal') return
const componentName = `${nameNode.value}`
if (!isValidComponentName(componentName)) {
context.report({
node: nameNode,
messageId: 'unexpected',
data: {
value: componentName
}
})
}
}

return utils.compositingVisitors(
utils.executeOnCallVueComponent(context, (node) => {
hasVue = true
if (node.arguments.length !== 2) return
hasName = true
validateName(node.arguments[0])
}),
utils.executeOnVue(context, (obj) => {
hasVue = true
const node = utils.findProperty(obj, 'name')
if (!node) return
hasName = true
validateName(node.value)
}),
{
/** @param {Program} node */
Program(node) {
'Program:exit'(node) {
if (hasName) return
if (!hasVue && node.body.length > 0) return
const fileName = context.getFilename()
const componentName = fileName.replace(/\.[^/.]+$/, '')
if (
!node.body.length &&
utils.isVueFile(fileName) &&
!isValidComponentName(componentName)
) {
Expand All @@ -72,44 +125,7 @@ module.exports = {
})
}
}
},

utils.executeOnVue(context, (obj) => {
const node = utils.findProperty(obj, 'name')

/** @type {SourceLocation | null} */
let loc = null

// Check if the component has a name property.
if (node) {
const valueNode = node.value
if (valueNode.type !== 'Literal') return

componentName = `${valueNode.value}`
loc = node.loc
} else if (
obj.parent.type === 'CallExpression' &&
obj.parent.arguments.length === 2
) {
// The component is registered globally with 'Vue.component', where
// the first paremter is the component name.
const argument = obj.parent.arguments[0]
if (argument.type !== 'Literal') return

componentName = `${argument.value}`
loc = argument.loc
}

if (!isValidComponentName(componentName)) {
context.report({
messageId: 'unexpected',
data: {
value: componentName
},
loc: loc || { line: 1, column: 0 }
})
}
})
}
)
}
}
28 changes: 28 additions & 0 deletions tests/lib/rules/multi-word-component-names.js
Expand Up @@ -158,6 +158,17 @@ tester.run('multi-word-component-names', rule, {
Vue.component('TheTest', {})
</script>
`
},
{
filename: 'test.vue',
options: [{ ignores: ['Todo'] }],
code: `
<script>
export default {
name: 'Todo'
}
</script>
`
}
],
invalid: [
Expand Down Expand Up @@ -248,6 +259,23 @@ tester.run('multi-word-component-names', rule, {
line: 3
}
]
},
{
filename: 'test.vue',
options: [{ ignores: ['Todo'] }],
code: `
<script>
export default {
name: 'Item'
}
</script>
`,
errors: [
{
message: 'Component name "Item" should always be multi-word.',
line: 4
}
]
}
]
})

0 comments on commit ecc1ae5

Please sign in to comment.