4
4
isUnionOrIntersectionType ,
5
5
unionTypeParts ,
6
6
isPropertyReadonlyInType ,
7
+ isSymbolFlagSet ,
7
8
} from 'tsutils' ;
8
9
import * as ts from 'typescript' ;
9
10
import { getTypeOfPropertyOfType , nullThrows , NullThrowsReasons } from '.' ;
@@ -17,9 +18,18 @@ const enum Readonlyness {
17
18
Readonly = 3 ,
18
19
}
19
20
21
+ export interface ReadonlynessOptions {
22
+ readonly treatMethodsAsReadonly : boolean ;
23
+ }
24
+
25
+ function hasSymbol ( node : ts . Node ) : node is ts . Node & { symbol : ts . Symbol } {
26
+ return Object . prototype . hasOwnProperty . call ( node , 'symbol' ) ;
27
+ }
28
+
20
29
function isTypeReadonlyArrayOrTuple (
21
30
checker : ts . TypeChecker ,
22
31
type : ts . Type ,
32
+ options : ReadonlynessOptions ,
23
33
seenTypes : Set < ts . Type > ,
24
34
) : Readonlyness {
25
35
function checkTypeArguments ( arrayType : ts . TypeReference ) : Readonlyness {
@@ -35,7 +45,7 @@ function isTypeReadonlyArrayOrTuple(
35
45
if (
36
46
typeArguments . some (
37
47
typeArg =>
38
- isTypeReadonlyRecurser ( checker , typeArg , seenTypes ) ===
48
+ isTypeReadonlyRecurser ( checker , typeArg , options , seenTypes ) ===
39
49
Readonlyness . Mutable ,
40
50
)
41
51
) {
@@ -71,6 +81,7 @@ function isTypeReadonlyArrayOrTuple(
71
81
function isTypeReadonlyObject (
72
82
checker : ts . TypeChecker ,
73
83
type : ts . Type ,
84
+ options : ReadonlynessOptions ,
74
85
seenTypes : Set < ts . Type > ,
75
86
) : Readonlyness {
76
87
function checkIndexSignature ( kind : ts . IndexKind ) : Readonlyness {
@@ -88,7 +99,18 @@ function isTypeReadonlyObject(
88
99
if ( properties . length ) {
89
100
// ensure the properties are marked as readonly
90
101
for ( const property of properties ) {
91
- if ( ! isPropertyReadonlyInType ( type , property . getEscapedName ( ) , checker ) ) {
102
+ if (
103
+ ! (
104
+ isPropertyReadonlyInType ( type , property . getEscapedName ( ) , checker ) ||
105
+ ( options . treatMethodsAsReadonly &&
106
+ property . valueDeclaration !== undefined &&
107
+ hasSymbol ( property . valueDeclaration ) &&
108
+ isSymbolFlagSet (
109
+ property . valueDeclaration . symbol ,
110
+ ts . SymbolFlags . Method ,
111
+ ) )
112
+ )
113
+ ) {
92
114
return Readonlyness . Mutable ;
93
115
}
94
116
}
@@ -112,7 +134,7 @@ function isTypeReadonlyObject(
112
134
}
113
135
114
136
if (
115
- isTypeReadonlyRecurser ( checker , propertyType , seenTypes ) ===
137
+ isTypeReadonlyRecurser ( checker , propertyType , options , seenTypes ) ===
116
138
Readonlyness . Mutable
117
139
) {
118
140
return Readonlyness . Mutable ;
@@ -137,14 +159,15 @@ function isTypeReadonlyObject(
137
159
function isTypeReadonlyRecurser (
138
160
checker : ts . TypeChecker ,
139
161
type : ts . Type ,
162
+ options : ReadonlynessOptions ,
140
163
seenTypes : Set < ts . Type > ,
141
164
) : Readonlyness . Readonly | Readonlyness . Mutable {
142
165
seenTypes . add ( type ) ;
143
166
144
167
if ( isUnionType ( type ) ) {
145
168
// all types in the union must be readonly
146
169
const result = unionTypeParts ( type ) . every ( t =>
147
- isTypeReadonlyRecurser ( checker , t , seenTypes ) ,
170
+ isTypeReadonlyRecurser ( checker , t , options , seenTypes ) ,
148
171
) ;
149
172
const readonlyness = result ? Readonlyness . Readonly : Readonlyness . Mutable ;
150
173
return readonlyness ;
@@ -164,12 +187,22 @@ function isTypeReadonlyRecurser(
164
187
return Readonlyness . Readonly ;
165
188
}
166
189
167
- const isReadonlyArray = isTypeReadonlyArrayOrTuple ( checker , type , seenTypes ) ;
190
+ const isReadonlyArray = isTypeReadonlyArrayOrTuple (
191
+ checker ,
192
+ type ,
193
+ options ,
194
+ seenTypes ,
195
+ ) ;
168
196
if ( isReadonlyArray !== Readonlyness . UnknownType ) {
169
197
return isReadonlyArray ;
170
198
}
171
199
172
- const isReadonlyObject = isTypeReadonlyObject ( checker , type , seenTypes ) ;
200
+ const isReadonlyObject = isTypeReadonlyObject (
201
+ checker ,
202
+ type ,
203
+ options ,
204
+ seenTypes ,
205
+ ) ;
173
206
/* istanbul ignore else */ if (
174
207
isReadonlyObject !== Readonlyness . UnknownType
175
208
) {
@@ -182,9 +215,14 @@ function isTypeReadonlyRecurser(
182
215
/**
183
216
* Checks if the given type is readonly
184
217
*/
185
- function isTypeReadonly ( checker : ts . TypeChecker , type : ts . Type ) : boolean {
218
+ function isTypeReadonly (
219
+ checker : ts . TypeChecker ,
220
+ type : ts . Type ,
221
+ options : ReadonlynessOptions ,
222
+ ) : boolean {
186
223
return (
187
- isTypeReadonlyRecurser ( checker , type , new Set ( ) ) === Readonlyness . Readonly
224
+ isTypeReadonlyRecurser ( checker , type , options , new Set ( ) ) ===
225
+ Readonlyness . Readonly
188
226
) ;
189
227
}
190
228
0 commit comments