Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |
| :wrench: | [vue/singleline-html-element-content-newline](./docs/rules/singleline-html-element-content-newline.md) | require a line break before and after the contents of a singleline element |
| | [vue/use-v-on-exact](./docs/rules/use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` |
| | [vue/valid-v-bind-sync](./docs/rules/valid-v-bind-sync.md) | enforce valid `.sync` modifier on `v-bind` directives |

### Deprecated

Expand Down
50 changes: 50 additions & 0 deletions docs/rules/valid-v-bind-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# enforce valid `.sync` modifier on `v-bind` directives (vue/valid-v-bind-sync)

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>`

This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.

:-1: Examples of **incorrect** code for this rule:

```vue
<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>
```

:+1: Examples of **correct** code for this rule:

```vue
<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>
```

## :wrench: Options

Nothing.

## :couple: Related rules

- [no-parsing-error]

[no-parsing-error]: no-parsing-error.md
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module.exports = {
'v-bind-style': require('./rules/v-bind-style'),
'v-on-style': require('./rules/v-on-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
136 changes: 136 additions & 0 deletions lib/rules/valid-v-bind-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* @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'
)
}

/**
* Get the variable by names.
* @param {string} name The variable name to find.
* @param {ASTNode} leafNode The node to look up.
* @returns {Variable|null} The found variable or null.
*/
function getVariable (name, leafNode) {
let node = leafNode

while (node) {
const variables = node.variables
const variable = variables && variables.find(v => v.id.name === name)

if (variable) {
return variable
}

node = node.parent
}

return null
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'enforce valid `.sync` modifier on `v-bind` directives',
category: undefined,
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-bind-sync.md'
},
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='bind']" (node) {
if (node.key.modifiers.indexOf('sync') < 0) {
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 = getVariable(id.name, element)
if (variable) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedUpdateIterationVariable',
data: { varName: id.name }
})
}
}
}
}
})
}
}
Loading