99
1010const 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