Skip to content

Fix: Add registeredComponentsOnly option to component-name-in-template-casing rule #714

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

Merged
merged 7 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
85 changes: 70 additions & 15 deletions docs/rules/component-name-in-template-casing.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,49 @@ This rule aims to warn the tag names other than the configured casing in Vue.js

```json
{
"vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", {
"vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", {
"registeredComponentsOnly": true,
"ignores": []
}]
}
```

- `"PascalCase"` (default) ... enforce tag names to pascal case. E.g. `<CoolComponent>`. This is consistent with the JSX practice.
- `"kebab-case"` ... enforce tag names to kebab case: E.g. `<cool-component>`. This is consistent with the HTML practice which is case-insensitive originally.
- `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component.
- `registeredComponentsOnly` ... If `true`, only registered components (in PascalCase) are checked. If `false`, check all.
default `true`
- `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, custom elements or Vue components with special name. You can set the regexp by writing it like `"/^name/"`.

### `"PascalCase"`
### `"PascalCase", { registeredComponentsOnly: true }` (default)

<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<TheComponent />
<CoolComponent />

<!-- ✗ BAD -->
<the-component />
<theComponent />
<The-component />
<cool-component />
<coolComponent />
<Cool-component />

<!-- ignore -->
<UnregisteredComponent />
<unregistered-component />

<registered-in-kebab-case />
<registeredInCamelCase />
</template>
<script>
export default {
components: {
CoolComponent,
'registered-in-kebab-case': VueComponent1,
'registeredInCamelCase': VueComponent2
}
}
</script>
```

</eslint-code-block>
Expand All @@ -55,28 +74,64 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
```vue
<template>
<!-- ✓ GOOD -->
<the-component />
<cool-component />

<!-- ✗ BAD -->
<TheComponent />
<theComponent />
<Thecomponent />
<The-component />
<CoolComponent />
<coolComponent />
<Cool-component />

<!-- ignore -->
<unregistered-component />
<UnregisteredComponent />
</template>
<script>
export default {
components: {
CoolComponent
}
}
</script>
```

</eslint-code-block>

### `"PascalCase", { registeredComponentsOnly: false }`

<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }]}">

```vue
<template>
<!-- ✓ GOOD -->
<CoolComponent />
<UnregisteredComponent />

<!-- ✗ BAD -->
<cool-component />
<unregistered-component />
</template>
<script>
export default {
components: {
CoolComponent
}
}
</script>
```

</eslint-code-block>

### `"PascalCase", { ignores: ["custom-element"] }`
### `"PascalCase", { ignores: ["/^custom-/"], registeredComponentsOnly: false }`

<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', {ignores: ['custom-element']}]}">
<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', {ignores: ['/^custom-/'], registeredComponentsOnly: false}]}">

```vue
<template>
<!-- ✓ GOOD -->
<TheComponent/>
<CoolComponent/>
<custom-element></custom-element>
<custom-button></custom-button>
<custom-input />

<!-- ✗ BAD -->
<magic-element></magic-element>
Expand Down
89 changes: 75 additions & 14 deletions lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,32 @@
const utils = require('../utils')
const casing = require('../utils/casing')

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

const allowedCaseOptions = ['PascalCase', 'kebab-case']
const defaultCase = 'PascalCase'

const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about adding comment above for a reference with example string that it describes? Or even better, write unit test for each REGEX? From my experience unit tests serve the best documentation for regular expressions :)

Copy link
Member Author

Choose a reason for hiding this comment

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

@michalsnik
Thank you for review! I added test cases.

const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source)
function escapeRegExp (string) {
return (string && RE_HAS_REGEXP_CHAR.test(string))
? string.replace(RE_REGEXP_CHAR, '\\$&')
: string
}

const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u
function toRegExpList (array) {
return array.map(str => {
const parts = RE_REGEXP_STR.exec(str)
if (parts) {
return new RegExp(parts[1], parts[2])
}
return new RegExp(`^${escapeRegExp(str)}$`)
})
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand All @@ -39,6 +62,9 @@ module.exports = {
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
},
registeredComponentsOnly: {
type: 'boolean'
}
},
additionalProperties: false
Expand All @@ -50,9 +76,43 @@ module.exports = {
const caseOption = context.options[0]
const options = context.options[1] || {}
const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase
const ignores = options.ignores || []
const ignores = toRegExpList(options.ignores || [])
const registeredComponentsOnly = options.registeredComponentsOnly !== false
const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()

const registeredComponents = []

/**
* Checks whether the given node is the verification target node.
* @param {VElement} node element node
* @returns {boolean} `true` if the given node is the verification target node.
*/
function isVerifyTarget (node) {
if (ignores.some(re => re.test(node.rawName))) {
// ignore
return false
}

if (!registeredComponentsOnly) {
// If the user specifies registeredComponentsOnly as false, it checks all component tags.
if ((!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName)
) {
return false
}
return true
}
// We only verify the components registered in the component.
if (registeredComponents
.filter(name => casing.pascalCase(name) === name) // When defining a component with PascalCase, you can use either case
.some(name => node.rawName === name || casing.pascalCase(node.rawName) === name)) {
return true
}

return false
}

let hasInvalidEOF = false

return utils.defineTemplateBodyVisitor(context, {
Expand All @@ -61,18 +121,11 @@ module.exports = {
return
}

if (
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName)
) {
if (!isVerifyTarget(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
Expand Down Expand Up @@ -100,10 +153,18 @@ module.exports = {
})
}
}
}, {
Program (node) {
hasInvalidEOF = utils.hasInvalidEOF(node)
}
})
},
Object.assign(
{
Program (node) {
hasInvalidEOF = utils.hasInvalidEOF(node)
}
},
registeredComponentsOnly
? utils.executeOnVue(context, (obj) => {
registeredComponents.push(...utils.getRegisteredComponents(obj).map(n => n.name))
})
: {}
))
}
}
4 changes: 2 additions & 2 deletions tests/lib/autofix.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('Complex autofix test cases', () => {
'component': 'never'
}
}],
'vue/component-name-in-template-casing': ['error', 'kebab-case']
'vue/component-name-in-template-casing': ['error', 'kebab-case', { registeredComponentsOnly: false }]
}})

const pascalConfig = Object.assign({}, baseConfig, { 'rules': {
Expand All @@ -104,7 +104,7 @@ describe('Complex autofix test cases', () => {
'component': 'never'
}
}],
'vue/component-name-in-template-casing': ['error']
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }]
}})

it('Even if set kebab-case, the output should be as expected.', () => {
Expand Down
Loading