Skip to content

Commit 0aeaaaf

Browse files
authored
Add vue/no-reserved-props rule (#1678)
* Add `vue/no-reserved-props` rule * add 'class' and 'style'
1 parent afec265 commit 0aeaaaf

File tree

10 files changed

+576
-7
lines changed

10 files changed

+576
-7
lines changed

docs/rules/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
7171
| [vue/no-parsing-error](./no-parsing-error.md) | disallow parsing errors in `<template>` | |
7272
| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | :wrench: |
7373
| [vue/no-reserved-keys](./no-reserved-keys.md) | disallow overwriting reserved keys | |
74+
| [vue/no-reserved-props](./no-reserved-props.md) | disallow reserved names in props | |
7475
| [vue/no-setup-props-destructure](./no-setup-props-destructure.md) | disallow destructuring of `props` passed to `setup` | |
7576
| [vue/no-shared-component-data](./no-shared-component-data.md) | enforce component's data property to be a function | :wrench: |
7677
| [vue/no-side-effects-in-computed-properties](./no-side-effects-in-computed-properties.md) | disallow side effects in computed properties | |
@@ -195,6 +196,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
195196
| [vue/no-mutating-props](./no-mutating-props.md) | disallow mutation of component props | |
196197
| [vue/no-parsing-error](./no-parsing-error.md) | disallow parsing errors in `<template>` | |
197198
| [vue/no-reserved-keys](./no-reserved-keys.md) | disallow overwriting reserved keys | |
199+
| [vue/no-reserved-props](./no-reserved-props.md) | disallow reserved names in props | |
198200
| [vue/no-shared-component-data](./no-shared-component-data.md) | enforce component's data property to be a function | :wrench: |
199201
| [vue/no-side-effects-in-computed-properties](./no-side-effects-in-computed-properties.md) | disallow side effects in computed properties | |
200202
| [vue/no-template-key](./no-template-key.md) | disallow `key` attribute on `<template>` | |

docs/rules/no-reserved-props.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-reserved-props
5+
description: disallow reserved names in props
6+
---
7+
# vue/no-reserved-props
8+
9+
> disallow reserved names in props
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+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
13+
14+
## :book: Rule Details
15+
16+
This rule disallow reserved names to be used in props.
17+
18+
<eslint-code-block :rules="{'vue/no-reserved-props': ['error']}">
19+
20+
```vue
21+
<script>
22+
export default {
23+
props: {
24+
/* ✗ BAD */
25+
ref: String,
26+
key: String,
27+
/* ✓ GOOD */
28+
foo: String,
29+
bar: String,
30+
}
31+
}
32+
</script>
33+
```
34+
35+
</eslint-code-block>
36+
37+
## :wrench: Options
38+
39+
```json
40+
{
41+
"vue/no-reserved-props": ["error", {
42+
"vueVersion": 3, // or 2
43+
}]
44+
}
45+
```
46+
47+
- `vueVersion` (`2 | 3`) ... Specify the version of Vue you are using. Default is `3`.
48+
49+
## :mag: Implementation
50+
51+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-reserved-props.js)
52+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-reserved-props.js)

lib/configs/essential.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ module.exports = {
1818
'vue/no-mutating-props': 'error',
1919
'vue/no-parsing-error': 'error',
2020
'vue/no-reserved-keys': 'error',
21+
'vue/no-reserved-props': [
22+
'error',
23+
{
24+
vueVersion: 2
25+
}
26+
],
2127
'vue/no-shared-component-data': 'error',
2228
'vue/no-side-effects-in-computed-properties': 'error',
2329
'vue/no-template-key': 'error',

lib/configs/vue3-essential.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
'vue/no-parsing-error': 'error',
3939
'vue/no-ref-as-operand': 'error',
4040
'vue/no-reserved-keys': 'error',
41+
'vue/no-reserved-props': 'error',
4142
'vue/no-setup-props-destructure': 'error',
4243
'vue/no-shared-component-data': 'error',
4344
'vue/no-side-effects-in-computed-properties': 'error',

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ module.exports = {
103103
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
104104
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
105105
'no-reserved-keys': require('./rules/no-reserved-keys'),
106+
'no-reserved-props': require('./rules/no-reserved-props'),
106107
'no-restricted-block': require('./rules/no-restricted-block'),
107108
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
108109
'no-restricted-class': require('./rules/no-restricted-class'),

lib/rules/no-reserved-props.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
const casing = require('../utils/casing')
13+
14+
/**
15+
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
16+
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
17+
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
18+
*/
19+
20+
// ------------------------------------------------------------------------------
21+
// Helpers
22+
// ------------------------------------------------------------------------------
23+
24+
const RESERVED = {
25+
3: ['key', 'ref'],
26+
2: ['key', 'ref', 'is', 'slot', 'slot-scope', 'slotScope', 'class', 'style']
27+
}
28+
29+
// ------------------------------------------------------------------------------
30+
// Rule Definition
31+
// ------------------------------------------------------------------------------
32+
33+
module.exports = {
34+
meta: {
35+
type: 'problem',
36+
docs: {
37+
description: 'disallow reserved names in props',
38+
categories: ['vue3-essential', 'essential'],
39+
url: 'https://eslint.vuejs.org/rules/no-reserved-props.html',
40+
defaultOptions: {
41+
vue2: [{ vueVersion: 2 }]
42+
}
43+
},
44+
fixable: null,
45+
schema: [
46+
{
47+
type: 'object',
48+
properties: {
49+
vueVersion: {
50+
enum: [2, 3]
51+
}
52+
},
53+
additionalProperties: false
54+
}
55+
],
56+
messages: {
57+
reserved:
58+
"'{{propName}}' is a reserved attribute and cannot be used as props."
59+
}
60+
},
61+
/** @param {RuleContext} context */
62+
create(context) {
63+
const options = context.options[0] || {}
64+
/** @type {2|3} */
65+
const vueVersion = options.vueVersion || 3
66+
67+
const reserved = new Set(RESERVED[vueVersion])
68+
69+
/**
70+
* @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props
71+
*/
72+
function processProps(props) {
73+
for (const prop of props) {
74+
if (prop.propName != null && reserved.has(prop.propName)) {
75+
context.report({
76+
node: prop.node,
77+
messageId: `reserved`,
78+
data: {
79+
propName: casing.kebabCase(prop.propName)
80+
}
81+
})
82+
}
83+
}
84+
}
85+
86+
return utils.compositingVisitors(
87+
utils.defineScriptSetupVisitor(context, {
88+
onDefinePropsEnter(_node, props) {
89+
processProps(props)
90+
}
91+
}),
92+
utils.executeOnVue(context, (obj) => {
93+
processProps(utils.getComponentProps(obj))
94+
})
95+
)
96+
}
97+
}

lib/utils/ts-ast-utils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ function* extractRuntimeProps(context, node) {
122122
for (const m of members) {
123123
if (
124124
(m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
125-
m.key.type === 'Identifier'
125+
(m.key.type === 'Identifier' || m.key.type === 'Literal')
126126
) {
127127
let type
128128
if (m.type === 'TSMethodSignature') {
@@ -132,8 +132,8 @@ function* extractRuntimeProps(context, node) {
132132
}
133133
yield {
134134
type: 'type',
135-
key: /** @type {Identifier} */ (m.key),
136-
propName: m.key.name,
135+
key: /** @type {Identifier | Literal} */ (m.key),
136+
propName: m.key.type === 'Identifier' ? m.key.name : `${m.key.value}`,
137137
value: null,
138138
node: /** @type {TSPropertySignature | TSMethodSignature} */ (m),
139139

0 commit comments

Comments
 (0)