Repository: vuejs/eslint-plugin-vue (js)

Issue:
Rule Proposal: `valid-v-bind-sync`
**Please describe what the rule should do:**

This rule checks `.sync` modifier with `v-bind`.
Most checking of this rule will do the same check as `vue/valid-v-model`.

[Custom Events `.sync` Modifier](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)

**What category of rule is this? (place an "X" next to just one item)**

[ ] Enforces code style
[X] Warns about a potential error
[ ] Suggests an alternate way of doing something
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule will warn about:**

```vue
<TheComponent v-bind:title.sync="foo + bar" />
<input v-bind:title.sync="foo"/>
<div v-for="todo in todos">
  <TheComponent v-bind:title.sync="todo" />
</div>
```

**Why should this rule be included?**

The `.sync` modifier behaves similarly to v-model, so the same problem may get into it.

I would like to ask your opinion whether this check should be a separate rule or included in `valid-v-bind`.


Pull request:
⭐️New: Add `vue/valid-v-bind-sync` rule
This PR add `vue/valid-v-bind-sync` rule. (#639)

`vue/valid-v-bind-sync` 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. `<MyComponent v-bind:aaa.sync="foo() + bar()" />`
- The `.sync` modifier is on non Vue-components. E.g. `<input v-bind:aaa.sync="foo"></div>`
- The `.sync` modifier's reference is iteration variables. E.g. `<div v-for="x in list"><MyComponent v-bind:aaa.sync="x" /></div>`

Close #639





Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 481f3909..f45d9c68 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/component-definition-name-casing.md b/docs/rules/component-definition-name-casing.md
new file mode 100644
index 00000000..8bc3f730
--- /dev/null
+++ b/docs/rules/component-definition-name-casing.md
@@ -0,0 +1,47 @@
+# enforce specific casing for component definition name (vue/component-definition-name-casing)
+
+- :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.
+
+Define a style for component definition name casing for consistency purposes.
+
+## :book: Rule Details
+
+:+1: Examples of **correct** code for `PascalCase`:
+
+```js
+export default {
+  name: 'MyComponent'
+}
+```
+```js
+Vue.component('MyComponent', {
+  
+})
+```
+
+:+1: Examples of **correct** code for `kebab-case`:
+
+```js
+export default {
+  name: 'my-component'
+}
+```
+```js
+Vue.component('my-component', {
+  
+})
+```
+
+## :wrench: Options
+
+Default casing is set to `PascalCase`.
+
+```json
+{
+  "vue/component-definition-name-casing": ["error", "PascalCase|kebab-case"]
+}
+```
+
+## Related links
+
+- [Style guide - Component name casing in JS/JSX](https://vuejs.org/v2/style-guide/#Component-name-casing-in-JS-JSX-strongly-recommended)
diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md
index 98aa4a26..30630e79 100644
--- a/docs/rules/name-property-casing.md
+++ b/docs/rules/name-property-casing.md
@@ -9,6 +9,7 @@ description: enforce specific casing for the name property in Vue components
 
 - :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
 - :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.
+- :warning: This rule was **deprecated** and replaced by [vue/component-definition-name-casing](component-definition-name-casing.md) rule.
 
 ## :book: Rule Details
 
diff --git a/docs/rules/valid-v-bind-sync.md b/docs/rules/valid-v-bind-sync.md
new file mode 100644
index 00000000..6e8d9504
--- /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. `<MyComponent v-bind:aaa.sync="foo() + bar()" />`
+- The `.sync` modifier is on non Vue-components. E.g. `<input v-bind:aaa.sync="foo"></div>`
+- The `.sync` modifier's reference is iteration variables. E.g. `<div v-for="x in list"><MyComponent v-bind:aaa.sync="x" /></div>`
+
+<eslint-code-block :rules="{'vue/valid-v-bind-sync': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <MyComponent v-bind:aaa.sync="foo"/>
+  <MyComponent :aaa.sync="foo"/>
+
+  <div v-for="todo in todos">
+    <MyComponent v-bind:aaa.sync="todo.name"/>
+    <MyComponent :aaa.sync="todo.name"/>
+  </div>
+
+  <!-- ✗ BAD -->
+  <MyComponent v-bind:aaa.sync="foo + bar" />
+  <MyComponent :aaa.sync="foo + bar" />
+
+  <input v-bind:aaa.sync="foo">
+  <input :aaa.sync="foo">
+
+  <div v-for="todo in todos">
+    <MyComponent v-bind:aaa.sync="todo" />
+    <MyComponent :aaa.sync="todo" />
+  </div>
+</template>
+```
+
+</eslint-code-block>
+
+::: 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 28343da6..9b6c3469 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/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js
new file mode 100644
index 00000000..bb8e46a0
--- /dev/null
+++ b/lib/rules/component-definition-name-casing.js
@@ -0,0 +1,102 @@
+/**
+ * @fileoverview enforce specific casing for component definition name
+ * @author Armano
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+const allowedCaseOptions = ['PascalCase', 'kebab-case']
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'enforce specific casing for component definition name',
+      category: undefined,
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.5/docs/rules/component-definition-name-casing.md'
+    },
+    fixable: 'code', // or "code" or "whitespace"
+    schema: [
+      {
+        enum: allowedCaseOptions
+      }
+    ]
+  },
+
+  create (context) {
+    const options = context.options[0]
+    const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase'
+
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+
+    function convertName (node) {
+      let nodeValue
+      let range
+      if (node.type === 'TemplateLiteral') {
+        const quasis = node.quasis[0]
+        nodeValue = quasis.value.cooked
+        range = quasis.range
+      } else {
+        nodeValue = node.value
+        range = node.range
+      }
+
+      const value = casing.getConverter(caseType)(nodeValue)
+      if (value !== nodeValue) {
+        context.report({
+          node: node,
+          message: 'Property name "{{value}}" is not {{caseType}}.',
+          data: {
+            value: nodeValue,
+            caseType: caseType
+          },
+          fix: fixer => fixer.replaceTextRange([range[0] + 1, range[1] - 1], value)
+        })
+      }
+    }
+
+    function canConvert (node) {
+      return node.type === 'Literal' || (
+        node.type === 'TemplateLiteral' &&
+        node.expressions.length === 0 &&
+        node.quasis.length === 1
+      )
+    }
+
+    return Object.assign({},
+      {
+        "CallExpression > MemberExpression > Identifier[name='component']" (node) {
+          const parent = node.parent.parent
+          const calleeObject = utils.unwrapTypes(parent.callee.object)
+
+          if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') {
+            if (parent.arguments && parent.arguments.length === 2) {
+              const argument = parent.arguments[0]
+              if (canConvert(argument)) {
+                convertName(argument)
+              }
+            }
+          }
+        }
+      },
+      utils.executeOnVue(context, (obj) => {
+        const node = obj.properties
+          .find(item => (
+            item.type === 'Property' &&
+            item.key.name === 'name' &&
+            canConvert(item.value)
+          ))
+
+        if (!node) return
+        convertName(node.value)
+      })
+    )
+  }
+}
diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js
index 6af706df..7f545447 100644
--- a/lib/rules/name-property-casing.js
+++ b/lib/rules/name-property-casing.js
@@ -20,6 +20,8 @@ module.exports = {
       category: 'strongly-recommended',
       url: 'https://eslint.vuejs.org/rules/name-property-casing.html'
     },
+    deprecated: true,
+    replacedBy: ['component-definition-name-casing'],
     fixable: 'code', // or "code" or "whitespace"
     schema: [
       {
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
new file mode 100644
index 00000000..727d0390
--- /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 }
+              })
+            }
+          }
+        }
+      }
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/component-definition-name-casing.js b/tests/lib/rules/component-definition-name-casing.js
new file mode 100644
index 00000000..c98c82c9
--- /dev/null
+++ b/tests/lib/rules/component-definition-name-casing.js
@@ -0,0 +1,349 @@
+/**
+ * @fileoverview enforce specific casing for component definition name
+ * @author Armano
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/component-definition-name-casing')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const parserOptions = {
+  ecmaVersion: 2018,
+  sourceType: 'module'
+}
+
+const ruleTester = new RuleTester()
+ruleTester.run('component-definition-name-casing', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          ...name
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      options: ['PascalCase'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo-bar'
+        }
+      `,
+      options: ['kebab-case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('FooBar', {})`,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('FooBar', {})`,
+      options: ['PascalCase'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('foo-bar', {})`,
+      options: ['kebab-case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component(fooBar, {})`,
+      options: ['kebab-case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('FooBar', component)`,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('FooBar', component)`,
+      options: ['PascalCase'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('foo-bar', component)`,
+      options: ['kebab-case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component(fooBar, component)`,
+      options: ['kebab-case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.mixin({})`,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `foo({})`,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `foo('foo-bar', {})`,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component(\`fooBar\${foo}\`, component)`,
+      options: ['kebab-case'],
+      parserOptions
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo-bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo-bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo  bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo  bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo!bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo!bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.js',
+      code: `
+        new Vue({
+          name: 'foo!bar'
+        })
+      `,
+      output: `
+        new Vue({
+          name: 'FooBar'
+        })
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      errors: [{
+        message: 'Property name "foo!bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo_bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo_bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'FooBar'
+        }
+      `,
+      options: ['PascalCase'],
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not PascalCase.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          name: 'foo_bar'
+        }
+      `,
+      output: `
+        export default {
+          name: 'foo-bar'
+        }
+      `,
+      options: ['kebab-case'],
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not kebab-case.',
+        type: 'Literal',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('foo-bar', component)`,
+      output: `Vue.component('FooBar', component)`,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo-bar" is not PascalCase.',
+        type: 'Literal',
+        line: 1
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `(Vue as VueConstructor<Vue>).component('foo-bar', component)`,
+      output: `(Vue as VueConstructor<Vue>).component('FooBar', component)`,
+      parserOptions,
+      parser: 'typescript-eslint-parser',
+      errors: [{
+        message: 'Property name "foo-bar" is not PascalCase.',
+        type: 'Literal',
+        line: 1
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('foo-bar', {})`,
+      output: `Vue.component('FooBar', {})`,
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo-bar" is not PascalCase.',
+        type: 'Literal',
+        line: 1
+      }]
+    },
+    {
+      filename: 'test.js',
+      code: `Vue.component('foo_bar', {})`,
+      output: `Vue.component('FooBar', {})`,
+      options: ['PascalCase'],
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not PascalCase.',
+        type: 'Literal',
+        line: 1
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component('foo_bar', {})`,
+      output: `Vue.component('foo-bar', {})`,
+      options: ['kebab-case'],
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not kebab-case.',
+        type: 'Literal',
+        line: 1
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `Vue.component(\`foo_bar\`, {})`,
+      output: `Vue.component(\`foo-bar\`, {})`,
+      options: ['kebab-case'],
+      parserOptions,
+      errors: [{
+        message: 'Property name "foo_bar" is not kebab-case.',
+        type: 'TemplateLiteral',
+        line: 1
+      }]
+    }
+  ]
+})
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 00000000..97fb33e9
--- /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: '<template><MyComponent :foo.sync="foo" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent v-bind:foo.sync="foo" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent :foo.sync="foo.bar" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent v-bind.sync="foo" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><svg><MyComponent :foo.sync="foo" /></svg></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent :foo.sync="this.foo().bar" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><svg><MyComponent :foo.sync="this.foo().bar" /></svg></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="x.foo" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[x]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[x - 1]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[`${x}`]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[`prefix_${x}`]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[x ? x : \'_\']" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[x || \'_\']" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[x()]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[/r/.match(x) ? 0 : 1]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[typeof x]" /></div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><div v-for="x in list"><MyComponent :foo.sync="foo[tag`${x}`]" /></div></div></template>'
+    },
+    // not .sync
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent :foo="a + b" /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent v-bind:foo="a + b" /></template>'
+    },
+    // does not report
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent :foo.sync /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent v-bind:foo.sync /></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent :foo.sync.unknown="foo" /></template>'
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <MyComponent :foo.sync="a + b" />
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+        line: 3,
+        column: 24,
+        endColumn: 41
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <MyComponent v-bind:foo.sync="a + b" />
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+        line: 3,
+        column: 24,
+        endColumn: 47
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <input :foo.sync="foo">
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers aren't supported on <input> non Vue-components.",
+        line: 3,
+        column: 18,
+        endColumn: 33
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <MyComponent :foo.sync="foo()" />
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+        line: 3,
+        column: 24,
+        endColumn: 41
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <MyComponent :foo.sync="this.foo()" />
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers require the attribute value which is valid as LHS.",
+        line: 3,
+        column: 24,
+        endColumn: 46
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <input v-bind:foo.sync="foo">
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers aren't supported on <input> non Vue-components.",
+        line: 3,
+        column: 18,
+        endColumn: 39
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="x in list">
+            <MyComponent :foo.sync="x" />
+          </div>
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers cannot update the iteration variable 'x' itself.",
+        line: 4,
+        column: 26,
+        endColumn: 39
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="e in list">
+            <MyComponent v-bind:foo.sync="e" />
+          </div>
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers cannot update the iteration variable 'e' itself.",
+        line: 4,
+        column: 26,
+        endColumn: 45
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="e1 in list1">
+            <div><div>
+              <div v-for="e2 in list2">
+                <MyComponent v-bind:foo.sync="e1" />
+              </div>
+            </div></div>
+          </div>
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers cannot update the iteration variable 'e1' itself.",
+        line: 6
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="(e, index) in list">
+            <MyComponent v-bind:foo.sync="index" />
+          </div>
+        </template>
+      `,
+      errors: [{
+        message: "'.sync' modifiers cannot update the iteration variable 'index' itself.",
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div :foo.sync="foo"></div>
+        </template>
+      `,
+      errors: ["'.sync' modifiers aren't supported on <div> non Vue-components."]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><MyComponent v-bind.sync="foo()" /></template>',
+      errors: ["'.sync' modifiers require the attribute value which is valid as LHS."]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><svg><MyComponent :foo.sync="foo()" /></svg></template>',
+      errors: ["'.sync' modifiers require the attribute value which is valid as LHS."]
+    }
+  ]
+})