Skip to content

Commit ef95155

Browse files
authored
Vue3 v-model API changes (#1039)
* feat(RFC0005): allow v-model argument when v-model is used on custom component * feat(RFC0005): add no-v-model-argument rule which disallows v-model arguments. Vue 2 compatibility rule * feat(RFC0005): add no-deprecated-v-bind-sync rule * feat(RFC0005): provide autofix fix for deprecated v-bind.prop.sync modifier * feat(RFC0011): add support for custom modifiers in valid-v-model used on Vue component * feat(RFC0011): add vue/no-custom-modifiers-on-v-model rule which checks if v-model has not allowed modifiers (Vue 2 backward compatibility)
1 parent d8e728a commit ef95155

16 files changed

+638
-14
lines changed

Diff for: docs/rules/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
3939
| Rule ID | Description | |
4040
|:--------|:------------|:---|
4141
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
42+
| [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | |
4243
| [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names | |
4344
| [vue/no-duplicate-attributes](./no-duplicate-attributes.md) | disallow duplication of attributes | |
4445
| [vue/no-multiple-template-root](./no-multiple-template-root.md) | disallow adding multiple root nodes to the template | |
@@ -51,6 +52,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
5152
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
5253
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
5354
| [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow use v-if on the same element as v-for | |
55+
| [vue/no-v-model-argument](./no-v-model-argument.md) | disallow adding an argument to `v-model` used in custom component | |
5456
| [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements | |
5557
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
5658
| [vue/require-render-return](./require-render-return.md) | enforce render function to always return value | |
@@ -161,6 +163,7 @@ For example:
161163
| [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
162164
| [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: |
163165
| [vue/no-deprecated-slot-scope-attribute](./no-deprecated-slot-scope-attribute.md) | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: |
166+
| [vue/no-deprecated-v-bind-sync](./no-deprecated-v-bind-sync.md) | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: |
164167
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
165168
| [vue/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | |
166169
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |

Diff for: docs/rules/no-custom-modifiers-on-v-model.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-custom-modifiers-on-v-model
5+
description: disallow custom modifiers on v-model used on the component
6+
---
7+
# vue/no-custom-modifiers-on-v-model
8+
> disallow custom modifiers on v-model used on the component
9+
10+
- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
11+
12+
This rule checks whether `v-model `used on the component do not have custom modifiers.
13+
14+
## Rule Details
15+
16+
This rule reports `v-model` directives in the following cases:
17+
18+
- The directive used on the component has custom modifiers. E.g. `<MyComponent v-model.aaa="foo" />`
19+
20+
<eslint-code-block :rules="{'vue/no-custom-modifiers-on-v-model': ['error']}">
21+
22+
```vue
23+
<template>
24+
<!-- ✓ GOOD -->
25+
<MyComponent v-model="foo" />
26+
<MyComponent v-model.trim="foo" />
27+
<MyComponent v-model.lazy="foo" />
28+
<MyComponent v-model.number="foo" />
29+
30+
31+
<!-- ✗ BAD -->
32+
<MyComponent v-model.aaa="foo" />
33+
<MyComponent v-model.aaa.bbb="foo" />
34+
35+
</template>
36+
```
37+
38+
</eslint-code-block>
39+
40+
### Options
41+
42+
Nothing.
43+
44+
## :couple: Related rules
45+
46+
- [valid-v-model]
47+
48+
[valid-v-model]: valid-v-model.md
49+
50+
## :mag: Implementation
51+
52+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-custom-modifiers-on-v-model.js)
53+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-custom-modifiers-on-v-model.js)

Diff for: docs/rules/no-deprecated-v-bind-sync.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-deprecated-v-bind-sync
5+
description: disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
6+
---
7+
# vue/no-deprecated-v-bind-sync
8+
> disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
9+
10+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
11+
12+
## :book: Rule Details
13+
14+
This rule reports use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
15+
16+
<eslint-code-block fix :rules="{'vue/no-deprecated-v-bind-sync': ['error']}">
17+
18+
```vue
19+
<template>
20+
<!-- ✓ GOOD -->
21+
<MyComponent v-bind:propName="foo"/>
22+
<MyComponent :propName="foo"/>
23+
24+
25+
<!-- ✗ BAD -->
26+
<MyComponent v-bind:propName.sync="foo"/>
27+
<MyComponent v-bind:[dynamiArg].sync="foo"/>
28+
<MyComponent v-bind.sync="foo"/>
29+
<MyComponent :propName.sync="foo"/>
30+
</template>
31+
```
32+
33+
</eslint-code-block>
34+
35+
## :wrench: Options
36+
37+
Nothing.
38+
39+
## :couple: Related rules
40+
41+
- [valid-v-bind]
42+
43+
[valid-v-bind]: valid-v-bind.md
44+
45+
## :books: Further reading
46+
47+
- [RFC: Replace v-bind.sync with v-model argument](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0005-replace-v-bind-sync-with-v-model-argument.md)
48+
49+
## :mag: Implementation
50+
51+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-bind-sync.js)
52+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-bind-sync.js)

Diff for: docs/rules/no-v-model-argument.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-v-model-argument
5+
description: disallow adding an argument to `v-model` used in custom component
6+
---
7+
# vue/no-v-model-argument
8+
> disallow adding an argument to `v-model` used in custom component
9+
10+
- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
11+
12+
This rule checks whether `v-model` used on custom component do not have an argument.
13+
14+
## :book: Rule Details
15+
16+
This rule reports `v-model` directives in the following cases:
17+
18+
- The directive used on component has an argument. E.g. `<MyComponent v-model:aaa="foo" />`
19+
20+
<eslint-code-block :rules="{'vue/no-v-model-argument': ['error']}">
21+
22+
```vue
23+
<template>
24+
<!-- ✓ GOOD -->
25+
<MyComponent v-model="foo" />
26+
27+
28+
<!-- ✗ BAD -->
29+
<MyComponent v-model:aaa="foo" />
30+
</template>
31+
```
32+
33+
</eslint-code-block>
34+
35+
36+
## :wrench: Options
37+
38+
Nothing.
39+
40+
## :couple: Related rules
41+
42+
- [valid-v-model]
43+
44+
[valid-v-model]: valid-v-model.md
45+
46+
## :mag: Implementation
47+
48+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-model-argument.js)
49+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-model-argument.js)

Diff for: docs/rules/valid-v-bind.md

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ Nothing.
5454

5555
[no-parsing-error]: no-parsing-error.md
5656

57+
- [no-deprecated-v-bind-sync]
58+
59+
[no-deprecated-v-bind-sync]: no-deprecated-v-bind-sync.md
60+
61+
- [valid-v-bind-sync]
62+
63+
[valid-v-bind-sync]: valid-v-bind-sync.md
64+
5765
## :mag: Implementation
5866

5967
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-bind.js)

Diff for: docs/rules/valid-v-model.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ This rule checks whether every `v-model` directive is valid.
1515

1616
This rule reports `v-model` directives in the following cases:
1717

18-
- The directive has that argument. E.g. `<input v-model:aaa="foo">`
19-
- The directive has the modifiers which are not supported. E.g. `<input v-model.bbb="foo">`
18+
- The directive used on HTMLElement has an argument. E.g. `<input v-model:aaa="foo">`
19+
- The directive used on HTMLElement has modifiers which are not supported. E.g. `<input v-model.bbb="foo">`
2020
- The directive does not have that attribute value. E.g. `<input v-model>`
2121
- The directive does not have the attribute value which is valid as LHS. E.g. `<input v-model="foo() + bar()">`
2222
- The directive is on unsupported elements. E.g. `<div v-model="foo"></div>`
@@ -32,6 +32,9 @@ This rule reports `v-model` directives in the following cases:
3232
<input v-model.lazy="foo">
3333
<textarea v-model="foo"/>
3434
<MyComponent v-model="foo"/>
35+
<MyComponent v-model:propName="foo"/>
36+
<MyComponent v-model.modifier="foo"/>
37+
<MyComponent v-model:propName.modifier="foo"/>
3538
<div v-for="todo in todos">
3639
<input v-model="todo.name">
3740
</div>

Diff for: lib/configs/essential.js

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
extends: require.resolve('./base'),
88
rules: {
99
'vue/no-async-in-computed-properties': 'error',
10+
'vue/no-custom-modifiers-on-v-model': 'error',
1011
'vue/no-dupe-keys': 'error',
1112
'vue/no-duplicate-attributes': 'error',
1213
'vue/no-multiple-template-root': 'error',
@@ -19,6 +20,7 @@ module.exports = {
1920
'vue/no-unused-components': 'error',
2021
'vue/no-unused-vars': 'error',
2122
'vue/no-use-v-if-with-v-for': 'error',
23+
'vue/no-v-model-argument': 'error',
2224
'vue/require-component-is': 'error',
2325
'vue/require-prop-type-constructor': 'error',
2426
'vue/require-render-return': 'error',

Diff for: lib/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ module.exports = {
3939
'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'),
4040
'no-boolean-default': require('./rules/no-boolean-default'),
4141
'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
42+
'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'),
4243
'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
4344
'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
4445
'no-deprecated-slot-scope-attribute': require('./rules/no-deprecated-slot-scope-attribute'),
46+
'no-deprecated-v-bind-sync': require('./rules/no-deprecated-v-bind-sync'),
4547
'no-dupe-keys': require('./rules/no-dupe-keys'),
4648
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
4749
'no-empty-pattern': require('./rules/no-empty-pattern'),
@@ -64,6 +66,7 @@ module.exports = {
6466
'no-unused-vars': require('./rules/no-unused-vars'),
6567
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
6668
'no-v-html': require('./rules/no-v-html'),
69+
'no-v-model-argument': require('./rules/no-v-model-argument'),
6770
'object-curly-spacing': require('./rules/object-curly-spacing'),
6871
'order-in-components': require('./rules/order-in-components'),
6972
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),

Diff for: lib/rules/no-custom-modifiers-on-v-model.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @author Przemyslaw Falowski (@przemkow)
3+
* @fileoverview This rule checks whether v-model used on the component do not have custom modifiers
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])
18+
19+
// ------------------------------------------------------------------------------
20+
// Rule Definition
21+
// ------------------------------------------------------------------------------
22+
23+
module.exports = {
24+
meta: {
25+
type: 'problem',
26+
docs: {
27+
description: 'disallow custom modifiers on v-model used on the component',
28+
category: 'essential',
29+
url: 'https://eslint.vuejs.org/rules/no-custom-modifiers-on-v-model.html'
30+
},
31+
fixable: null,
32+
schema: [],
33+
messages: {
34+
notSupportedModifier: "'v-model' directives don't support the modifier '{{name}}'."
35+
}
36+
},
37+
create (context) {
38+
return utils.defineTemplateBodyVisitor(context, {
39+
"VAttribute[directive=true][key.name.name='model']" (node) {
40+
const element = node.parent.parent
41+
42+
if (utils.isCustomComponent(element)) {
43+
for (const modifier of node.key.modifiers) {
44+
if (!VALID_MODIFIERS.has(modifier.name)) {
45+
context.report({
46+
node,
47+
loc: node.loc,
48+
messageId: 'notSupportedModifier',
49+
data: { name: modifier.name }
50+
})
51+
}
52+
}
53+
}
54+
}
55+
})
56+
}
57+
}

