Skip to content

Commit e643d44

Browse files
authored
Add vue/no-restricted-component-names rule (#2210)
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

+1
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: |
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

+1
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'),
+157
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+
}

0 commit comments

Comments
 (0)