Skip to content

Commit e643d44

Browse files
authoredJun 12, 2023
Add vue/no-restricted-component-names rule (#2210)
·
v10.2.0v9.15.0
1 parent 81ce0ce commit e643d44

File tree

5 files changed

+510
-0
lines changed

5 files changed

+510
-0
lines changed
 

‎docs/rules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ For example:
234234
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
235235
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | | :hammer: |
236236
| [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components | | :warning: |
237+
| [vue/no-restricted-component-names](./no-restricted-component-names.md) | disallow specific component names | :bulb: | :hammer: |
237238
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | | :hammer: |
238239
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | :bulb: | :hammer: |
239240
| [vue/no-restricted-html-elements](./no-restricted-html-elements.md) | disallow specific HTML elements | | :hammer: |
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-restricted-component-names
5+
description: disallow specific component names
6+
---
7+
# vue/no-restricted-component-names
8+
9+
> disallow specific component names
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
13+
14+
## :book: Rule Details
15+
16+
This rule allows you to specify component names that you don't want to use in your application.
17+
18+
<eslint-code-block :rules="{'vue/no-restricted-component-names': ['error', 'Disallow']}">
19+
20+
```vue
21+
<!-- ✗ BAD -->
22+
<script>
23+
export default {
24+
name: 'Disallow',
25+
}
26+
</script>
27+
```
28+
29+
</eslint-code-block>
30+
31+
<eslint-code-block :rules="{'vue/no-restricted-component-names': ['error', 'Disallow']}">
32+
33+
```vue
34+
<!-- ✓ GOOD -->
35+
<script>
36+
export default {
37+
name: 'Allow',
38+
}
39+
</script>
40+
```
41+
42+
</eslint-code-block>
43+
44+
## :wrench: Options
45+
46+
This rule takes a list of strings, where each string is a component name or pattern to be restricted:
47+
48+
```json
49+
{
50+
"vue/no-restricted-component-names": ["error", "foo", "/^Disallow/"]
51+
}
52+
```
53+
54+
Alternatively, you can specify an object with a `name` property and an optional `message` and `suggest` property:
55+
56+
```json
57+
{
58+
"vue/no-restricted-component-names": [
59+
"error",
60+
{
61+
"name": "Disallow",
62+
"message": "Please do not use `Disallow` as a component name",
63+
"suggest": "allow"
64+
},
65+
{
66+
"name": "/^custom/",
67+
"message": "Please do not use component names starting with 'custom'"
68+
}
69+
]
70+
}
71+
```
72+
73+
<eslint-code-block :rules="{'vue/no-restricted-component-names': ['error', { name: 'Disallow', message: 'Please do not use \'Disallow\' as a component name', suggest: 'allow'}]}">
74+
75+
```vue
76+
<!-- ✗ BAD -->
77+
<script>
78+
export default {
79+
name: 'Disallow',
80+
}
81+
</script>
82+
```
83+
84+
</eslint-code-block>
85+
86+
## :mag: Implementation
87+
88+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-component-names.js)
89+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-component-names.js)

