Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
vue/component-options-name-casing
rule (#1725)
* Add `vue/component-options-name-casing` rule * fix docs * fix demo * refactor * fix auto-fix * fix checking kebab-case * provide suggestions * accept suggestions
- Loading branch information
Showing
5 changed files
with
840 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,165 @@ | ||
--- | ||
pageClass: rule-details | ||
sidebarDepth: 0 | ||
title: vue/component-options-name-casing | ||
description: enforce the casing of component name in `components` options | ||
--- | ||
# vue/component-options-name-casing | ||
|
||
> enforce the casing of component name in `components` options | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge> | ||
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. | ||
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). | ||
|
||
## :book: Rule Details | ||
|
||
This rule aims to enforce casing of the component names in `components` options. | ||
|
||
## :wrench: Options | ||
|
||
```json | ||
{ | ||
"vue/component-options-name-casing": ["error", "PascalCase" | "kebab-case" | "camelCase"] | ||
} | ||
``` | ||
|
||
This rule has an option which can be one of these values: | ||
|
||
- `"PascalCase"` (default) ... enforce component names to pascal case. | ||
- `"kebab-case"` ... enforce component names to kebab case. | ||
- `"camelCase"` ... enforce component names to camel case. | ||
|
||
Please note that if you use kebab case in `components` options, | ||
you can **only** use kebab case in template; | ||
and if you use camel case in `components` options, | ||
you **can't** use pascal case in template. | ||
|
||
For demonstration, the code example is invalid: | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<!-- All are invalid. DO NOT use like these. --> | ||
<KebabCase /> | ||
<kebabCase /> | ||
<CamelCase /> | ||
</div> | ||
</template> | ||
<script> | ||
export default { | ||
components: { | ||
camelCase: MyComponent, | ||
'kebab-case': MyComponent | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
### `"PascalCase"` (default) | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✓ GOOD */ | ||
components: { | ||
AppHeader, | ||
AppSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✗ BAD */ | ||
components: { | ||
appHeader, | ||
'app-sidebar': AppSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### `"kebab-case"` | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error', 'kebab-case']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✓ GOOD */ | ||
components: { | ||
'app-header': AppHeader, | ||
'app-sidebar': appSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error', 'kebab-case']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✗ BAD */ | ||
components: { | ||
AppHeader, | ||
appSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### `"camelCase"` | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error', 'camelCase']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✓ GOOD */ | ||
components: { | ||
appHeader, | ||
appSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
<eslint-code-block fix :rules="{'vue/component-options-name-casing': ['error', 'camelCase']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
/* ✗ BAD */ | ||
components: { | ||
AppHeader, | ||
'app-sidebar': appSidebar | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-options-name-casing.js) | ||
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-options-name-casing.js) |
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,115 @@ | ||
/** | ||
* @author Pig Fang | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const utils = require('../utils') | ||
const casing = require('../utils/casing') | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Helpers | ||
// ------------------------------------------------------------------------------ | ||
|
||
/** | ||
* @param {import('../../typings/eslint-plugin-vue/util-types/ast').Expression} node | ||
* @returns {string | null} | ||
*/ | ||
function getOptionsComponentName(node) { | ||
if (node.type === 'Identifier') { | ||
return node.name | ||
} | ||
if (node.type === 'Literal') { | ||
return typeof node.value === 'string' ? node.value : null | ||
} | ||
return null | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: | ||
'enforce the casing of component name in `components` options', | ||
categories: undefined, | ||
url: 'https://eslint.vuejs.org/rules/component-options-name-casing.html' | ||
}, | ||
fixable: 'code', | ||
hasSuggestions: true, | ||
schema: [{ enum: casing.allowedCaseOptions }], | ||
messages: { | ||
caseNotMatched: 'Component name "{{component}}" is not {{caseType}}.', | ||
possibleRenaming: 'Rename component name to be in {{caseType}}.' | ||
} | ||
}, | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
const caseType = context.options[0] || 'PascalCase' | ||
|
||
const canAutoFix = caseType === 'PascalCase' | ||
const checkCase = casing.getChecker(caseType) | ||
const convert = casing.getConverter(caseType) | ||
|
||
return utils.executeOnVue(context, (obj) => { | ||
const node = utils.findProperty(obj, 'components') | ||
if (!node || node.value.type !== 'ObjectExpression') { | ||
return | ||
} | ||
|
||
node.value.properties.forEach((property) => { | ||
if (property.type !== 'Property') { | ||
return | ||
} | ||
|
||
const name = getOptionsComponentName(property.key) | ||
if (!name || checkCase(name)) { | ||
return | ||
} | ||
|
||
context.report({ | ||
node: property.key, | ||
messageId: 'caseNotMatched', | ||
data: { | ||
component: name, | ||
caseType | ||
}, | ||
fix: canAutoFix | ||
? (fixer) => { | ||
const converted = convert(name) | ||
return property.shorthand | ||
? fixer.replaceText(property, `${converted}: ${name}`) | ||
: fixer.replaceText(property.key, converted) | ||
} | ||
: undefined, | ||
suggest: canAutoFix | ||
? undefined | ||
: [ | ||
{ | ||
messageId: 'possibleRenaming', | ||
data: { caseType }, | ||
fix: (fixer) => { | ||
const converted = convert(name) | ||
if (caseType === 'kebab-case') { | ||
return property.shorthand | ||
? fixer.replaceText(property, `'${converted}': ${name}`) | ||
: fixer.replaceText(property.key, `'${converted}'`) | ||
} | ||
return property.shorthand | ||
? fixer.replaceText(property, `${converted}: ${name}`) | ||
: fixer.replaceText(property.key, converted) | ||
} | ||
} | ||
] | ||
}) | ||
}) | ||
}) | ||
} | ||
} |
Oops, something went wrong.