From 220d62488e66558116cf367902c2549c6e0d64af Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 4 Dec 2020 12:50:35 +0900 Subject: [PATCH] Fix false positives for methods whose arguments should not be changed in the `vue/v-on-function-call` rule. --- lib/rules/v-on-function-call.js | 173 ++++++++++++++++---------- tests/lib/rules/v-on-function-call.js | 103 ++++++++++++++- 2 files changed, 207 insertions(+), 69 deletions(-) diff --git a/lib/rules/v-on-function-call.js b/lib/rules/v-on-function-call.js index 4befe9f8c..90da5ed43 100644 --- a/lib/rules/v-on-function-call.js +++ b/lib/rules/v-on-function-call.js @@ -9,6 +9,10 @@ const utils = require('../utils') +/** + * @typedef { import('../utils').ComponentPropertyData } ComponentPropertyData + */ + // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ @@ -96,77 +100,110 @@ module.exports = { return expression } - return utils.defineTemplateBodyVisitor(context, { - ...(always - ? { - /** @param {Identifier} node */ - "VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"( - node - ) { - context.report({ - node, - message: - "Method calls inside of 'v-on' directives must have parentheses." - }) + if (always) { + return utils.defineTemplateBodyVisitor(context, { + /** @param {Identifier} node */ + "VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"( + node + ) { + context.report({ + node, + message: + "Method calls inside of 'v-on' directives must have parentheses." + }) + } + }) + } + + const option = context.options[1] || {} + const ignoreIncludesComment = !!option.ignoreIncludesComment + /** @type {Set} */ + const useArgsMethods = new Set() + + return utils.defineTemplateBodyVisitor( + context, + { + /** @param {VOnExpression} node */ + "VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"( + node + ) { + const expression = getInvalidNeverCallExpression(node) + if (!expression) { + return + } + + const tokenStore = context.parserServices.getTemplateBodyTokenStore() + const tokens = tokenStore.getTokens(node.parent, { + includeComments: true + }) + /** @type {Token | undefined} */ + let leftQuote + /** @type {Token | undefined} */ + let rightQuote + if (isQuote(tokens[0])) { + leftQuote = tokens.shift() + rightQuote = tokens.pop() + } + + const hasComment = tokens.some( + (token) => token.type === 'Block' || token.type === 'Line' + ) + + if (ignoreIncludesComment && hasComment) { + return + } + + if (expression.callee.type === 'Identifier') { + if (useArgsMethods.has(expression.callee.name)) { + // The behavior of target method can change given the arguments. + return } } - : { - /** @param {VOnExpression} node */ - "VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"( - node + + context.report({ + node: expression, + message: + "Method calls without arguments inside of 'v-on' directives must not have parentheses.", + fix: hasComment + ? null /* The comment is included and cannot be fixed. */ + : (fixer) => { + /** @type {Range} */ + const range = + leftQuote && rightQuote + ? [leftQuote.range[1], rightQuote.range[0]] + : [tokens[0].range[0], tokens[tokens.length - 1].range[1]] + + return fixer.replaceTextRange( + range, + context.getSourceCode().getText(expression.callee) + ) + } + }) + } + }, + utils.defineVueVisitor(context, { + onVueObjectEnter(node) { + for (const method of utils.iterateProperties( + node, + new Set(['methods']) + )) { + if (useArgsMethods.has(method.name)) { + continue + } + if (method.type !== 'object') { + continue + } + const value = method.property.value + if ( + (value.type === 'FunctionExpression' || + value.type === 'ArrowFunctionExpression') && + value.params.length > 0 ) { - const expression = getInvalidNeverCallExpression(node) - if (!expression) { - return - } - const option = context.options[1] || {} - const ignoreIncludesComment = !!option.ignoreIncludesComment - - const tokenStore = context.parserServices.getTemplateBodyTokenStore() - const tokens = tokenStore.getTokens(node.parent, { - includeComments: true - }) - /** @type {Token | undefined} */ - let leftQuote - /** @type {Token | undefined} */ - let rightQuote - if (isQuote(tokens[0])) { - leftQuote = tokens.shift() - rightQuote = tokens.pop() - } - - const hasComment = tokens.some( - (token) => token.type === 'Block' || token.type === 'Line' - ) - - if (ignoreIncludesComment && hasComment) { - return - } - - context.report({ - node: expression, - message: - "Method calls without arguments inside of 'v-on' directives must not have parentheses.", - fix: hasComment - ? null /* The comment is included and cannot be fixed. */ - : (fixer) => { - /** @type {Range} */ - const range = - leftQuote && rightQuote - ? [leftQuote.range[1], rightQuote.range[0]] - : [ - tokens[0].range[0], - tokens[tokens.length - 1].range[1] - ] - - return fixer.replaceTextRange( - range, - context.getSourceCode().getText(expression.callee) - ) - } - }) + useArgsMethods.add(method.name) } - }) - }) + } + } + }) + ) } } diff --git a/tests/lib/rules/v-on-function-call.js b/tests/lib/rules/v-on-function-call.js index 2dee605d0..cca7834da 100644 --- a/tests/lib/rules/v-on-function-call.js +++ b/tests/lib/rules/v-on-function-call.js @@ -16,7 +16,7 @@ const rule = require('../../../lib/rules/v-on-function-call') const tester = new RuleTester({ parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + parserOptions: { ecmaVersion: 2020, sourceType: 'module' } }) tester.run('v-on-function-call', rule, { @@ -111,6 +111,57 @@ tester.run('v-on-function-call', rule, { filename: 'test.vue', code: '', options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] } ], invalid: [ @@ -255,6 +306,56 @@ tester.run('v-on-function-call', rule, { "Method calls without arguments inside of 'v-on' directives must not have parentheses." ], options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + "Method calls without arguments inside of 'v-on' directives must not have parentheses." + ], + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + "Method calls without arguments inside of 'v-on' directives must not have parentheses." + ], + options: ['never'] } ] })