Skip to content

Commit d9f72a5

Browse files
authored
Fix false positives for methods whose arguments should not be changed in the vue/v-on-function-call rule. (#1369)
1 parent b73d7c5 commit d9f72a5

File tree

2 files changed

+207
-69
lines changed

2 files changed

+207
-69
lines changed

Diff for: lib/rules/v-on-function-call.js

+105-68
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

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

12+
/**
13+
* @typedef { import('../utils').ComponentPropertyData } ComponentPropertyData
14+
*/
15+
1216
// ------------------------------------------------------------------------------
1317
// Helpers
1418
// ------------------------------------------------------------------------------
@@ -96,77 +100,110 @@ module.exports = {
96100
return expression
97101
}
98102

99-
return utils.defineTemplateBodyVisitor(context, {
100-
...(always
101-
? {
102-
/** @param {Identifier} node */
103-
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
104-
node
105-
) {
106-
context.report({
107-
node,
108-
message:
109-
"Method calls inside of 'v-on' directives must have parentheses."
110-
})
103+
if (always) {
104+
return utils.defineTemplateBodyVisitor(context, {
105+
/** @param {Identifier} node */
106+
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
107+
node
108+
) {
109+
context.report({
110+
node,
111+
message:
112+
"Method calls inside of 'v-on' directives must have parentheses."
113+
})
114+
}
115+
})
116+
}
117+
118+
const option = context.options[1] || {}
119+
const ignoreIncludesComment = !!option.ignoreIncludesComment
120+
/** @type {Set<string>} */
121+
const useArgsMethods = new Set()
122+
123+
return utils.defineTemplateBodyVisitor(
124+
context,
125+
{
126+
/** @param {VOnExpression} node */
127+
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
128+
node
129+
) {
130+
const expression = getInvalidNeverCallExpression(node)
131+
if (!expression) {
132+
return
133+
}
134+
135+
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
136+
const tokens = tokenStore.getTokens(node.parent, {
137+
includeComments: true
138+
})
139+
/** @type {Token | undefined} */
140+
let leftQuote
141+
/** @type {Token | undefined} */
142+
let rightQuote
143+
if (isQuote(tokens[0])) {
144+
leftQuote = tokens.shift()
145+
rightQuote = tokens.pop()
146+
}
147+
148+
const hasComment = tokens.some(
149+
(token) => token.type === 'Block' || token.type === 'Line'
150+
)
151+
152+
if (ignoreIncludesComment && hasComment) {
153+
return
154+
}
155+
156+
if (expression.callee.type === 'Identifier') {
157+
if (useArgsMethods.has(expression.callee.name)) {
158+
// The behavior of target method can change given the arguments.
159+
return
111160
}
112161
}
113-
: {
114-
/** @param {VOnExpression} node */
115-
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
116-
node
162+
163+
context.report({
164+
node: expression,
165+
message:
166+
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
167+
fix: hasComment
168+
? null /* The comment is included and cannot be fixed. */
169+
: (fixer) => {
170+
/** @type {Range} */
171+
const range =
172+
leftQuote && rightQuote
173+
? [leftQuote.range[1], rightQuote.range[0]]
174+
: [tokens[0].range[0], tokens[tokens.length - 1].range[1]]
175+
176+
return fixer.replaceTextRange(
177+
range,
178+
context.getSourceCode().getText(expression.callee)
179+
)
180+
}
181+
})
182+
}
183+
},
184+
utils.defineVueVisitor(context, {
185+
onVueObjectEnter(node) {
186+
for (const method of utils.iterateProperties(
187+
node,
188+
new Set(['methods'])
189+
)) {
190+
if (useArgsMethods.has(method.name)) {
191+
continue
192+
}
193+
if (method.type !== 'object') {
194+
continue
195+
}
196+
const value = method.property.value
197+
if (
198+
(value.type === 'FunctionExpression' ||
199+
value.type === 'ArrowFunctionExpression') &&
200+
value.params.length > 0
117201
) {
118-
const expression = getInvalidNeverCallExpression(node)
119-
if (!expression) {
120-
return
121-
}
122-
const option = context.options[1] || {}
123-
const ignoreIncludesComment = !!option.ignoreIncludesComment
124-
125-
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
126-
const tokens = tokenStore.getTokens(node.parent, {
127-
includeComments: true
128-
})
129-
/** @type {Token | undefined} */
130-
let leftQuote
131-
/** @type {Token | undefined} */
132-
let rightQuote
133-
if (isQuote(tokens[0])) {
134-
leftQuote = tokens.shift()
135-
rightQuote = tokens.pop()
136-
}
137-
138-
const hasComment = tokens.some(
139-
(token) => token.type === 'Block' || token.type === 'Line'
140-
)
141-
142-
if (ignoreIncludesComment && hasComment) {
143-
return
144-
}
145-
146-
context.report({
147-
node: expression,
148-
message:
149-
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
150-
fix: hasComment
151-
? null /* The comment is included and cannot be fixed. */
152-
: (fixer) => {
153-
/** @type {Range} */
154-
const range =
155-
leftQuote && rightQuote
156-
? [leftQuote.range[1], rightQuote.range[0]]
157-
: [
158-
tokens[0].range[0],
159-
tokens[tokens.length - 1].range[1]
160-
]
161-
162-
return fixer.replaceTextRange(
163-
range,
164-
context.getSourceCode().getText(expression.callee)
165-
)
166-
}
167-
})
202+
useArgsMethods.add(method.name)
168203
}
169-
})
170-
})
204+
}
205+
}
206+
})
207+
)
171208
}
172209
}

