@@ -7,17 +7,19 @@ import {
7
7
type IgnoreAccessorPatternOption ,
8
8
type IgnoreClassesOption ,
9
9
type IgnoreIdentifierPatternOption ,
10
+ type IgnoreMapsAndSetsOption ,
10
11
type OverridableOptions ,
11
12
type RawOverridableOptions ,
12
13
getCoreOptions ,
13
14
ignoreAccessorPatternOptionSchema ,
14
15
ignoreClassesOptionSchema ,
15
16
ignoreIdentifierPatternOptionSchema ,
17
+ ignoreMapsAndSetsOptionSchema ,
16
18
shouldIgnoreClasses ,
17
19
shouldIgnorePattern ,
18
20
upgradeRawOverridableOptions ,
19
21
} from "#/options" ;
20
- import { isExpected , ruleNameScope } from "#/utils/misc" ;
22
+ import { ruleNameScope } from "#/utils/misc" ;
21
23
import { type NamedCreateRuleCustomMeta , type Rule , type RuleResult , createRule , getTypeOfNode } from "#/utils/rule" ;
22
24
import { overridableOptionsSchema } from "#/utils/schemas" ;
23
25
import { findRootIdentifier , isDefinedByMutableVariable , isInConstructor } from "#/utils/tree" ;
@@ -27,9 +29,13 @@ import {
27
29
isArrayType ,
28
30
isCallExpression ,
29
31
isIdentifier ,
32
+ isMapConstructorType ,
33
+ isMapType ,
30
34
isMemberExpression ,
31
35
isNewExpression ,
32
36
isObjectConstructorType ,
37
+ isSetConstructorType ,
38
+ isSetType ,
33
39
isTSAsExpression ,
34
40
} from "#/utils/type-guards" ;
35
41
@@ -45,6 +51,7 @@ export const fullName: `${typeof ruleNameScope}/${typeof name}` = `${ruleNameSco
45
51
46
52
type CoreOptions = IgnoreAccessorPatternOption &
47
53
IgnoreClassesOption &
54
+ IgnoreMapsAndSetsOption &
48
55
IgnoreIdentifierPatternOption & {
49
56
ignoreImmediateMutation : boolean ;
50
57
ignoreNonConstDeclarations :
@@ -64,6 +71,7 @@ const coreOptionsPropertiesSchema = deepmerge(
64
71
ignoreIdentifierPatternOptionSchema ,
65
72
ignoreAccessorPatternOptionSchema ,
66
73
ignoreClassesOptionSchema ,
74
+ ignoreMapsAndSetsOptionSchema ,
67
75
{
68
76
ignoreImmediateMutation : {
69
77
type : "boolean" ,
@@ -98,6 +106,7 @@ const schema: JSONSchema4[] = [overridableOptionsSchema(coreOptionsPropertiesSch
98
106
const defaultOptions = [
99
107
{
100
108
ignoreClasses : false ,
109
+ ignoreMapsAndSets : false ,
101
110
ignoreImmediateMutation : true ,
102
111
ignoreNonConstDeclarations : false ,
103
112
} ,
@@ -110,6 +119,8 @@ const errorMessages = {
110
119
generic : "Modifying an existing object/array is not allowed." ,
111
120
object : "Modifying properties of existing object not allowed." ,
112
121
array : "Modifying an array is not allowed." ,
122
+ map : "Modifying a map is not allowed." ,
123
+ set : "Modifying a set is not allowed." ,
113
124
} as const ;
114
125
115
126
/**
@@ -151,14 +162,38 @@ const arrayMutatorMethods = new Set([
151
162
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Accessor_methods
152
163
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods
153
164
*/
154
- const arrayNewObjectReturningMethods = [ "concat" , "slice" , "filter" , "map" , "reduce" , "reduceRight" ] ;
165
+ const arrayNewObjectReturningMethods = new Set ( [ "concat" , "slice" , "filter" , "map" , "reduce" , "reduceRight" ] ) ;
155
166
156
167
/**
157
168
* Array constructor functions that create a new array.
158
169
*
159
170
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods
160
171
*/
161
- const arrayConstructorFunctions = [ "from" , "of" ] ;
172
+ const arrayConstructorFunctions = new Set ( [ "from" , "of" ] ) ;
173
+
174
+ /**
175
+ * Map methods that mutate an map.
176
+ *
177
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
178
+ */
179
+ const mapMutatorMethods = new Set ( [ "clear" , "delete" , "set" ] ) ;
180
+
181
+ /**
182
+ * Map methods that return a new object without mutating the original.
183
+ */
184
+ const mapNewObjectReturningMethods = new Set < string > ( [ ] ) ;
185
+
186
+ /**
187
+ * Set methods that mutate an set.
188
+ *
189
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
190
+ */
191
+ const setMutatorMethods = new Set ( [ "add" , "clear" , "delete" ] ) ;
192
+
193
+ /**
194
+ * Set methods that return a new object without mutating the original.
195
+ */
196
+ const setNewObjectReturningMethods = new Set ( [ "difference" , "intersection" , "symmetricDifference" , "union" ] ) ;
162
197
163
198
/**
164
199
* Object constructor functions that mutate an object.
@@ -170,7 +205,7 @@ const objectConstructorMutatorFunctions = new Set(["assign", "defineProperties",
170
205
/**
171
206
* Object constructor functions that return new objects.
172
207
*/
173
- const objectConstructorNewObjectReturningMethods = [
208
+ const objectConstructorNewObjectReturningMethods = new Set ( [
174
209
"create" ,
175
210
"entries" ,
176
211
"fromEntries" ,
@@ -181,14 +216,14 @@ const objectConstructorNewObjectReturningMethods = [
181
216
"groupBy" ,
182
217
"keys" ,
183
218
"values" ,
184
- ] ;
219
+ ] ) ;
185
220
186
221
/**
187
222
* String constructor functions that return new objects.
188
223
*
189
224
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Methods
190
225
*/
191
- const stringConstructorNewObjectReturningMethods = [ "split" ] ;
226
+ const stringConstructorNewObjectReturningMethods = new Set ( [ "split" ] ) ;
192
227
193
228
/**
194
229
* Check if the given assignment expression violates this rule.
@@ -391,35 +426,49 @@ function isInChainCallAndFollowsNew(
391
426
}
392
427
393
428
// Check for: new Array()
394
- if ( isNewExpression ( node ) && isArrayConstructorType ( context , getTypeOfNode ( node . callee , context ) ) ) {
395
- return true ;
429
+ if ( isNewExpression ( node ) ) {
430
+ const type = getTypeOfNode ( node . callee , context ) ;
431
+ return (
432
+ isArrayConstructorType ( context , type ) ||
433
+ isMapConstructorType ( context , type ) ||
434
+ isSetConstructorType ( context , type )
435
+ ) ;
396
436
}
397
437
398
438
if ( isCallExpression ( node ) && isMemberExpression ( node . callee ) && isIdentifier ( node . callee . property ) ) {
399
439
// Check for: Array.from(iterable)
400
440
if (
401
- arrayConstructorFunctions . some ( isExpected ( node . callee . property . name ) ) &&
441
+ arrayConstructorFunctions . has ( node . callee . property . name ) &&
402
442
isArrayConstructorType ( context , getTypeOfNode ( node . callee . object , context ) )
403
443
) {
404
444
return true ;
405
445
}
406
446
407
447
// Check for: array.slice(0)
408
- if ( arrayNewObjectReturningMethods . some ( isExpected ( node . callee . property . name ) ) ) {
448
+ if ( arrayNewObjectReturningMethods . has ( node . callee . property . name ) ) {
449
+ return true ;
450
+ }
451
+
452
+ if ( mapNewObjectReturningMethods . has ( node . callee . property . name ) ) {
453
+ return true ;
454
+ }
455
+
456
+ // Check for: set.difference(otherSet)
457
+ if ( setNewObjectReturningMethods . has ( node . callee . property . name ) ) {
409
458
return true ;
410
459
}
411
460
412
461
// Check for: Object.entries(object)
413
462
if (
414
- objectConstructorNewObjectReturningMethods . some ( isExpected ( node . callee . property . name ) ) &&
463
+ objectConstructorNewObjectReturningMethods . has ( node . callee . property . name ) &&
415
464
isObjectConstructorType ( context , getTypeOfNode ( node . callee . object , context ) )
416
465
) {
417
466
return true ;
418
467
}
419
468
420
469
// Check for: "".split("")
421
470
if (
422
- stringConstructorNewObjectReturningMethods . some ( isExpected ( node . callee . property . name ) ) &&
471
+ stringConstructorNewObjectReturningMethods . has ( node . callee . property . name ) &&
423
472
getTypeOfNode ( node . callee . object , context ) . isStringLiteral ( )
424
473
) {
425
474
return true ;
@@ -510,6 +559,68 @@ function checkCallExpression(
510
559
}
511
560
}
512
561
562
+ // Set mutation?
563
+ if (
564
+ setMutatorMethods . has ( node . callee . property . name ) &&
565
+ ( ! ignoreImmediateMutation || ! isInChainCallAndFollowsNew ( node . callee , context ) ) &&
566
+ isSetType ( context , getTypeOfNode ( node . callee . object , context ) )
567
+ ) {
568
+ if ( ignoreNonConstDeclarations === false ) {
569
+ return {
570
+ context,
571
+ descriptors : [ { node, messageId : "set" } ] ,
572
+ } ;
573
+ }
574
+ const rootIdentifier = findRootIdentifier ( node . callee . object ) ;
575
+ if (
576
+ rootIdentifier === undefined ||
577
+ ! isDefinedByMutableVariable (
578
+ rootIdentifier ,
579
+ context ,
580
+ ( variableNode ) =>
581
+ ignoreNonConstDeclarations === true ||
582
+ ! ignoreNonConstDeclarations . treatParametersAsConst ||
583
+ shouldIgnorePattern ( variableNode , context , ignoreIdentifierPattern , ignoreAccessorPattern ) ,
584
+ )
585
+ ) {
586
+ return {
587
+ context,
588
+ descriptors : [ { node, messageId : "set" } ] ,
589
+ } ;
590
+ }
591
+ }
592
+
593
+ // Map mutation?
594
+ if (
595
+ mapMutatorMethods . has ( node . callee . property . name ) &&
596
+ ( ! ignoreImmediateMutation || ! isInChainCallAndFollowsNew ( node . callee , context ) ) &&
597
+ isMapType ( context , getTypeOfNode ( node . callee . object , context ) )
598
+ ) {
599
+ if ( ignoreNonConstDeclarations === false ) {
600
+ return {
601
+ context,
602
+ descriptors : [ { node, messageId : "map" } ] ,
603
+ } ;
604
+ }
605
+ const rootIdentifier = findRootIdentifier ( node . callee . object ) ;
606
+ if (
607
+ rootIdentifier === undefined ||
608
+ ! isDefinedByMutableVariable (
609
+ rootIdentifier ,
610
+ context ,
611
+ ( variableNode ) =>
612
+ ignoreNonConstDeclarations === true ||
613
+ ! ignoreNonConstDeclarations . treatParametersAsConst ||
614
+ shouldIgnorePattern ( variableNode , context , ignoreIdentifierPattern , ignoreAccessorPattern ) ,
615
+ )
616
+ ) {
617
+ return {
618
+ context,
619
+ descriptors : [ { node, messageId : "map" } ] ,
620
+ } ;
621
+ }
622
+ }
623
+
513
624
// Non-array object mutation (ex. Object.assign on identifier)?
514
625
if (
515
626
objectConstructorMutatorFunctions . has ( node . callee . property . name ) &&
0 commit comments