Skip to content

Commit b48dc16

Browse files
ota-meshimichalsnik
authored andcommitted
Fix: Add registeredComponentsOnly option to component-name-in-template-casing rule (#714)
* Add `registeredComponentsOnly` option to `component-name-in-template-casing` rule * update doc * no message * update test case * Added test cases of RegExp utils. * update for Node 6
1 parent eefb98f commit b48dc16

File tree

6 files changed

+569
-61
lines changed

6 files changed

+569
-61
lines changed

docs/rules/component-name-in-template-casing.md

+70-15
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,49 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
1919

2020
```json
2121
{
22-
"vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", {
22+
"vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", {
23+
"registeredComponentsOnly": true,
2324
"ignores": []
2425
}]
2526
}
2627
```
2728

2829
- `"PascalCase"` (default) ... enforce tag names to pascal case. E.g. `<CoolComponent>`. This is consistent with the JSX practice.
2930
- `"kebab-case"` ... enforce tag names to kebab case: E.g. `<cool-component>`. This is consistent with the HTML practice which is case-insensitive originally.
30-
- `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component.
31+
- `registeredComponentsOnly` ... If `true`, only registered components (in PascalCase) are checked. If `false`, check all.
32+
default `true`
33+
- `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/"`.
3134

32-
### `"PascalCase"`
35+
### `"PascalCase", { registeredComponentsOnly: true }` (default)
3336

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

3639
```vue
3740
<template>
3841
<!-- ✓ GOOD -->
39-
<TheComponent />
42+
<CoolComponent />
4043
4144
<!-- ✗ BAD -->
42-
<the-component />
43-
<theComponent />
44-
<The-component />
45+
<cool-component />
46+
<coolComponent />
47+
<Cool-component />
48+
49+
<!-- ignore -->
50+
<UnregisteredComponent />
51+
<unregistered-component />
52+
53+
<registered-in-kebab-case />
54+
<registeredInCamelCase />
4555
</template>
56+
<script>
57+
export default {
58+
components: {
59+
CoolComponent,
60+
'registered-in-kebab-case': VueComponent1,
61+
'registeredInCamelCase': VueComponent2
62+
}
63+
}
64+
</script>
4665
```
4766

4867
</eslint-code-block>
@@ -54,28 +73,64 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
5473
```vue
5574
<template>
5675
<!-- ✓ GOOD -->
57-
<the-component />
76+
<cool-component />
5877
5978
<!-- ✗ BAD -->
60-
<TheComponent />
61-
<theComponent />
62-
<Thecomponent />
63-
<The-component />
79+
<CoolComponent />
80+
<coolComponent />
81+
<Cool-component />
82+
83+
<!-- ignore -->
84+
<unregistered-component />
85+
<UnregisteredComponent />
6486
</template>
87+
<script>
88+
export default {
89+
components: {
90+
CoolComponent
91+
}
92+
}
93+
</script>
6594
```
6695

6796
</eslint-code-block>
6897

98+
### `"PascalCase", { registeredComponentsOnly: false }`
99+
100+
<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }]}">
101+
102+
```vue
103+
<template>
104+
<!-- ✓ GOOD -->
105+
<CoolComponent />
106+
<UnregisteredComponent />
107+
108+
<!-- ✗ BAD -->
109+
<cool-component />
110+
<unregistered-component />
111+
</template>
112+
<script>
113+
export default {
114+
components: {
115+
CoolComponent
116+
}
117+
}
118+
</script>
119+
```
120+
121+
</eslint-code-block>
69122

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

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

