-
-
Notifications
You must be signed in to change notification settings - Fork 696
⭐️New: Add vue/component-name-in-template-casing
#397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
98dbf8a
7c4a1d2
cd22a28
ee5cc0a
6861c81
2b4798b
3f86365
3dd2038
e35ca6c
243e61a
de29eb8
fea5dfc
38e4849
2ce184b
25c7e4d
54fb3fb
e8e0db0
802a9a8
4b300dc
33b47d5
fb3d433
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# enforce specific casing for the component naming style in template (vue/component-name-in-template-casing) | ||
|
||
- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`. | ||
- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule. | ||
|
||
Define a style for the component name in template casing for consistency purposes. | ||
|
||
## :book: Rule Details | ||
|
||
:+1: Examples of **correct** code for `PascalCase`: | ||
|
||
```html | ||
<template> | ||
<TheComponent /> | ||
</template> | ||
``` | ||
|
||
:-1: Examples of **incorrect** code for `PascalCase`: | ||
|
||
```html | ||
<template> | ||
<the-component /> | ||
<theComponent /> | ||
<The-component /> | ||
</template> | ||
``` | ||
|
||
:+1: Examples of **correct** code for `kebab-case`: | ||
|
||
```html | ||
<template> | ||
<the-component /> | ||
</template> | ||
``` | ||
|
||
:-1: Examples of **incorrect** code for `kebab-case`: | ||
|
||
```html | ||
<template> | ||
<TheComponent /> | ||
<theComponent /> | ||
<Thecomponent /> | ||
<The-component /> | ||
</template> | ||
``` | ||
|
||
## :wrench: Options | ||
|
||
Default casing is set to `PascalCase`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shouldn't be the default for the reasons Iv listed on #250 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my understanding, we can only lint Single File Components and according to the Vue Style Guide PascalCase is strongly recommended in SFC's, so this seems like a sensible default. If you want to use kebab-case it's configurable so it can be either. +1 for this PR, really looking forward to it being merged 🎉
|
||
|
||
```json | ||
"vue/component-name-in-template-casing": ["error", | ||
"PascalCase|kebab-case", | ||
{ | ||
"ignores": [] | ||
} | ||
] | ||
``` | ||
|
||
- `ignores` (`string[]`) ... The element name to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component. | ||
|
||
|
||
:+1: Examples of **correct** code for `{ignores: ["custom-element"]}`: | ||
|
||
```html | ||
<template> | ||
<custom-element></custom-element> | ||
<TheComponent/> | ||
</template> | ||
``` | ||
|
||
## Related links | ||
|
||
- [Style guide - Component name casing in templates](https://vuejs.org/v2/style-guide/#Component-name-casing-in-templates-strongly-recommended) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** | ||
* @author Yosuke Ota | ||
* issue https://github.com/vuejs/eslint-plugin-vue/issues/250 | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const utils = require('../utils') | ||
const casing = require('../utils/casing') | ||
|
||
const allowedCaseOptions = ['PascalCase', 'kebab-case'] | ||
const defaultCase = 'PascalCase' | ||
|
||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'enforce specific casing for the component naming style in template', | ||
category: undefined, // strongly-recommended | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.1/docs/rules/component-name-in-template-casing.md' | ||
}, | ||
fixable: 'code', | ||
schema: [ | ||
{ | ||
enum: allowedCaseOptions | ||
}, | ||
{ | ||
type: 'object', | ||
properties: { | ||
ignores: { | ||
type: 'array', | ||
items: { type: 'string' }, | ||
uniqueItems: true, | ||
additionalItems: false | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
|
||
create (context) { | ||
const caseOption = context.options[0] | ||
const options = context.options[1] || {} | ||
const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase | ||
const ignores = options.ignores || [] | ||
const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() | ||
const sourceCode = context.getSourceCode() | ||
|
||
let hasInvalidEOF = false | ||
|
||
return utils.defineTemplateBodyVisitor(context, { | ||
'VElement' (node) { | ||
if (hasInvalidEOF) { | ||
return | ||
} | ||
|
||
if (!utils.isCustomComponent(node)) { | ||
return | ||
} | ||
|
||
const name = node.rawName | ||
if (ignores.indexOf(name) >= 0) { | ||
return | ||
} | ||
const casingName = casing.getConverter(caseType)(name) | ||
if (casingName !== name) { | ||
const startTag = node.startTag | ||
const open = tokens.getFirstToken(startTag) | ||
|
||
context.report({ | ||
node: open, | ||
loc: open.loc, | ||
message: 'Component name "{{name}}" is not {{caseType}}.', | ||
data: { | ||
name, | ||
caseType | ||
}, | ||
fix: fixer => { | ||
const endTag = node.endTag | ||
if (!endTag) { | ||
return fixer.replaceText(open, `<${casingName}`) | ||
} | ||
const endTagOpen = tokens.getFirstToken(endTag) | ||
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by: | ||
// return [ | ||
// fixer.replaceText(open, `<${casingName}`), | ||
// fixer.replaceText(endTagOpen, `</${casingName}`) | ||
// ] | ||
const code = `<${casingName}${sourceCode.text.slice(open.range[1], endTagOpen.range[0])}</${casingName}` | ||
return fixer.replaceTextRange([open.range[0], endTagOpen.range[1]], code) | ||
} | ||
}) | ||
} | ||
} | ||
}, { | ||
Program (node) { | ||
hasInvalidEOF = utils.hasInvalidEOF(node) | ||
} | ||
}) | ||
} | ||
} |