4
4
*/
5
5
'use strict'
6
6
7
+ /**
8
+ * @typedef {{name?: string, set: Set<string>} } PropsInfo
9
+ */
10
+
7
11
const utils = require ( '../utils' )
8
12
const { findVariable } = require ( '@eslint-community/eslint-utils' )
9
13
@@ -84,6 +88,19 @@ function isVmReference(node) {
84
88
return false
85
89
}
86
90
91
+ /**
92
+ * @param { object } options
93
+ * @param { boolean } options.propProps avoid mutating the value of a prop but leaving the reference the same
94
+ */
95
+ function parseOptions ( options ) {
96
+ return Object . assign (
97
+ {
98
+ propProps : true
99
+ } ,
100
+ options
101
+ )
102
+ }
103
+
87
104
module . exports = {
88
105
meta : {
89
106
type : 'suggestion' ,
@@ -94,12 +111,21 @@ module.exports = {
94
111
} ,
95
112
fixable : null , // or "code" or "whitespace"
96
113
schema : [
97
- // fill in your schema
114
+ {
115
+ type : 'object' ,
116
+ properties : {
117
+ propProps : {
118
+ type : 'boolean'
119
+ }
120
+ } ,
121
+ additionalProperties : false
122
+ }
98
123
]
99
124
} ,
100
125
/** @param {RuleContext } context */
101
126
create ( context ) {
102
- /** @type {Map<ObjectExpression|CallExpression, Set<string>> } */
127
+ const { propProps } = parseOptions ( context . options [ 0 ] )
128
+ /** @type {Map<ObjectExpression|CallExpression, PropsInfo> } */
103
129
const propsMap = new Map ( )
104
130
/** @type { { type: 'export' | 'mark' | 'definition', object: ObjectExpression } | { type: 'setup', object: CallExpression } | null } */
105
131
let vueObjectData = null
@@ -138,9 +164,10 @@ module.exports = {
138
164
/**
139
165
* @param {MemberExpression|Identifier } props
140
166
* @param {string } name
167
+ * @param {boolean } isRootProps
141
168
*/
142
- function verifyMutating ( props , name ) {
143
- const invalid = utils . findMutating ( props )
169
+ function verifyMutating ( props , name , isRootProps = false ) {
170
+ const invalid = utils . findMutating ( props , propProps , isRootProps )
144
171
if ( invalid ) {
145
172
report ( invalid . node , name )
146
173
}
@@ -192,8 +219,9 @@ module.exports = {
192
219
/**
193
220
* @param {Identifier } prop
194
221
* @param {string[] } path
222
+ * @param {boolean } isRootProps
195
223
*/
196
- function verifyPropVariable ( prop , path ) {
224
+ function verifyPropVariable ( prop , path , isRootProps = false ) {
197
225
const variable = findVariable ( context . getScope ( ) , prop )
198
226
if ( ! variable ) {
199
227
return
@@ -205,7 +233,7 @@ module.exports = {
205
233
}
206
234
const id = reference . identifier
207
235
208
- const invalid = utils . findMutating ( id )
236
+ const invalid = utils . findMutating ( id , propProps , isRootProps )
209
237
if ( ! invalid ) {
210
238
continue
211
239
}
@@ -252,20 +280,23 @@ module.exports = {
252
280
onDefinePropsEnter ( node , props ) {
253
281
const defineVariableNames = new Set ( extractDefineVariableNames ( ) )
254
282
255
- const propsSet = new Set (
256
- props
257
- . map ( ( p ) => p . propName )
258
- . filter (
259
- /**
260
- * @returns {propName is string }
261
- */
262
- ( propName ) =>
263
- utils . isDef ( propName ) &&
264
- ! GLOBALS_WHITE_LISTED . has ( propName ) &&
265
- ! defineVariableNames . has ( propName )
266
- )
267
- )
268
- propsMap . set ( node , propsSet )
283
+ const propsInfo = {
284
+ name : '' ,
285
+ set : new Set (
286
+ props
287
+ . map ( ( p ) => p . propName )
288
+ . filter (
289
+ /**
290
+ * @returns {propName is string }
291
+ */
292
+ ( propName ) =>
293
+ utils . isDef ( propName ) &&
294
+ ! GLOBALS_WHITE_LISTED . has ( propName ) &&
295
+ ! defineVariableNames . has ( propName )
296
+ )
297
+ )
298
+ }
299
+ propsMap . set ( node , propsInfo )
269
300
vueObjectData = {
270
301
type : 'setup' ,
271
302
object : node
@@ -294,22 +325,25 @@ module.exports = {
294
325
target . parent . id ,
295
326
[ ]
296
327
) ) {
297
- verifyPropVariable ( prop , path )
298
- propsSet . add ( prop . name )
328
+ if ( path . length === 0 ) {
329
+ propsInfo . name = prop . name
330
+ } else {
331
+ propsInfo . set . add ( prop . name )
332
+ }
333
+ verifyPropVariable ( prop , path , propsInfo . name === prop . name )
299
334
}
300
335
}
301
336
} ) ,
302
337
utils . defineVueVisitor ( context , {
303
338
onVueObjectEnter ( node ) {
304
- propsMap . set (
305
- node ,
306
- new Set (
339
+ propsMap . set ( node , {
340
+ set : new Set (
307
341
utils
308
342
. getComponentPropsFromOptions ( node )
309
343
. map ( ( p ) => p . propName )
310
344
. filter ( utils . isDef )
311
345
)
312
- )
346
+ } )
313
347
} ,
314
348
onVueObjectExit ( node , { type } ) {
315
349
if (
@@ -341,7 +375,11 @@ module.exports = {
341
375
propsParam ,
342
376
[ ]
343
377
) ) {
344
- verifyPropVariable ( prop , path )
378
+ verifyPropVariable (
379
+ prop ,
380
+ path ,
381
+ prop . parent . type === 'FunctionExpression'
382
+ )
345
383
}
346
384
} ,
347
385
/** @param {(Identifier | ThisExpression) & { parent: MemberExpression } } node */
@@ -359,7 +397,7 @@ module.exports = {
359
397
const name = utils . getStaticPropertyName ( mem )
360
398
if (
361
399
name &&
362
- /** @type {Set<string> } */ ( propsMap . get ( vueNode ) ) . has ( name )
400
+ /** @type {PropsInfo } */ ( propsMap . get ( vueNode ) ) . set . has ( name )
363
401
) {
364
402
verifyMutating ( mem , name )
365
403
}
@@ -378,9 +416,9 @@ module.exports = {
378
416
const name = utils . getStaticPropertyName ( mem )
379
417
if (
380
418
name &&
381
- /** @type {Set<string> } */ ( propsMap . get ( vueObjectData . object ) ) . has (
382
- name
383
- )
419
+ /** @type {PropsInfo } */ (
420
+ propsMap . get ( vueObjectData . object )
421
+ ) . set . has ( name )
384
422
) {
385
423
verifyMutating ( mem , name )
386
424
}
@@ -393,14 +431,19 @@ module.exports = {
393
431
if ( ! isVmReference ( node ) ) {
394
432
return
395
433
}
396
- const name = node . name
397
- if (
398
- name &&
399
- /** @type {Set<string> } */ ( propsMap . get ( vueObjectData . object ) ) . has (
400
- name
401
- )
402
- ) {
403
- verifyMutating ( node , name )
434
+ const propsInfo = /** @type {PropsInfo } */ (
435
+ propsMap . get ( vueObjectData . object )
436
+ )
437
+ const isRootProps = ! ! node . name && propsInfo . name === node . name
438
+ const parent = node . parent
439
+ const parentProperty =
440
+ parent . type === 'MemberExpression' ? parent . property : null
441
+ const name =
442
+ isRootProps && parentProperty ?. type === 'Identifier'
443
+ ? parentProperty . name
444
+ : node . name
445
+ if ( name && ( propsInfo . set . has ( name ) || isRootProps ) ) {
446
+ verifyMutating ( node , name , isRootProps )
404
447
}
405
448
} ,
406
449
/** @param {ESNode } node */
@@ -423,12 +466,22 @@ module.exports = {
423
466
return
424
467
}
425
468
469
+ const propsInfo = /** @type {PropsInfo } */ (
470
+ propsMap . get ( vueObjectData . object )
471
+ )
472
+
426
473
const nodes = utils . getMemberChaining ( node )
427
474
const first = nodes [ 0 ]
428
475
let name
429
- if ( isVmReference ( first ) ) {
476
+ if ( isVmReference ( first ) && first . name !== propsInfo . name ) {
477
+ if ( ! propProps && nodes . length > 1 ) {
478
+ return
479
+ }
430
480
name = first . name
431
- } else if ( first . type === 'ThisExpression' ) {
481
+ } else if ( first . type === 'ThisExpression' || isVmReference ( first ) ) {
482
+ if ( ! propProps && nodes . length > 2 ) {
483
+ return
484
+ }
432
485
const mem = nodes [ 1 ]
433
486
if ( ! mem ) {
434
487
return
@@ -437,12 +490,7 @@ module.exports = {
437
490
} else {
438
491
return
439
492
}
440
- if (
441
- name &&
442
- /** @type {Set<string> } */ ( propsMap . get ( vueObjectData . object ) ) . has (
443
- name
444
- )
445
- ) {
493
+ if ( name && propsInfo . set . has ( name ) ) {
446
494
report ( node , name )
447
495
}
448
496
}
0 commit comments