Skip to content

Commit 27fc097

Browse files
authored
Add vue/no-restricted-props rule (#1376)
1 parent 7212c36 commit 27fc097

File tree

5 files changed

+586
-0
lines changed

5 files changed

+586
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ For example:
302302
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
303303
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
304304
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
305+
| [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | |
305306
| [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute | |
306307
| [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` | |
307308
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |

docs/rules/no-restricted-props.md

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-restricted-props
5+
description: disallow specific props
6+
---
7+
# vue/no-restricted-props
8+
> disallow specific props
9+
10+
## :book: Rule Details
11+
12+
This rule allows you to specify props that you don't want to use in your application.
13+
14+
## :wrench: Options
15+
16+
This rule takes a list of strings, where each string is a prop name or pattern to be restricted:
17+
18+
```json
19+
{
20+
"vue/no-restricted-props": ["error", "value", "/^forbidden/"]
21+
}
22+
```
23+
24+
<eslint-code-block :rules="{'vue/no-restricted-props': ['error', 'value', '/^forbidden/']}">
25+
26+
```vue
27+
<script>
28+
export default {
29+
props: {
30+
/* ✗ BAD */
31+
value: String,
32+
forbiddenNum: Number,
33+
34+
/* ✓ GOOD */
35+
foo: {},
36+
bar: {},
37+
arrowedBool: Boolean,
38+
}
39+
}
40+
</script>
41+
```
42+
43+
</eslint-code-block>
44+
45+
<eslint-code-block :rules="{'vue/no-restricted-props': ['error', 'value', '/^forbidden/']}">
46+
47+
```vue
48+
<script>
49+
export default {
50+
props: [
51+
/* ✗ BAD */
52+
'value',
53+
'forbiddenNum',
54+
55+
/* ✓ GOOD */
56+
'foo',
57+
'bar',
58+
'arrowedBool',
59+
]
60+
}
61+
</script>
62+
```
63+
64+
</eslint-code-block>
65+
66+
Alternatively, the rule also accepts objects.
67+
68+
```json
69+
{
70+
"vue/no-restricted-props": ["error",
71+
{
72+
"name": "value",
73+
"message": "If you intend a prop for v-model, it should be 'modelValue' in Vue 3.",
74+
"suggest": "modelValue"
75+
},
76+
]
77+
}
78+
```
79+
80+
The following properties can be specified for the object.
81+
82+
- `name` ... Specify the prop name or pattern.
83+
- `message` ... Specify an optional custom message.
84+
- `suggest` ... Specify an optional name to suggest changes.
85+
86+
<eslint-code-block :rules="{'vue/no-restricted-props': ['error', { name: 'value', message: 'If you intend a prop for v-model, it should be \'modelValue\' in Vue 3.', suggest: 'modelValue'}]}">
87+
88+
```vue
89+
<script>
90+
export default {
91+
props: [
92+
/* ✗ BAD */
93+
'value',
94+
]
95+
}
96+
</script>
97+
```
98+
99+
</eslint-code-block>
100+
101+
## :mag: Implementation
102+
103+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-props.js)
104+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-props.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ module.exports = {
9292
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
9393
'no-reserved-keys': require('./rules/no-reserved-keys'),
9494
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
95+
'no-restricted-props': require('./rules/no-restricted-props'),
9596
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
9697
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
9798
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'),

lib/rules/no-restricted-props.js

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const regexp = require('../utils/regexp')
9+
10+
/**
11+
* @typedef {object} ParsedOption
12+
* @property { (name: string) => boolean } test
13+
* @property {string} [message]
14+
* @property {string} [suggest]
15+
*/
16+
17+
/**
18+
* @param {string} str
19+
* @returns {(str: string) => boolean}
20+
*/
21+
function buildMatcher(str) {
22+
if (regexp.isRegExp(str)) {
23+
const re = regexp.toRegExp(str)
24+
return (s) => {
25+
re.lastIndex = 0
26+
return re.test(s)
27+
}
28+
}
29+
return (s) => s === str
30+
}
31+
/**
32+
* @param {string|{name:string, message?: string, suggest?:string}} option
33+
* @returns {ParsedOption}
34+
*/
35+
function parseOption(option) {
36+
if (typeof option === 'string') {
37+
const matcher = buildMatcher(option)
38+
return {
39+
test(name) {
40+
return matcher(name)
41+
}
42+
}
43+
}
44+
const parsed = parseOption(option.name)
45+
parsed.message = option.message
46+
parsed.suggest = option.suggest
47+
return parsed
48+
}
49+
50+
module.exports = {
51+
meta: {
52+
type: 'suggestion',
53+
docs: {
54+
description: 'disallow specific props',
55+
categories: undefined,
56+
url: 'https://eslint.vuejs.org/rules/no-restricted-props.html'
57+
},
58+
fixable: null,
59+
schema: {
60+
type: 'array',
61+
items: {
62+
oneOf: [
63+
{ type: ['string'] },
64+
{
65+
type: 'object',
66+
properties: {
67+
name: { type: 'string' },
68+
message: { type: 'string', minLength: 1 },
69+
suggest: { type: 'string' }
70+
},
71+
required: ['name'],
72+
additionalProperties: false
73+
}
74+
]
75+
},
76+
uniqueItems: true,
77+
minItems: 0
78+
},
79+
80+
messages: {
81+
// eslint-disable-next-line eslint-plugin/report-message-format
82+
restrictedProp: '{{message}}',
83+
instead: 'Instead, change to `{{suggest}}`.'
84+
}
85+
},
86+
/** @param {RuleContext} context */
87+
create(context) {
88+
/** @type {ParsedOption[]} */
89+
const options = context.options.map(parseOption)
90+
91+
return utils.defineVueVisitor(context, {
92+
onVueObjectEnter(node) {
93+
for (const prop of utils.getComponentProps(node)) {
94+
if (!prop.propName) {
95+
continue
96+
}
97+
98+
for (const option of options) {
99+
if (option.test(prop.propName)) {
100+
const message =
101+
option.message ||
102+
`Using \`${prop.propName}\` props is not allowed.`
103+
context.report({
104+
node: prop.key,
105+
messageId: 'restrictedProp',
106+
data: { message },
107+
suggest: createSuggest(prop.key, option)
108+
})
109+
break
110+
}
111+
}
112+
}
113+
}
114+
})
115+
}
116+
}
117+
118+
/**
119+
* @param {Expression} node
120+
* @param {ParsedOption} option
121+
* @returns {Rule.SuggestionReportDescriptor[]}
122+
*/
123+
function createSuggest(node, option) {
124+
if (!option.suggest) {
125+
return []
126+
}
127+
128+
/** @type {string} */
129+
let replaceText
130+
if (node.type === 'Literal' || node.type === 'TemplateLiteral') {
131+
replaceText = JSON.stringify(option.suggest)
132+
} else if (node.type === 'Identifier') {
133+
replaceText = /^[a-z]\w*$/iu.exec(option.suggest)
134+
? option.suggest
135+
: JSON.stringify(option.suggest)
136+
} else {
137+
return []
138+
}
139+
140+
return [
141+
{
142+
fix(fixer) {
143+
return fixer.replaceText(node, replaceText)
144+
},
145+
messageId: 'instead',
146+
data: { suggest: option.suggest }
147+
}
148+
]
149+
}

0 commit comments

Comments
 (0)