diff --git a/docs/rules/README.md b/docs/rules/README.md
index 481f39099..f45d9c68a 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -166,6 +166,7 @@ For example:
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |
| [vue/v-slot-style](./v-slot-style.md) | enforce `v-slot` directive style | :wrench: |
+| [vue/valid-v-bind-sync](./valid-v-bind-sync.md) | enforce valid `.sync` modifier on `v-bind` directives | |
| [vue/valid-v-slot](./valid-v-slot.md) | enforce valid `v-slot` directives | |
## Deprecated
diff --git a/docs/rules/valid-v-bind-sync.md b/docs/rules/valid-v-bind-sync.md
new file mode 100644
index 000000000..6e8d9504e
--- /dev/null
+++ b/docs/rules/valid-v-bind-sync.md
@@ -0,0 +1,70 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-bind-sync
+description: enforce valid `.sync` modifier on `v-bind` directives
+---
+# vue/valid-v-bind-sync
+> enforce valid `.sync` modifier on `v-bind` directives
+
+This rule checks whether every `.sync` modifier on `v-bind` directives is valid.
+
+## :book: Rule Details
+
+This rule reports `.sync` modifier on `v-bind` directives in the following cases:
+
+- The `.sync` modifier does not have the attribute value which is valid as LHS. E.g. ` `
+- The `.sync` modifier is on non Vue-components. E.g. ` `
+- The `.sync` modifier's reference is iteration variables. E.g. `
`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+:::
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related rules
+
+- [no-parsing-error]
+
+[no-parsing-error]: no-parsing-error.md
+
+## :books: Further reading
+
+- [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))
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-bind-sync.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-bind-sync.js)
diff --git a/lib/index.js b/lib/index.js
index 28343da6d..9b6c34693 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -81,6 +81,7 @@ module.exports = {
'v-on-style': require('./rules/v-on-style'),
'v-slot-style': require('./rules/v-slot-style'),
'valid-template-root': require('./rules/valid-template-root'),
+ 'valid-v-bind-sync': require('./rules/valid-v-bind-sync'),
'valid-v-bind': require('./rules/valid-v-bind'),
'valid-v-cloak': require('./rules/valid-v-cloak'),
'valid-v-else-if': require('./rules/valid-v-else-if'),
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
new file mode 100644
index 000000000..727d0390e
--- /dev/null
+++ b/lib/rules/valid-v-bind-sync.js
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview enforce valid `.sync` modifier on `v-bind` directives
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Check whether the given node is valid or not.
+ * @param {ASTNode} node The element node to check.
+ * @returns {boolean} `true` if the node is valid.
+ */
+function isValidElement (node) {
+ if (
+ (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
+ utils.isHtmlWellKnownElementName(node.rawName) ||
+ utils.isSvgWellKnownElementName(node.rawName)
+ ) {
+ // non Vue-component
+ return false
+ }
+ return true
+}
+
+/**
+ * Check whether the given node can be LHS.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node can be LHS.
+ */
+function isLhs (node) {
+ return Boolean(node) && (
+ node.type === 'Identifier' ||
+ node.type === 'MemberExpression'
+ )
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `.sync` modifier on `v-bind` directives',
+ category: undefined,
+ url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedInvalidElement: "'.sync' modifiers aren't supported on <{{name}}> non Vue-components.",
+ unexpectedNonLhsExpression: "'.sync' modifiers require the attribute value which is valid as LHS.",
+ unexpectedUpdateIterationVariable: "'.sync' modifiers cannot update the iteration variable '{{varName}}' itself."
+ }
+ },
+
+ create (context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='bind']" (node) {
+ if (!node.key.modifiers.map(mod => mod.name).includes('sync')) {
+ return
+ }
+ const element = node.parent.parent
+ const name = element.name
+
+ if (!isValidElement(element)) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpectedInvalidElement',
+ data: { name }
+ })
+ }
+
+ if (node.value) {
+ if (!isLhs(node.value.expression)) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpectedNonLhsExpression'
+ })
+ }
+
+ for (const reference of node.value.references) {
+ const id = reference.id
+ if (id.parent.type !== 'VExpressionContainer') {
+ continue
+ }
+ const variable = reference.variable
+ if (variable) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpectedUpdateIterationVariable',
+ data: { varName: id.name }
+ })
+ }
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/valid-v-bind-sync.js b/tests/lib/rules/valid-v-bind-sync.js
new file mode 100644
index 000000000..97fb33e9d
--- /dev/null
+++ b/tests/lib/rules/valid-v-bind-sync.js
@@ -0,0 +1,292 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/valid-v-bind-sync')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+ parser: 'vue-eslint-parser',
+ parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('valid-v-bind-sync', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ // not .sync
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ // does not report
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+ line: 3,
+ column: 24,
+ endColumn: 41
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+ line: 3,
+ column: 24,
+ endColumn: 47
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers aren't supported on non Vue-components.",
+ line: 3,
+ column: 18,
+ endColumn: 33
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+ line: 3,
+ column: 24,
+ endColumn: 41
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+ line: 3,
+ column: 24,
+ endColumn: 46
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers aren't supported on non Vue-components.",
+ line: 3,
+ column: 18,
+ endColumn: 39
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers cannot update the iteration variable 'x' itself.",
+ line: 4,
+ column: 26,
+ endColumn: 39
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers cannot update the iteration variable 'e' itself.",
+ line: 4,
+ column: 26,
+ endColumn: 45
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers cannot update the iteration variable 'e1' itself.",
+ line: 6
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [{
+ message: "'.sync' modifiers cannot update the iteration variable 'index' itself.",
+ line: 4
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: ["'.sync' modifiers aren't supported on non Vue-components."]
+ },
+ {
+ filename: 'test.vue',
+ code: ' ',
+ errors: ["'.sync' modifiers require the attribute value which is valid as LHS."]
+ },
+ {
+ filename: 'test.vue',
+ code: ' ',
+ errors: ["'.sync' modifiers require the attribute value which is valid as LHS."]
+ }
+ ]
+})