Skip to content

Commit eddf098

Browse files
authored
Add vue/no-deprecated-model-definition rule (#2238)
1 parent 52a9966 commit eddf098

File tree

5 files changed

+395
-0
lines changed

5 files changed

+395
-0
lines changed

docs/rules/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ For example:
226226
| [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: |
227227
| [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` | | :hammer: |
228228
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | :hammer: |
229+
| [vue/no-deprecated-model-definition](./no-deprecated-model-definition.md) | disallow deprecated `model` definition (in Vue.js 3.0.0+) | :bulb: | :warning: |
229230
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | | :hammer: |
230231
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | | :hammer: |
231232
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | | :hammer: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-deprecated-model-definition
5+
description: disallow deprecated `model` definition (in Vue.js 3.0.0+)
6+
---
7+
# vue/no-deprecated-model-definition
8+
9+
> disallow deprecated `model` definition (in Vue.js 3.0.0+)
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 reports use of the component `model` option, which has been deprecated in Vue.js 3.0.0+.
17+
18+
See [Migration Guide – `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html) for more details.
19+
20+
<eslint-code-block :rules="{'vue/no-deprecated-model-definition': ['error']}">
21+
22+
```vue
23+
<script>
24+
export default defineComponent({
25+
model: {
26+
prop: 'my-value',
27+
event: 'input'
28+
}
29+
})
30+
</script>
31+
```
32+
33+
</eslint-code-block>
34+
35+
## :wrench: Options
36+
37+
```json
38+
{
39+
"vue/no-deprecated-model-definition": ["error", {
40+
"allowVue3Compat": true
41+
}]
42+
}
43+
```
44+
45+
### `"allowVue3Compat": true`
46+
47+
Allow `model` definitions with prop/event names that match the Vue.js 3.0.0+ `v-model` syntax, e.g. `fooBar`/`update:fooBar`.
48+
49+
<eslint-code-block :rules="{'vue/no-deprecated-model-definition': ['error', { allowVue3Compat: true }]}">
50+
51+
```vue
52+
<script>
53+
export default defineComponent({
54+
model: {
55+
prop: 'fooBar',
56+
event: 'update:fooBar'
57+
}
58+
})
59+
</script>
60+
```
61+
62+
</eslint-code-block>
63+
64+
## :couple: Related Rules
65+
66+
- [vue/valid-model-definition](./valid-model-definition.md) (for Vue.js 2.x)
67+
- [vue/no-v-model-argument](./no-v-model-argument.md) (for Vue.js 2.x)
68+
69+
## :books: Further Reading
70+
71+
- [Migration Guide – `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html)
72+
73+
## :mag: Implementation
74+
75+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-model-definition.js)
76+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-model-definition.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ module.exports = {
7979
'no-deprecated-functional-template': require('./rules/no-deprecated-functional-template'),
8080
'no-deprecated-html-element-is': require('./rules/no-deprecated-html-element-is'),
8181
'no-deprecated-inline-template': require('./rules/no-deprecated-inline-template'),
82+
'no-deprecated-model-definition': require('./rules/no-deprecated-model-definition'),
8283
'no-deprecated-props-default-this': require('./rules/no-deprecated-props-default-this'),
8384
'no-deprecated-router-link-tag-prop': require('./rules/no-deprecated-router-link-tag-prop'),
8485
'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @author Flo Edelmann
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/**
10+
* @param {RuleContext} context
11+
* @param {ASTNode} node
12+
*/
13+
function reportWithoutSuggestion(context, node) {
14+
context.report({
15+
node,
16+
messageId: 'deprecatedModel'
17+
})
18+
}
19+
20+
/**
21+
* @param {ObjectExpression} node
22+
* @param {string} key
23+
* @returns {Literal | undefined}
24+
*/
25+
function findPropertyValue(node, key) {
26+
const property = node.properties.find(
27+
(property) =>
28+
property.type === 'Property' &&
29+
property.key.type === 'Identifier' &&
30+
property.key.name === key
31+
)
32+
if (
33+
!property ||
34+
property.type !== 'Property' ||
35+
property.value.type !== 'Literal'
36+
) {
37+
return undefined
38+
}
39+
return property.value
40+
}
41+
42+
module.exports = {
43+
meta: {
44+
type: 'problem',
45+
docs: {
46+
description: 'disallow deprecated `model` definition (in Vue.js 3.0.0+)',
47+
categories: undefined,
48+
url: 'https://eslint.vuejs.org/rules/no-deprecated-model-definition.html'
49+
},
50+
fixable: null,
51+
hasSuggestions: true,
52+
schema: [
53+
{
54+
type: 'object',
55+
additionalProperties: false,
56+
properties: {
57+
allowVue3Compat: {
58+
type: 'boolean'
59+
}
60+
}
61+
}
62+
],
63+
messages: {
64+
deprecatedModel: '`model` definition is deprecated.',
65+
renameEvent: 'Rename event to `{{expectedEventName}}`.'
66+
}
67+
},
68+
/** @param {RuleContext} context */
69+
create(context) {
70+
const allowVue3Compat = Boolean(context.options[0]?.allowVue3Compat)
71+
72+
return utils.executeOnVue(context, (obj) => {
73+
const modelProperty = utils.findProperty(obj, 'model')
74+
if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
75+
return
76+
}
77+
78+
if (!allowVue3Compat) {
79+
reportWithoutSuggestion(context, modelProperty)
80+
return
81+
}
82+
83+
const propName = findPropertyValue(modelProperty.value, 'prop')
84+
const eventName = findPropertyValue(modelProperty.value, 'event')
85+
86+
if (!propName || !eventName) {
87+
reportWithoutSuggestion(context, modelProperty)
88+
return
89+
}
90+
91+
const expectedEventName = `update:${propName.value}`
92+
if (eventName.value !== expectedEventName) {
93+
context.report({
94+
node: modelProperty,
95+
messageId: 'deprecatedModel',
96+
suggest: [
97+
{
98+
messageId: 'renameEvent',
99+
data: { expectedEventName },
100+
fix(fixer) {
101+
return fixer.replaceTextRange(
102+
[eventName.range[0] + 1, eventName.range[1] - 1],
103+
expectedEventName
104+
)
105+
}
106+
}
107+
]
108+
})
109+
}
110+
})
111+
}
112+
}

0 commit comments

Comments
 (0)