Skip to content

Commit

Permalink
Add vue/no-restricted-props rule (#1376)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored Dec 16, 2020
1 parent 7212c36 commit 27fc097
Show file tree
Hide file tree
Showing 5 changed files with 586 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ For example:
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
| [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | |
| [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute | |
| [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` | |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
Expand Down
104 changes: 104 additions & 0 deletions docs/rules/no-restricted-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-restricted-props
description: disallow specific props
---
# vue/no-restricted-props
> disallow specific props
## :book: Rule Details

This rule allows you to specify props that you don't want to use in your application.

## :wrench: Options

This rule takes a list of strings, where each string is a prop name or pattern to be restricted:

```json
{
"vue/no-restricted-props": ["error", "value", "/^forbidden/"]
}
```

<eslint-code-block :rules="{'vue/no-restricted-props': ['error', 'value', '/^forbidden/']}">

```vue
<script>
export default {
props: {
/* ✗ BAD */
value: String,
forbiddenNum: Number,
/* ✓ GOOD */
foo: {},
bar: {},
arrowedBool: Boolean,
}
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/no-restricted-props': ['error', 'value', '/^forbidden/']}">

```vue
<script>
export default {
props: [
/* ✗ BAD */
'value',
'forbiddenNum',
/* ✓ GOOD */
'foo',
'bar',
'arrowedBool',
]
}
</script>
```

</eslint-code-block>

Alternatively, the rule also accepts objects.

```json
{
"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"
},
]
}
```

The following properties can be specified for the object.

- `name` ... Specify the prop name or pattern.
- `message` ... Specify an optional custom message.
- `suggest` ... Specify an optional name to suggest changes.

<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'}]}">

```vue
<script>
export default {
props: [
/* ✗ BAD */
'value',
]
}
</script>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-props.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-props.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ module.exports = {
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
'no-restricted-props': require('./rules/no-restricted-props'),
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'),
Expand Down
149 changes: 149 additions & 0 deletions lib/rules/no-restricted-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const regexp = require('../utils/regexp')

/**
* @typedef {object} ParsedOption
* @property { (name: string) => boolean } test
* @property {string} [message]
* @property {string} [suggest]
*/

/**
* @param {string} str
* @returns {(str: string) => boolean}
*/
function buildMatcher(str) {
if (regexp.isRegExp(str)) {
const re = regexp.toRegExp(str)
return (s) => {
re.lastIndex = 0
return re.test(s)
}
}
return (s) => s === str
}
/**
* @param {string|{name:string, message?: string, suggest?:string}} option
* @returns {ParsedOption}
*/
function parseOption(option) {
if (typeof option === 'string') {
const matcher = buildMatcher(option)
return {
test(name) {
return matcher(name)
}
}
}
const parsed = parseOption(option.name)
parsed.message = option.message
parsed.suggest = option.suggest
return parsed
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow specific props',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-restricted-props.html'
},
fixable: null,
schema: {
type: 'array',
items: {
oneOf: [
{ type: ['string'] },
{
type: 'object',
properties: {
name: { type: 'string' },
message: { type: 'string', minLength: 1 },
suggest: { type: 'string' }
},
required: ['name'],
additionalProperties: false
}
]
},
uniqueItems: true,
minItems: 0
},

messages: {
// eslint-disable-next-line eslint-plugin/report-message-format
restrictedProp: '{{message}}',
instead: 'Instead, change to `{{suggest}}`.'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {ParsedOption[]} */
const options = context.options.map(parseOption)

return utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
for (const prop of utils.getComponentProps(node)) {
if (!prop.propName) {
continue
}

for (const option of options) {
if (option.test(prop.propName)) {
const message =
option.message ||
`Using \`${prop.propName}\` props is not allowed.`
context.report({
node: prop.key,
messageId: 'restrictedProp',
data: { message },
suggest: createSuggest(prop.key, option)
})
break
}
}
}
}
})
}
}

/**
* @param {Expression} node
* @param {ParsedOption} option
* @returns {Rule.SuggestionReportDescriptor[]}
*/
function createSuggest(node, option) {
if (!option.suggest) {
return []
}

/** @type {string} */
let replaceText
if (node.type === 'Literal' || node.type === 'TemplateLiteral') {
replaceText = JSON.stringify(option.suggest)
} else if (node.type === 'Identifier') {
replaceText = /^[a-z]\w*$/iu.exec(option.suggest)
? option.suggest
: JSON.stringify(option.suggest)
} else {
return []
}

return [
{
fix(fixer) {
return fixer.replaceText(node, replaceText)
},
messageId: 'instead',
data: { suggest: option.suggest }
}
]
}
Loading

0 comments on commit 27fc097

Please sign in to comment.