Diff for: lib/rules/no-deprecated-v-bind-sync.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @author Przemyslaw Falowski (@przemkow)
3+
* @fileoverview Disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Rule Definition
15+
// ------------------------------------------------------------------------------
16+
17+
module.exports = {
18+
meta: {
19+
type: 'problem',
20+
docs: {
21+
description: 'disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)',
22+
category: undefined,
23+
url: 'https://eslint.vuejs.org/rules/no-deprecated-v-bind-sync.html'
24+
},
25+
fixable: 'code',
26+
schema: [],
27+
messages: {
28+
syncModifierIsDeprecated: "'.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName' instead."
29+
}
30+
},
31+
create (context) {
32+
return utils.defineTemplateBodyVisitor(context, {
33+
"VAttribute[directive=true][key.name.name='bind']" (node) {
34+
if (node.key.modifiers.map(mod => mod.name).includes('sync')) {
35+
context.report({
36+
node,
37+
loc: node.loc,
38+
messageId: 'syncModifierIsDeprecated',
39+
fix: (fixer) => {
40+
const isUsingSpreadSyntax = node.key.argument == null
41+
const hasMultipleModifiers = node.key.modifiers.length > 1
42+
if (isUsingSpreadSyntax || hasMultipleModifiers) {
43+
return
44+
}
45+
46+
const bindArgument = context.getSourceCode().getText(node.key.argument)
47+
return fixer.replaceText(node.key, `v-model:${bindArgument}`)
48+
}
49+
})
50+
}
51+
}
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)