Skip to content

Commit 6f8acee

Browse files
authored
Fixed wrong autofix in vue/v-on-function-call rule and ignoreIncludesComment option to vue/v-on-function-call rule (vuejs#1204)
1 parent 6def98a commit 6f8acee

File tree

4 files changed

+285
-51
lines changed

4 files changed

+285
-51
lines changed

docs/rules/v-on-function-call.md

+33-2
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,19 @@ Default is set to `never`.
3737

3838
```json
3939
{
40-
"vue/v-on-function-call": ["error", "always"|"never"]
40+
"vue/v-on-function-call": ["error",
41+
"always"|"never",
42+
{
43+
"ignoreIncludesComment": false
44+
}
45+
]
4146
}
4247
```
4348

49+
- `"always"` ... Always use parentheses in `v-on` directives.
50+
- `"never"` ... Never use parentheses in `v-on` directives for method calls without arguments. this is default.
51+
- `ignoreIncludesComment` ... If `true`, do not report expressions containing comments. default `false`.
52+
4453
### `"always"` - Always use parentheses in `v-on` directives
4554

4655
<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error', 'always']}">
@@ -63,7 +72,6 @@ Default is set to `never`.
6372

6473
### `"never"` - Never use parentheses in `v-on` directives for method calls without arguments
6574

66-
6775
<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error', 'never']}">
6876

6977
```vue
@@ -85,6 +93,29 @@ Default is set to `never`.
8593

8694
</eslint-code-block>
8795

96+
### `"never", { "ignoreIncludesComment": true }`
97+
98+
<eslint-code-block fix :rules="{'vue/v-on-function-call': ['error', 'never', {ignoreIncludesComment: true}]}">
99+
100+
```vue
101+
<template>
102+
<!-- ✓ GOOD -->
103+
<button v-on:click="closeModal">
104+
Close
105+
</button>
106+
<button v-on:click="closeModal() /* comment */">
107+
Close
108+
</button>
109+
110+
<!-- ✗ BAD -->
111+
<button v-on:click="closeModal()">
112+
Close
113+
</button>
114+
</template>
115+
```
116+
117+
</eslint-code-block>
118+
88119
## :mag: Implementation
89120

90121
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-function-call.js)

lib/rules/no-restricted-v-bind.js

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* @author Yosuke Ota
33
* See LICENSE file in root directory for full license.
44
*/
5-
// @ts-check
65
'use strict'
76

87
const utils = require('../utils')

lib/rules/v-on-function-call.js

+120-48
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@
99

1010
const utils = require('../utils')
1111

12+
/**
13+
* @typedef {import('vue-eslint-parser').AST.VOnExpression} VOnExpression
14+
* @typedef {import('vue-eslint-parser').AST.Token} Token
15+
* @typedef {import('vue-eslint-parser').AST.ESLintExpressionStatement} ExpressionStatement
16+
* @typedef {import('vue-eslint-parser').AST.ESLintCallExpression} CallExpression
17+
*/
18+
1219
// ------------------------------------------------------------------------------
1320
// Helpers
1421
// ------------------------------------------------------------------------------
1522

1623
/**
17-
* Check whether the given token is a left parenthesis.
24+
* Check whether the given token is a quote.
1825
* @param {Token} token The token to check.
19-
* @returns {boolean} `true` if the token is a left parenthesis.
26+
* @returns {boolean} `true` if the token is a quote.
2027
*/
21-
function isLeftParen(token) {
22-
return token != null && token.type === 'Punctuator' && token.value === '('
28+
function isQuote(token) {
29+
return (
30+
token != null &&
31+
token.type === 'Punctuator' &&
32+
(token.value === '"' || token.value === "'")
33+
)
2334
}
2435

2536
// ------------------------------------------------------------------------------
@@ -36,64 +47,125 @@ module.exports = {
3647
url: 'https://eslint.vuejs.org/rules/v-on-function-call.html'
3748
},
3849
fixable: 'code',
39-
schema: [{ enum: ['always', 'never'] }]
50+
schema: [
51+
{ enum: ['always', 'never'] },
52+
{
53+
type: 'object',
54+
properties: {
55+
ignoreIncludesComment: {
56+
type: 'boolean'
57+
}
58+
},
59+
additionalProperties: false
60+
}
61+
]
4062
},
4163

4264
create(context) {
4365
const always = context.options[0] === 'always'
4466

67+
/**
68+
* @param {VOnExpression} node
69+
* @returns {CallExpression | null}
70+
*/
71+
function getInvalidNeverCallExpression(node) {
72+
/** @type {ExpressionStatement} */
73+
let exprStatement
74+
let body = node.body
75+
while (true) {
76+
const statements = body.filter((st) => st.type !== 'EmptyStatement')
77+
if (statements.length !== 1) {
78+
return null
79+
}
80+
const statement = statements[0]
81+
if (statement.type === 'ExpressionStatement') {
82+
exprStatement = statement
83+
break
84+
}
85+
if (statement.type === 'BlockStatement') {
86+
body = statement.body
87+
continue
88+
}
89+
return null
90+
}
91+
const expression = exprStatement.expression
92+
if (expression.type !== 'CallExpression' || expression.arguments.length) {
93+
return null
94+
}
95+
const callee = expression.callee
96+
if (callee.type !== 'Identifier') {
97+
return null
98+
}
99+
return expression
100+
}
101+
45102
return utils.defineTemplateBodyVisitor(context, {
46-
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
47-
node
48-
) {
49-
if (!always) return
50-
context.report({
51-
node,
52-
loc: node.loc,
53-
message:
54-
"Method calls inside of 'v-on' directives must have parentheses."
55-
})
56-
},
103+
...(always
104+
? {
105+
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
106+
node
107+
) {
108+
context.report({
109+
node,
110+
message:
111+
"Method calls inside of 'v-on' directives must have parentheses."
112+
})
113+
}
114+
}
115+
: {
116+
/** @param {VOnExpression} node */
117+
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
118+
node
119+
) {
120+
const expression = getInvalidNeverCallExpression(node)
121+
if (!expression) {
122+
return
123+
}
124+
const option = context.options[1] || {}
125+
const ignoreIncludesComment = option.ignoreIncludesComment
57126

58-
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression > ExpressionStatement > CallExpression"(
59-
node
60-
) {
61-
if (
62-
!always &&
63-
node.arguments.length === 0 &&
64-
node.callee.type === 'Identifier'
65-
) {
66-
context.report({
67-
node,
68-
loc: node.loc,
69-
message:
70-
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
71-
fix: (fixer) => {
72127
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
73-
const rightToken = tokenStore.getLastToken(node)
74-
const leftToken = tokenStore.getTokenAfter(
75-
node.callee,
76-
isLeftParen
77-
)
78-
const tokens = tokenStore.getTokensBetween(
79-
leftToken,
80-
rightToken,
81-
{ includeComments: true }
128+
/** @type {Token[]} */
129+
const tokens = tokenStore.getTokens(node.parent, {
130+
includeComments: true
131+
})
132+
let leftQuote
133+
let rightQuote
134+
if (isQuote(tokens[0])) {
135+
leftQuote = tokens.shift()
136+
rightQuote = tokens.pop()
137+
}
138+
139+
const hasComment = tokens.some(
140+
(token) => token.type === 'Block' || token.type === 'Line'
82141
)
83142

84-
if (tokens.length) {
85-
// The comment is included and cannot be fixed.
86-
return null
143+
if (ignoreIncludesComment && hasComment) {
144+
return
87145
}
88146

89-
return fixer.removeRange([
90-
leftToken.range[0],
91-
rightToken.range[1]
92-
])
147+
context.report({
148+
node: expression,
149+
message:
150+
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
151+
fix: hasComment
152+
? null /* The comment is included and cannot be fixed. */
153+
: (fixer) => {
154+
const range = leftQuote
155+
? [leftQuote.range[1], rightQuote.range[0]]
156+
: [
157+
tokens[0].range[0],
158+
tokens[tokens.length - 1].range[1]
159+
]
160+
161+
return fixer.replaceTextRange(
162+
range,
163+
context.getSourceCode().getText(expression.callee)
164+
)
165+
}
166+
})
93167
}
94168
})
95-
}
96-
}
97169
})
98170
}
99171
}

0 commit comments

Comments
 (0)