Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
98dbf8a
Add Vue.extend support, add missing info about Vue.mixin check in readme
michalsnik Jan 2, 2018
7c4a1d2
Docs: fixes wording in docs (#372)
samturrell Feb 1, 2018
cd22a28
Fix: fix script-indent to prevent removing <script> tag (fixes #367) …
mysticatea Feb 16, 2018
ee5cc0a
[Update] Make `vue/max-attributes-per-line` fixable (#380)
ota-meshi Feb 16, 2018
6861c81
Update: make `vue/order-in-components` fixable (#381)
ota-meshi Feb 17, 2018
2b4798b
[New] Add `vue/component-name-in-template-casing`
ota-meshi Feb 19, 2018
3f86365
[update] documents
ota-meshi Feb 19, 2018
3dd2038
[fix] require-meta-docs-url
ota-meshi Feb 19, 2018
e35ca6c
[fix] failed tests
ota-meshi Feb 19, 2018
243e61a
[fix] review contents
ota-meshi Feb 22, 2018
de29eb8
[fix] No deletes space and attributes of endTag
ota-meshi Feb 23, 2018
fea5dfc
[fix] Remove test unnecessary option
ota-meshi Feb 23, 2018
38e4849
Merge branch 'master' into add-component-name-in-template-casing
ota-meshi Feb 24, 2018
2ce184b
[fix] lint error caused by merging the master for conflict resolution
ota-meshi Feb 24, 2018
25c7e4d
Merge branch 'upstream/master' into add-component-name-in-template-ca…
ota-meshi Jul 18, 2018
54fb3fb
Add ignores option.
ota-meshi Jul 18, 2018
e8e0db0
Fixed that extra differences.
ota-meshi Jul 19, 2018
802a9a8
Merge branch 'master' into add-component-name-in-template-casing
ota-meshi Jul 27, 2018
4b300dc
update docs link
ota-meshi Jul 27, 2018
33b47d5
Merge branch 'master' into pr/397
michalsnik Jul 30, 2018
fb3d433
Update formatting
michalsnik Jul 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[New] Add vue/component-name-in-template-casing
  • Loading branch information
ota-meshi authored Feb 19, 2018
commit 2b4798b626c69f659f894a18284eebeba47f6bb7
54 changes: 54 additions & 0 deletions docs/rules/component-name-in-template-casing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# enforce specific casing for the component name in tamplates (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 tamplates 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`.
Copy link

@Mouvedia Mouvedia Apr 26, 2018

Choose a reason for hiding this comment

The 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)
Please don't merge this until that's fixed.

Copy link

@samit4me samit4me Jun 22, 2018

Choose a reason for hiding this comment

The 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 🎉

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this plugin uses the Style Guide as the source of truth, so If you believe kebab-case should be the default (a lot of people I work with agree) it might be more appropriate to raise a new issue over on the Style Guide repo itself, see https://github.com/vuejs/vuejs.org/blob/e93b8371d73e4467dd8152703ddf1db423f489a2/src/v2/style-guide/index.md#single-file-component-filename-casing-strongly-recommended


```
"vue/component-name-in-template-casing": ["error", "PascalCase|kebab-case"]
```

1 change: 1 addition & 0 deletions lib/configs/strongly-recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
extends: require.resolve('./essential'),
rules: {
'vue/attribute-hyphenation': 'error',
'vue/component-name-in-template-casing': 'error',
'vue/html-end-tags': 'error',
'vue/html-indent': 'error',
'vue/html-self-closing': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
rules: {
'attribute-hyphenation': require('./rules/attribute-hyphenation'),
'comment-directive': require('./rules/comment-directive'),
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
'html-end-tags': require('./rules/html-end-tags'),
Expand Down
88 changes: 88 additions & 0 deletions lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @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']

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'enforce specific casing for the component name in tamplates',
category: 'strongly-recommended',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set category to undefined, as it would require us to do major release. And we're not going to do so in the next 2 weeks. We're focusing on minor changes and will do a major release once per 2-3 months probably to avoid confusion.

url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/component-name-in-template-casing.md'
},
fixable: 'code',
schema: [
{
enum: allowedCaseOptions
}
]
},

create (context) {
const options = context.options[0]
const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add const defaultCase = 'PascalCase' right below allowedCaseOptions, and just use it here. It would be more obv I guess :)

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
const casingName = casing.getConverter(caseType)(name)
if (casingName !== name) {
const startTag = node.startTag
const open = tokens.getFirstToken(startTag)
context.report({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add empty line above, for clarity

node: open,
loc: open.loc,
message: 'Component name "{{name}}" is not {{caseType}}.',
data: {
name,
caseType: caseType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use shorthand here :)

},
fix: fixer => {
const endTag = node.endTag
if (!endTag) {
return fixer.replaceText(open, `<${casingName}`)
}
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by:
// return [
// fixer.replaceText(open, `<${casingName}`),
// fixer.replaceText(endTag, `</${casingName}>`)
// ]
const code = `<${casingName}${sourceCode.text.slice(open.range[1], endTag.range[0])}</${casingName}>`
return fixer.replaceTextRange([open.range[0], endTag.range[1]], code)
}
})
}
}
}, {
Program (node) {
hasInvalidEOF = utils.hasInvalidEOF(node)
}
})
}
}
234 changes: 234 additions & 0 deletions tests/lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/**
* @author Yosuke Ota
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/component-name-in-template-casing')
const RuleTester = require('eslint').RuleTester

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const tester = new RuleTester({
parser: 'vue-eslint-parser'
})

tester.run('html-self-closing', rule, {
valid: [
// default
'<template><div/></template>',
'<template><img></template>',
'<template><TheComponent/></template>',
'<template><svg><path/></svg></template>',
'<template><math><mspace/></math></template>',
'<template><div><slot></slot></div></template>',

// kebab-case
{
code: '<template><the-component></the-component></template>',
output: null,
options: ['kebab-case']
},
{
code: '<template><div/></template>',
output: null,
options: ['kebab-case']
},
{
code: '<template><img></template>',
output: null,
options: ['kebab-case']
},
{
code: '<template><svg><path/></svg></template>',
output: null,
options: ['kebab-case']
},
{
code: '<template><math><mspace/></math></template>',
output: null,
options: ['kebab-case']
},
// Invalid EOF
'<template><the-component a=">test</the-component></template>',
'<template><the-component><!--test</the-component></template>'
],
invalid: [
{
code: `
<template>
<the-component id="id">
<!-- comment -->
</the-component>
</template>
`,
output: `
<template>
<TheComponent id="id">
<!-- comment -->
</TheComponent>
</template>
`,
errors: ['Component name "the-component" is not PascalCase.']
},
{
code: `
<template>
<the-component id="id"/>
</template>
`,
output: `
<template>
<TheComponent id="id"/>
</template>
`,
errors: ['Component name "the-component" is not PascalCase.']
},
{
code: `
<template>
<TheComponent id="id">
<!-- comment -->
</TheComponent>
</template>
`,
options: ['kebab-case'],
output: `
<template>
<the-component id="id">
<!-- comment -->
</the-component>
</template>
`,
errors: ['Component name "TheComponent" is not kebab-case.']
},
{
code: `
<template>
<TheComponent id="id"/>
</template>
`,
options: ['kebab-case'],
output: `
<template>
<the-component id="id"/>
</template>
`,
errors: ['Component name "TheComponent" is not kebab-case.']
},
{
code: `
<template>
<the-component
id="id"/>
</template>
`,
output: `
<template>
<TheComponent
id="id"/>
</template>
`,
errors: ['Component name "the-component" is not PascalCase.']
},
{
code: `
<template>
<the-component/>
</template>
`,
output: `
<template>
<TheComponent/>
</template>
`,
errors: ['Component name "the-component" is not PascalCase.']
},
{
code: `
<template>
<the-component></the-component>
</template>
`,
output: `
<template>
<TheComponent></TheComponent>
</template>
`,
errors: ['Component name "the-component" is not PascalCase.']
},
{
code: `
<template>
<theComponent/>
</template>
`,
output: `
<template>
<TheComponent/>
</template>
`,
errors: ['Component name "theComponent" is not PascalCase.']
},
{
code: `
<template>
<theComponent/>
</template>
`,
options: ['kebab-case'],
output: `
<template>
<the-component/>
</template>
`,
errors: ['Component name "theComponent" is not kebab-case.']
},
{
code: `
<template>
<The-component/>
</template>
`,
output: `
<template>
<TheComponent/>
</template>
`,
errors: ['Component name "The-component" is not PascalCase.']
},
{
code: `
<template>
<The-component/>
</template>
`,
options: ['kebab-case'],
output: `
<template>
<the-component/>
</template>
`,
errors: ['Component name "The-component" is not kebab-case.']
},
{
code: `
<template>
<Thecomponent/>
</template>
`,
options: ['kebab-case'],
output: `
<template>
<thecomponent/>
</template>
`,
errors: ['Component name "Thecomponent" is not kebab-case.']
}
]
})