Diff for: tests/lib/rules/v-on-function-call.js

+102-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const rule = require('../../../lib/rules/v-on-function-call')
1616

1717
const tester = new RuleTester({
1818
parser: require.resolve('vue-eslint-parser'),
19-
parserOptions: { ecmaVersion: 2020 }
19+
parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
2020
})
2121

2222
tester.run('v-on-function-call', rule, {
@@ -111,6 +111,57 @@ tester.run('v-on-function-call', rule, {
111111
filename: 'test.vue',
112112
code: '<template><div @click="foo?.()"></div></template>',
113113
options: ['never']
114+
},
115+
{
116+
filename: 'test.vue',
117+
code: `
118+
<template><div @click="foo()" /></template>
119+
<script>
120+
export default {
121+
methods: {
122+
foo(a) {}
123+
}
124+
}
125+
</script>`,
126+
options: ['never']
127+
},
128+
{
129+
filename: 'test.vue',
130+
code: `
131+
<template>
132+
<div @click="foo()" />
133+
<div @click="bar()" />
134+
<div @click="baz()" />
135+
</template>
136+
<script>
137+
export default {
138+
methods: {
139+
foo(a,b) {},
140+
bar(...a) {},
141+
baz(a = 42) {},
142+
}
143+
}
144+
</script>`,
145+
options: ['never']
146+
},
147+
{
148+
filename: 'test.vue',
149+
code: `
150+
<template>
151+
<div @click="foo()" />
152+
<div @click="bar()" />
153+
<div @click="baz()" />
154+
</template>
155+
<script>
156+
export default {
157+
methods: {
158+
foo: (a,b) => {},
159+
bar: (...a) => {},
160+
baz: (a = 42) => {},
161+
}
162+
}
163+
</script>`,
164+
options: ['never']
114165
}
115166
],
116167
invalid: [
@@ -255,6 +306,56 @@ tester.run('v-on-function-call', rule, {
255306
"Method calls without arguments inside of 'v-on' directives must not have parentheses."
256307
],
257308
options: ['never']
309+
},
310+
{
311+
filename: 'test.vue',
312+
code: `
313+
<template><div @click="foo()" /></template>
314+
<script>
315+
export default {
316+
methods: {
317+
foo() {}
318+
}
319+
}
320+
</script>`,
321+
output: `
322+
<template><div @click="foo" /></template>
323+
<script>
324+
export default {
325+
methods: {
326+
foo() {}
327+
}
328+
}
329+
</script>`,
330+
errors: [
331+
"Method calls without arguments inside of 'v-on' directives must not have parentheses."
332+
],
333+
options: ['never']
334+
},
335+
{
336+
filename: 'test.vue',
337+
code: `
338+
<template><div @click="foo()" /></template>
339+
<script>
340+
export default {
341+
methods: {
342+
foo: () => {}
343+
}
344+
}
345+
</script>`,
346+
output: `
347+
<template><div @click="foo" /></template>
348+
<script>
349+
export default {
350+
methods: {
351+
foo: () => {}
352+
}
353+
}
354+
</script>`,
355+
errors: [
356+
"Method calls without arguments inside of 'v-on' directives must not have parentheses."
357+
],
358+
options: ['never']
258359
}
259360
]
260361
})

0 commit comments

Comments
 (0)