74127
```vue
75128
<template>
76129
<!-- ✓ GOOD -->
77-
<TheComponent/>
130+
<CoolComponent/>
78131
<custom-element></custom-element>
132+
<custom-button></custom-button>
133+
<custom-input />
79134
80135
<!-- ✗ BAD -->
81136
<magic-element></magic-element>

lib/rules/component-name-in-template-casing.js

+57-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010

1111
const utils = require('../utils')
1212
const casing = require('../utils/casing')
13+
const { toRegExp } = require('../utils/regexp')
14+
15+
// -----------------------------------------------------------------------------
16+
// Helpers
17+
// -----------------------------------------------------------------------------
1318

1419
const allowedCaseOptions = ['PascalCase', 'kebab-case']
1520
const defaultCase = 'PascalCase'
@@ -39,6 +44,9 @@ module.exports = {
3944
items: { type: 'string' },
4045
uniqueItems: true,
4146
additionalItems: false
47+
},
48+
registeredComponentsOnly: {
49+
type: 'boolean'
4250
}
4351
},
4452
additionalProperties: false
@@ -50,9 +58,43 @@ module.exports = {
5058
const caseOption = context.options[0]
5159
const options = context.options[1] || {}
5260
const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase
53-
const ignores = options.ignores || []
61+
const ignores = (options.ignores || []).map(toRegExp)
62+
const registeredComponentsOnly = options.registeredComponentsOnly !== false
5463
const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
5564

65+
const registeredComponents = []
66+
67+
/**
68+
* Checks whether the given node is the verification target node.
69+
* @param {VElement} node element node
70+
* @returns {boolean} `true` if the given node is the verification target node.
71+
*/
72+
function isVerifyTarget (node) {
73+
if (ignores.some(re => re.test(node.rawName))) {
74+
// ignore
75+
return false
76+
}
77+
78+
if (!registeredComponentsOnly) {
79+
// If the user specifies registeredComponentsOnly as false, it checks all component tags.
80+
if ((!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
81+
utils.isHtmlWellKnownElementName(node.rawName) ||
82+
utils.isSvgWellKnownElementName(node.rawName)
83+
) {
84+
return false
85+
}
86+
return true
87+
}
88+
// We only verify the components registered in the component.
89+
if (registeredComponents
90+
.filter(name => casing.pascalCase(name) === name) // When defining a component with PascalCase, you can use either case
91+
.some(name => node.rawName === name || casing.pascalCase(node.rawName) === name)) {
92+
return true
93+
}
94+
95+
return false
96+
}
97+
5698
let hasInvalidEOF = false
5799

58100
return utils.defineTemplateBodyVisitor(context, {
@@ -61,18 +103,11 @@ module.exports = {
61103
return
62104
}
63105

64-
if (
65-
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
66-
utils.isHtmlWellKnownElementName(node.rawName) ||
67-
utils.isSvgWellKnownElementName(node.rawName)
68-
) {
106+
if (!isVerifyTarget(node)) {
69107
return
70108
}
71109

72110
const name = node.rawName
73-
if (ignores.indexOf(name) >= 0) {
74-
return
75-
}
76111
const casingName = casing.getConverter(caseType)(name)
77112
if (casingName !== name) {
78113
const startTag = node.startTag
@@ -100,10 +135,18 @@ module.exports = {
100135
})
101136
}
102137
}
103-
}, {
104-
Program (node) {
105-
hasInvalidEOF = utils.hasInvalidEOF(node)
106-
}
107-
})
138+
},
139+
Object.assign(
140+
{
141+
Program (node) {
142+
hasInvalidEOF = utils.hasInvalidEOF(node)
143+
}
144+
},
145+
registeredComponentsOnly
146+
? utils.executeOnVue(context, (obj) => {
147+
registeredComponents.push(...utils.getRegisteredComponents(obj).map(n => n.name))
148+
})
149+
: {}
150+
))
108151
}
109152
}

lib/utils/regexp.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu
2+
const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source)
3+
4+
const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u
5+
6+
/**
7+
* Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
8+
* "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
9+
*
10+
* @param {string} string The string to escape.
11+
* @returns {string} Returns the escaped string.
12+
*/
13+
function escape (string) {
14+
return (string && RE_HAS_REGEXP_CHAR.test(string))
15+
? string.replace(RE_REGEXP_CHAR, '\\$&')
16+
: string
17+
}
18+
19+
/**
20+
* Convert a string to the `RegExp`.
21+
* Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`.
22+
* Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
23+
*
24+
* @param {string} string The string to convert.
25+
* @returns {string} Returns the `RegExp`.
26+
*/
27+
function toRegExp (string) {
28+
const parts = RE_REGEXP_STR.exec(string)
29+
if (parts) {
30+
return new RegExp(parts[1], parts[2])
31+
}
32+
return new RegExp(`^${escape(string)}$`)
33+
}
34+
35+
module.exports = {
36+
escape,
37+
toRegExp
38+
}

tests/lib/autofix.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('Complex autofix test cases', () => {
9595
'component': 'never'
9696
}
9797
}],
98-
'vue/component-name-in-template-casing': ['error', 'kebab-case']
98+
'vue/component-name-in-template-casing': ['error', 'kebab-case', { registeredComponentsOnly: false }]
9999
}})
100100

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

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

0 commit comments

Comments
 (0)