‎lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ module.exports = {
118118
'no-restricted-block': require('./rules/no-restricted-block'),
119119
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
120120
'no-restricted-class': require('./rules/no-restricted-class'),
121+
'no-restricted-component-names': require('./rules/no-restricted-component-names'),
121122
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
122123
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
123124
'no-restricted-html-elements': require('./rules/no-restricted-html-elements'),
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**
2+
* @author ItMaga <https://github.com/ItMaga>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const casing = require('../utils/casing')
9+
const { isRegExp, toRegExp } = require('../utils/regexp')
10+
11+
/**
12+
* @typedef {object} OptionParsed
13+
* @property { (name: string) => boolean } test
14+
* @property {string|undefined} [message]
15+
* @property {string|undefined} [suggest]
16+
*/
17+
18+
/**
19+
* @param {string} str
20+
* @returns {(str: string) => boolean}
21+
* @private
22+
*/
23+
function buildMatcher(str) {
24+
if (isRegExp(str)) {
25+
const regex = toRegExp(str)
26+
return (s) => regex.test(s)
27+
}
28+
return (s) => s === casing.pascalCase(str) || s === casing.kebabCase(str)
29+
}
30+
31+
/**
32+
* @param {string|{name: string, message?: string, suggest?: string}} option
33+
* @returns {OptionParsed}
34+
* @private
35+
* */
36+
function parseOption(option) {
37+
if (typeof option === 'string') {
38+
const matcher = buildMatcher(option)
39+
return { test: matcher }
40+
}
41+
const parsed = parseOption(option.name)
42+
parsed.message = option.message
43+
parsed.suggest = option.suggest
44+
return parsed
45+
}
46+
47+
/**
48+
* @param {Property | AssignmentProperty} property
49+
* @param {string | undefined} suggest
50+
* @returns {Rule.SuggestionReportDescriptor[]}
51+
* @private
52+
* */
53+
function createSuggest(property, suggest) {
54+
if (!suggest) {
55+
return []
56+
}
57+
58+
return [
59+
{
60+
fix(fixer) {
61+
return fixer.replaceText(property.value, JSON.stringify(suggest))
62+
},
63+
messageId: 'suggest',
64+
data: { suggest }
65+
}
66+
]
67+
}
68+
69+
module.exports = {
70+
meta: {
71+
hasSuggestions: true,
72+
type: 'suggestion',
73+
docs: {
74+
description: 'disallow specific component names',
75+
categories: undefined,
76+
url: 'https://eslint.vuejs.org/rules/no-restricted-component-names.html'
77+
},
78+
fixable: null,
79+
schema: {
80+
type: 'array',
81+
items: {
82+
oneOf: [
83+
{ type: 'string' },
84+
{
85+
type: 'object',
86+
properties: {
87+
name: { type: 'string' },
88+
message: { type: 'string', minLength: 1 },
89+
suggest: { type: 'string' }
90+
},
91+
required: ['name'],
92+
additionalProperties: false
93+
}
94+
]
95+
},
96+
uniqueItems: true,
97+
minItems: 0
98+
},
99+
messages: {
100+
// eslint-disable-next-line eslint-plugin/report-message-format
101+
disallow: '{{message}}',
102+
suggest: 'Instead, change to `{{suggest}}`.'
103+
}
104+
},
105+
/** @param {RuleContext} context */
106+
create(context) {
107+
/** @type {OptionParsed[]} */
108+
const options = context.options.map(parseOption)
109+
110+
/**
111+
* @param {ObjectExpression} node
112+
*/
113+
function verify(node) {
114+
const property = utils.findProperty(node, 'name')
115+
if (!property) return
116+
117+
const propertyName = utils.getStaticPropertyName(property)
118+
if (propertyName === 'name' && property.value.type === 'Literal') {
119+
const componentName = property.value.value?.toString()
120+
if (!componentName) {
121+
return
122+
}
123+
124+
for (const option of options) {
125+
if (option.test(componentName)) {
126+
context.report({
127+
node: property.value,
128+
messageId: 'disallow',
129+
data: {
130+
message:
131+
option.message ||
132+
`Using component name \`${componentName}\` is not allowed.`
133+
},
134+
suggest: createSuggest(property, option.suggest)
135+
})
136+
}
137+
}
138+
}
139+
}
140+
141+
return utils.compositingVisitors(
142+
utils.defineVueVisitor(context, {
143+
onVueObjectEnter(node) {
144+
verify(node)
145+
}
146+
}),
147+
utils.defineScriptSetupVisitor(context, {
148+
onDefineOptionsEnter(node) {
149+
const expression = node.arguments[0]
150+
if (expression.type === 'ObjectExpression') {
151+
verify(expression)
152+
}
153+
}
154+
})
155+
)
156+
}
157+
}
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/**
2+
* @author ItMaga <https://github.com/ItMaga>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../lib/rules/no-restricted-component-names')
9+
10+
const tester = new RuleTester({
11+
parser: require.resolve('vue-eslint-parser'),
12+
parserOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('no-restricted-component-names', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<script>
24+
export default {
25+
name: 'Allow'
26+
}
27+
`
28+
},
29+
{
30+
filename: 'test.vue',
31+
code: `
32+
new Vue({
33+
name: 'Allow',
34+
})
35+
`
36+
},
37+
{
38+
filename: 'test.vue',
39+
code: `
40+
<script setup>
41+
defineOptions({
42+
name: 'Allow'
43+
})
44+
</script>
45+
`
46+
}
47+
],
48+
invalid: [
49+
{
50+
filename: 'test.vue',
51+
code: `
52+
<script>
53+
export default {
54+
name: 'Disallow'
55+
}
56+
</script>
57+
`,
58+
options: ['Disallow', 'Disallow2'],
59+
errors: [
60+
{
61+
message: 'Using component name `Disallow` is not allowed.',
62+
line: 4,
63+
column: 15
64+
}
65+
]
66+
},
67+
{
68+
filename: 'test.vue',
69+
code: `
70+
<script>
71+
new Vue({
72+
name: 'Disallow',
73+
})
74+
</script>
75+
`,
76+
options: ['Disallow'],
77+
errors: [
78+
{
79+
message: 'Using component name `Disallow` is not allowed.',
80+
line: 4,
81+
column: 15
82+
}
83+
]
84+
},
85+
{
86+
filename: 'test.vue',
87+
code: `
88+
<script setup>
89+
defineOptions({
90+
name: 'Disallow'
91+
})
92+
</script>
93+
`,
94+
options: ['Disallow'],
95+
errors: [
96+
{
97+
message: 'Using component name `Disallow` is not allowed.',
98+
line: 4,
99+
column: 15
100+
}
101+
]
102+
},
103+
{
104+
filename: 'test.vue',
105+
code: `
106+
<script setup>
107+
defineOptions({
108+
name: 'FooBar'
109+
})
110+
</script>
111+
`,
112+
options: ['/^Foo(Bar|Baz)/'],
113+
errors: [
114+
{
115+
message: 'Using component name `FooBar` is not allowed.',
116+
line: 4,
117+
column: 15
118+
}
119+
]
120+
},
121+
{
122+
filename: 'test.vue',
123+
code: `
124+
<script setup>
125+
defineOptions({
126+
name: 'Disallow',
127+
inheritAttrs: false,
128+
})
129+
</script>
130+
`,
131+
options: [
132+
{ name: 'Disallow', message: 'Custom message', suggest: 'Allow' }
133+
],
134+
errors: [
135+
{
136+
message: 'Custom message',
137+
line: 4,
138+
column: 15,
139+
suggestions: [
140+
{
141+
desc: 'Instead, change to `Allow`.',
142+
output: `
143+
<script setup>
144+
defineOptions({
145+
name: "Allow",
146+
inheritAttrs: false,
147+
})
148+
</script>
149+
`
150+
}
151+
]
152+
}
153+
]
154+
},
155+
{
156+
filename: 'test.vue',
157+
code: `
158+
<script setup>
159+
defineOptions({
160+
name: 'Disallow',
161+
inheritAttrs: false,
162+
})
163+
</script>
164+
`,
165+
options: [{ name: 'Disallow', suggest: 'Allow' }],
166+
errors: [
167+
{
168+
message: 'Using component name `Disallow` is not allowed.',
169+
line: 4,
170+
column: 15,
171+
suggestions: [
172+
{
173+
desc: 'Instead, change to `Allow`.',
174+
output: `
175+
<script setup>
176+
defineOptions({
177+
name: "Allow",
178+
inheritAttrs: false,
179+
})
180+
</script>
181+
`
182+
}
183+
]
184+
}
185+
]
186+
},
187+
{
188+
filename: 'test.vue',
189+
code: `
190+
<script setup>
191+
defineOptions({
192+
name: 'Disallow',
193+
inheritAttrs: false,
194+
})
195+
</script>
196+
`,
197+
options: [{ name: 'Disallow', message: 'Custom message' }],
198+
errors: [
199+
{
200+
message: 'Custom message',
201+
line: 4,
202+
column: 15
203+
}
204+
]
205+
},
206+
{
207+
filename: 'test.vue',
208+
code: `
209+
<script setup>
210+
defineOptions({
211+
name: 1
212+
})
213+
</script>
214+
`,
215+
options: ['1'],
216+
errors: [
217+
{
218+
message: 'Using component name `1` is not allowed.',
219+
line: 4,
220+
column: 15
221+
}
222+
]
223+
},
224+
{
225+
filename: 'test.vue',
226+
code: `
227+
<script setup>
228+
defineOptions({
229+
name: 'disallowed-component',
230+
})
231+
</script>
232+
`,
233+
options: ['DisallowedComponent'],
234+
errors: [
235+
{
236+
message:
237+
'Using component name `disallowed-component` is not allowed.',
238+
line: 4,
239+
column: 15
240+
}
241+
]
242+
},
243+
{
244+
filename: 'test.vue',
245+
code: `
246+
<script setup>
247+
defineOptions({
248+
name: 'DisallowedComponent',
249+
})
250+
</script>
251+
`,
252+
options: ['disallowed-component'],
253+
errors: [
254+
{
255+
message: 'Using component name `DisallowedComponent` is not allowed.',
256+
line: 4,
257+
column: 15
258+
}
259+
]
260+
}
261+
]
262+
})

0 commit comments

Comments
 (0)
Please sign in to comment.