Skip to content

Commit 6a78831

Browse files
authored
⭐️New: Add vue/valid-v-bind-sync rule (#647)
* New: Add `vue/valid-v-bind-sync` rule * update doc * update use messageId * Change: null comparison to Boolean call. * add testcase * Update: to use reference.variable * fixed eslint error * update document * revert readme * fix eof * fixed doc * update docs * fixed lint errors * fixed
1 parent 227ff77 commit 6a78831

File tree

5 files changed

+477
-0
lines changed

5 files changed

+477
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ For example:
166166
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
167167
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |
168168
| [vue/v-slot-style](./v-slot-style.md) | enforce `v-slot` directive style | :wrench: |
169+
| [vue/valid-v-bind-sync](./valid-v-bind-sync.md) | enforce valid `.sync` modifier on `v-bind` directives | |
169170
| [vue/valid-v-slot](./valid-v-slot.md) | enforce valid `v-slot` directives | |
170171

171172
## Deprecated

docs/rules/valid-v-bind-sync.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/valid-v-bind-sync
5+
description: enforce valid `.sync` modifier on `v-bind` directives
6+
---
7+
# vue/valid-v-bind-sync
8+
> enforce valid `.sync` modifier on `v-bind` directives
9+
10+
This rule checks whether every `.sync` modifier on `v-bind` directives is valid.
11+
12+
## :book: Rule Details
13+
14+
This rule reports `.sync` modifier on `v-bind` directives in the following cases:
15+
16+
- The `.sync` modifier does not have the attribute value which is valid as LHS. E.g. `<MyComponent v-bind:aaa.sync="foo() + bar()" />`
17+
- The `.sync` modifier is on non Vue-components. E.g. `<input v-bind:aaa.sync="foo"></div>`
18+
- The `.sync` modifier's reference is iteration variables. E.g. `<div v-for="x in list"><MyComponent v-bind:aaa.sync="x" /></div>`
19+
20+
<eslint-code-block :rules="{'vue/valid-v-bind-sync': ['error']}">
21+
22+
```vue
23+
<template>
24+
<!-- ✓ GOOD -->
25+
<MyComponent v-bind:aaa.sync="foo"/>
26+
<MyComponent :aaa.sync="foo"/>
27+
28+
<div v-for="todo in todos">
29+
<MyComponent v-bind:aaa.sync="todo.name"/>
30+
<MyComponent :aaa.sync="todo.name"/>
31+
</div>
32+
33+
<!-- ✗ BAD -->
34+
<MyComponent v-bind:aaa.sync="foo + bar" />
35+
<MyComponent :aaa.sync="foo + bar" />
36+
37+
<input v-bind:aaa.sync="foo">
38+
<input :aaa.sync="foo">
39+
40+
<div v-for="todo in todos">
41+
<MyComponent v-bind:aaa.sync="todo" />
42+
<MyComponent :aaa.sync="todo" />
43+
</div>
44+
</template>
45+
```
46+
47+
</eslint-code-block>
48+
49+
::: warning Note
50+
This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
51+
:::
52+
53+
## :wrench: Options
54+
55+
Nothing.
56+
57+
## :couple: Related rules
58+
59+
- [no-parsing-error]
60+
61+
[no-parsing-error]: no-parsing-error.md
62+
63+
## :books: Further reading
64+
65+
- [Guide - `.sync` Modifier]([https://vuejs.org/v2/guide/list.html#v-for-with-a-Component](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier))
66+
67+
## :mag: Implementation
68+
69+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-bind-sync.js)
70+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-bind-sync.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ module.exports = {
8181
'v-on-style': require('./rules/v-on-style'),
8282
'v-slot-style': require('./rules/v-slot-style'),
8383
'valid-template-root': require('./rules/valid-template-root'),
84+
'valid-v-bind-sync': require('./rules/valid-v-bind-sync'),
8485
'valid-v-bind': require('./rules/valid-v-bind'),
8586
'valid-v-cloak': require('./rules/valid-v-cloak'),
8687
'valid-v-else-if': require('./rules/valid-v-else-if'),

lib/rules/valid-v-bind-sync.js

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* @fileoverview enforce valid `.sync` modifier on `v-bind` directives
3+
* @author Yosuke Ota
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
/**
18+
* Check whether the given node is valid or not.
19+
* @param {ASTNode} node The element node to check.
20+
* @returns {boolean} `true` if the node is valid.
21+
*/
22+
function isValidElement (node) {
23+
if (
24+
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
25+
utils.isHtmlWellKnownElementName(node.rawName) ||
26+
utils.isSvgWellKnownElementName(node.rawName)
27+
) {
28+
// non Vue-component
29+
return false
30+
}
31+
return true
32+
}
33+
34+
/**
35+
* Check whether the given node can be LHS.
36+
* @param {ASTNode} node The node to check.
37+
* @returns {boolean} `true` if the node can be LHS.
38+
*/
39+
function isLhs (node) {
40+
return Boolean(node) && (
41+
node.type === 'Identifier' ||
42+
node.type === 'MemberExpression'
43+
)
44+
}
45+
46+
// ------------------------------------------------------------------------------
47+
// Rule Definition
48+
// ------------------------------------------------------------------------------
49+
50+
module.exports = {
51+
meta: {
52+
type: 'problem',
53+
docs: {
54+
description: 'enforce valid `.sync` modifier on `v-bind` directives',
55+
category: undefined,
56+
url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html'
57+
},
58+
fixable: null,
59+
schema: [],
60+
messages: {
61+
unexpectedInvalidElement: "'.sync' modifiers aren't supported on <{{name}}> non Vue-components.",
62+
unexpectedNonLhsExpression: "'.sync' modifiers require the attribute value which is valid as LHS.",
63+
unexpectedUpdateIterationVariable: "'.sync' modifiers cannot update the iteration variable '{{varName}}' itself."
64+
}
65+
},
66+
67+
create (context) {
68+
return utils.defineTemplateBodyVisitor(context, {
69+
"VAttribute[directive=true][key.name.name='bind']" (node) {
70+
if (!node.key.modifiers.map(mod => mod.name).includes('sync')) {
71+
return
72+
}
73+
const element = node.parent.parent
74+
const name = element.name
75+
76+
if (!isValidElement(element)) {
77+
context.report({
78+
node,
79+
loc: node.loc,
80+
messageId: 'unexpectedInvalidElement',
81+
data: { name }
82+
})
83+
}
84+
85+
if (node.value) {
86+
if (!isLhs(node.value.expression)) {
87+
context.report({
88+
node,
89+
loc: node.loc,
90+
messageId: 'unexpectedNonLhsExpression'
91+
})
92+
}
93+
94+
for (const reference of node.value.references) {
95+
const id = reference.id
96+
if (id.parent.type !== 'VExpressionContainer') {
97+
continue
98+
}
99+
const variable = reference.variable
100+
if (variable) {
101+
context.report({
102+
node,
103+
loc: node.loc,
104+
messageId: 'unexpectedUpdateIterationVariable',
105+
data: { varName: id.name }
106+
})
107+
}
108+
}
109+
}
110+
}
111+
})
112+
}
113+
}

0 commit comments

Comments
 (0)