@@ -3,12 +3,15 @@ import type { JSONSchema4 } from "json-schema";
3
3
4
4
import type { RuleContext , RuleMetaData , RuleResult } from "~/util/rule" ;
5
5
import { createRule , isReadonly } from "~/util/rule" ;
6
- import { getParentTypeAliasDeclaration } from "~/util/tree" ;
6
+ import { getAncestorOfType } from "~/util/tree" ;
7
7
import {
8
8
isIdentifier ,
9
9
isTSArrayType ,
10
+ isTSIndexSignature ,
11
+ isTSInterfaceDeclaration ,
10
12
isTSParameterProperty ,
11
13
isTSTupleType ,
14
+ isTSTypeAliasDeclaration ,
12
15
isTSTypeOperator ,
13
16
} from "~/util/typeguard" ;
14
17
@@ -32,6 +35,7 @@ type Options = {
32
35
readonly requireOthersToBeReadonly : boolean ;
33
36
} ;
34
37
readonly blacklist : ReadonlyArray < string > ;
38
+ readonly ignoreInterface : boolean ;
35
39
} ;
36
40
37
41
// The schema for the rule options.
@@ -75,6 +79,9 @@ const schema: JSONSchema4 = [
75
79
type : "string" ,
76
80
} ,
77
81
} ,
82
+ ignoreInterface : {
83
+ type : "boolean" ,
84
+ } ,
78
85
} ,
79
86
additionalProperties : false ,
80
87
} ,
@@ -83,14 +90,15 @@ const schema: JSONSchema4 = [
83
90
// The default options for the rule.
84
91
const defaultOptions : Options = {
85
92
mustBeReadonly : {
86
- pattern : "^Readonly" ,
93
+ pattern : "^(I?) Readonly" ,
87
94
requireOthersToBeMutable : false ,
88
95
} ,
89
96
mustBeMutable : {
90
- pattern : "^Mutable" ,
97
+ pattern : "^(I?) Mutable" ,
91
98
requireOthersToBeReadonly : true ,
92
99
} ,
93
100
blacklist : [ "^Mutable$" ] ,
101
+ ignoreInterface : false ,
94
102
} ;
95
103
96
104
// The possible error messages.
@@ -130,7 +138,7 @@ const mutableToImmutableTypes: ReadonlyMap<string, string> = new Map<
130
138
[ "Set" , "ReadonlySet" ] ,
131
139
] ) ;
132
140
133
- enum TypeAliasDeclarationDetails {
141
+ enum TypeReadonlynessDetails {
134
142
ERROR_MUTABLE_READONLY ,
135
143
NEEDS_EXPLICIT_MARKING ,
136
144
IGNORE ,
@@ -140,37 +148,51 @@ enum TypeAliasDeclarationDetails {
140
148
READONLY_NOT_OK ,
141
149
}
142
150
143
- const cachedTypeAliasDeclarationsDetails = new WeakMap <
144
- TSESTree . TSTypeAliasDeclaration ,
145
- TypeAliasDeclarationDetails
151
+ const cachedDetails = new WeakMap <
152
+ TSESTree . TSInterfaceDeclaration | TSESTree . TSTypeAliasDeclaration ,
153
+ TypeReadonlynessDetails
146
154
> ( ) ;
147
155
148
156
/**
149
157
* Get the details for the given type alias.
150
158
*/
151
159
function getTypeAliasDeclarationDetails (
152
- node : TSESTree . TSTypeAliasDeclaration ,
160
+ node : TSESTree . Node ,
153
161
context : RuleContext < keyof typeof errorMessages , Options > ,
154
162
options : Options
155
- ) : TypeAliasDeclarationDetails {
156
- const cached = cachedTypeAliasDeclarationsDetails . get ( node ) ;
163
+ ) : TypeReadonlynessDetails {
164
+ const typeDeclaration = getTypeDeclaration ( node ) ;
165
+ if ( typeDeclaration === null ) {
166
+ return TypeReadonlynessDetails . IGNORE ;
167
+ }
168
+
169
+ const indexSignature = getParentIndexSignature ( node ) ;
170
+ if ( indexSignature !== null && getTypeDeclaration ( indexSignature ) !== null ) {
171
+ return TypeReadonlynessDetails . IGNORE ;
172
+ }
173
+
174
+ const cached = cachedDetails . get ( typeDeclaration ) ;
157
175
if ( cached !== undefined ) {
158
176
return cached ;
159
177
}
160
178
161
- const result = getTypeAliasDeclarationDetailsInternal ( node , context , options ) ;
162
- cachedTypeAliasDeclarationsDetails . set ( node , result ) ;
179
+ const result = getTypeAliasDeclarationDetailsInternal (
180
+ typeDeclaration ,
181
+ context ,
182
+ options
183
+ ) ;
184
+ cachedDetails . set ( typeDeclaration , result ) ;
163
185
return result ;
164
186
}
165
187
166
188
/**
167
189
* Get the details for the given type alias.
168
190
*/
169
191
function getTypeAliasDeclarationDetailsInternal (
170
- node : TSESTree . TSTypeAliasDeclaration ,
192
+ node : TSESTree . TSInterfaceDeclaration | TSESTree . TSTypeAliasDeclaration ,
171
193
context : RuleContext < keyof typeof errorMessages , Options > ,
172
194
options : Options
173
- ) : TypeAliasDeclarationDetails {
195
+ ) : TypeReadonlynessDetails {
174
196
const blacklistPatterns = (
175
197
Array . isArray ( options . blacklist ) ? options . blacklist : [ options . blacklist ]
176
198
) . map ( ( pattern ) => new RegExp ( pattern , "u" ) ) ;
@@ -180,7 +202,7 @@ function getTypeAliasDeclarationDetailsInternal(
180
202
) ;
181
203
182
204
if ( blacklisted ) {
183
- return TypeAliasDeclarationDetails . IGNORE ;
205
+ return TypeReadonlynessDetails . IGNORE ;
184
206
}
185
207
186
208
const mustBeReadonlyPatterns = (
@@ -203,7 +225,7 @@ function getTypeAliasDeclarationDetailsInternal(
203
225
) ;
204
226
205
227
if ( patternStatesReadonly && patternStatesMutable ) {
206
- return TypeAliasDeclarationDetails . ERROR_MUTABLE_READONLY ;
228
+ return TypeReadonlynessDetails . ERROR_MUTABLE_READONLY ;
207
229
}
208
230
209
231
if (
@@ -212,7 +234,7 @@ function getTypeAliasDeclarationDetailsInternal(
212
234
options . mustBeReadonly . requireOthersToBeMutable &&
213
235
options . mustBeMutable . requireOthersToBeReadonly
214
236
) {
215
- return TypeAliasDeclarationDetails . NEEDS_EXPLICIT_MARKING ;
237
+ return TypeReadonlynessDetails . NEEDS_EXPLICIT_MARKING ;
216
238
}
217
239
218
240
const requiredReadonlyness =
@@ -226,34 +248,44 @@ function getTypeAliasDeclarationDetailsInternal(
226
248
: RequiredReadonlyness . EITHER ;
227
249
228
250
if ( requiredReadonlyness === RequiredReadonlyness . EITHER ) {
229
- return TypeAliasDeclarationDetails . IGNORE ;
251
+ return TypeReadonlynessDetails . IGNORE ;
230
252
}
231
253
232
- const readonly = isReadonly ( node . typeAnnotation , context ) ;
254
+ const readonly = isReadonly (
255
+ isTSTypeAliasDeclaration ( node ) ? node . typeAnnotation : node . body ,
256
+ context
257
+ ) ;
233
258
234
259
if ( requiredReadonlyness === RequiredReadonlyness . MUTABLE ) {
235
260
return readonly
236
- ? TypeAliasDeclarationDetails . MUTABLE_NOT_OK
237
- : TypeAliasDeclarationDetails . MUTABLE_OK ;
261
+ ? TypeReadonlynessDetails . MUTABLE_NOT_OK
262
+ : TypeReadonlynessDetails . MUTABLE_OK ;
238
263
}
239
264
240
265
return readonly
241
- ? TypeAliasDeclarationDetails . READONLY_OK
242
- : TypeAliasDeclarationDetails . READONLY_NOT_OK ;
266
+ ? TypeReadonlynessDetails . READONLY_OK
267
+ : TypeReadonlynessDetails . READONLY_NOT_OK ;
243
268
}
244
269
245
270
/**
246
271
* Check if the given TypeReference violates this rule.
247
272
*/
248
273
function checkTypeAliasDeclaration (
249
- node : TSESTree . TSTypeAliasDeclaration ,
274
+ node : TSESTree . TSInterfaceDeclaration | TSESTree . TSTypeAliasDeclaration ,
250
275
context : RuleContext < keyof typeof errorMessages , Options > ,
251
276
options : Options
252
277
) : RuleResult < keyof typeof errorMessages , Options > {
278
+ if ( options . ignoreInterface && isTSInterfaceDeclaration ( node ) ) {
279
+ return {
280
+ context,
281
+ descriptors : [ ] ,
282
+ } ;
283
+ }
284
+
253
285
const details = getTypeAliasDeclarationDetails ( node , context , options ) ;
254
286
255
287
switch ( details ) {
256
- case TypeAliasDeclarationDetails . NEEDS_EXPLICIT_MARKING : {
288
+ case TypeReadonlynessDetails . NEEDS_EXPLICIT_MARKING : {
257
289
return {
258
290
context,
259
291
descriptors : [
@@ -264,7 +296,7 @@ function checkTypeAliasDeclaration(
264
296
] ,
265
297
} ;
266
298
}
267
- case TypeAliasDeclarationDetails . ERROR_MUTABLE_READONLY : {
299
+ case TypeReadonlynessDetails . ERROR_MUTABLE_READONLY : {
268
300
return {
269
301
context,
270
302
descriptors : [
@@ -275,7 +307,7 @@ function checkTypeAliasDeclaration(
275
307
] ,
276
308
} ;
277
309
}
278
- case TypeAliasDeclarationDetails . MUTABLE_NOT_OK : {
310
+ case TypeReadonlynessDetails . MUTABLE_NOT_OK : {
279
311
return {
280
312
context,
281
313
descriptors : [
@@ -286,7 +318,7 @@ function checkTypeAliasDeclaration(
286
318
] ,
287
319
} ;
288
320
}
289
- case TypeAliasDeclarationDetails . READONLY_NOT_OK : {
321
+ case TypeReadonlynessDetails . READONLY_NOT_OK : {
290
322
return {
291
323
context,
292
324
descriptors : [
@@ -314,19 +346,10 @@ function checkArrayOrTupleType(
314
346
context : RuleContext < keyof typeof errorMessages , Options > ,
315
347
options : Options
316
348
) : RuleResult < keyof typeof errorMessages , Options > {
317
- const typeAlias = getParentTypeAliasDeclaration ( node ) ;
318
-
319
- if ( typeAlias === null ) {
320
- return {
321
- context,
322
- descriptors : [ ] ,
323
- } ;
324
- }
325
-
326
- const details = getTypeAliasDeclarationDetails ( typeAlias , context , options ) ;
349
+ const details = getTypeAliasDeclarationDetails ( node , context , options ) ;
327
350
328
351
switch ( details ) {
329
- case TypeAliasDeclarationDetails . READONLY_NOT_OK : {
352
+ case TypeReadonlynessDetails . READONLY_NOT_OK : {
330
353
return {
331
354
context,
332
355
descriptors :
@@ -368,19 +391,10 @@ function checkMappedType(
368
391
context : RuleContext < keyof typeof errorMessages , Options > ,
369
392
options : Options
370
393
) : RuleResult < keyof typeof errorMessages , Options > {
371
- const typeAlias = getParentTypeAliasDeclaration ( node ) ;
372
-
373
- if ( typeAlias === null ) {
374
- return {
375
- context,
376
- descriptors : [ ] ,
377
- } ;
378
- }
379
-
380
- const details = getTypeAliasDeclarationDetails ( typeAlias , context , options ) ;
394
+ const details = getTypeAliasDeclarationDetails ( node , context , options ) ;
381
395
382
396
switch ( details ) {
383
- case TypeAliasDeclarationDetails . READONLY_NOT_OK : {
397
+ case TypeReadonlynessDetails . READONLY_NOT_OK : {
384
398
return {
385
399
context,
386
400
descriptors :
@@ -423,19 +437,10 @@ function checkTypeReference(
423
437
} ;
424
438
}
425
439
426
- const typeAlias = getParentTypeAliasDeclaration ( node ) ;
427
-
428
- if ( typeAlias === null ) {
429
- return {
430
- context,
431
- descriptors : [ ] ,
432
- } ;
433
- }
434
-
435
- const details = getTypeAliasDeclarationDetails ( typeAlias , context , options ) ;
440
+ const details = getTypeAliasDeclarationDetails ( node , context , options ) ;
436
441
437
442
switch ( details ) {
438
- case TypeAliasDeclarationDetails . READONLY_NOT_OK : {
443
+ case TypeReadonlynessDetails . READONLY_NOT_OK : {
439
444
const immutableType = mutableToImmutableTypes . get ( node . typeName . name ) ;
440
445
441
446
return {
@@ -473,19 +478,10 @@ function checkProperty(
473
478
context : RuleContext < keyof typeof errorMessages , Options > ,
474
479
options : Options
475
480
) : RuleResult < keyof typeof errorMessages , Options > {
476
- const typeAlias = getParentTypeAliasDeclaration ( node ) ;
477
-
478
- if ( typeAlias === null ) {
479
- return {
480
- context,
481
- descriptors : [ ] ,
482
- } ;
483
- }
484
-
485
- const details = getTypeAliasDeclarationDetails ( typeAlias , context , options ) ;
481
+ const details = getTypeAliasDeclarationDetails ( node , context , options ) ;
486
482
487
483
switch ( details ) {
488
- case TypeAliasDeclarationDetails . READONLY_NOT_OK : {
484
+ case TypeReadonlynessDetails . READONLY_NOT_OK : {
489
485
return {
490
486
context,
491
487
descriptors :
@@ -512,6 +508,45 @@ function checkProperty(
512
508
}
513
509
}
514
510
511
+ /**
512
+ * Get the type alias or interface that the given node is in.
513
+ */
514
+ function getTypeDeclaration (
515
+ node : TSESTree . Node
516
+ ) : TSESTree . TSInterfaceDeclaration | TSESTree . TSTypeAliasDeclaration | null {
517
+ if ( isTSTypeAliasDeclaration ( node ) || isTSInterfaceDeclaration ( node ) ) {
518
+ return node ;
519
+ }
520
+
521
+ return ( getAncestorOfType (
522
+ ( n ) : n is TSESTree . Node =>
523
+ n . parent !== undefined &&
524
+ n . parent !== null &&
525
+ ( ( isTSTypeAliasDeclaration ( n . parent ) && n . parent . typeAnnotation === n ) ||
526
+ ( isTSInterfaceDeclaration ( n . parent ) && n . parent . body === n ) ) ,
527
+ node
528
+ ) ?. parent ?? null ) as
529
+ | TSESTree . TSInterfaceDeclaration
530
+ | TSESTree . TSTypeAliasDeclaration
531
+ | null ;
532
+ }
533
+
534
+ /**
535
+ * Get the parent Index Signature that the given node is in.
536
+ */
537
+ function getParentIndexSignature (
538
+ node : TSESTree . Node
539
+ ) : TSESTree . TSIndexSignature | null {
540
+ return ( getAncestorOfType (
541
+ ( n ) : n is TSESTree . Node =>
542
+ n . parent !== undefined &&
543
+ n . parent !== null &&
544
+ isTSIndexSignature ( n . parent ) &&
545
+ n . parent . typeAnnotation === n ,
546
+ node
547
+ ) ?. parent ?? null ) as TSESTree . TSIndexSignature | null ;
548
+ }
549
+
515
550
// Create the rule.
516
551
export const rule = createRule < keyof typeof errorMessages , Options > (
517
552
name ,
@@ -520,6 +555,7 @@ export const rule = createRule<keyof typeof errorMessages, Options>(
520
555
{
521
556
TSArrayType : checkArrayOrTupleType ,
522
557
TSIndexSignature : checkProperty ,
558
+ TSInterfaceDeclaration : checkTypeAliasDeclaration ,
523
559
TSMappedType : checkMappedType ,
524
560
TSParameterProperty : checkProperty ,
525
561
TSPropertySignature : checkProperty ,
0 commit comments