Skip to content

Commit

Permalink
⭐️New: Add vue/valid-v-bind-sync rule (#647)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ota-meshi committed Dec 26, 2019
1 parent 227ff77 commit 6a78831
Show file tree
Hide file tree
Showing 5 changed files with 477 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -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
Expand Down
70 changes: 70 additions & 0 deletions 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)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -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'),
Expand Down
113 changes: 113 additions & 0 deletions 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 }
})
}
}
}
}
})
}
}

0 comments on commit 6a78831

Please sign in to comment.