0
# Type Safety Utilities
1
2
Functions for detecting potentially unsafe type operations, particularly around readonly properties and any type assignments. These utilities help identify code patterns that may lead to runtime errors or violate type safety guarantees.
3
4
## Capabilities
5
6
### Readonly Type Analysis
7
8
Functions for analyzing whether types and their properties are readonly, crucial for understanding mutability guarantees.
9
10
```typescript { .api }
11
interface ReadonlynessOptions {
12
readonly allow?: TypeOrValueSpecifier[];
13
readonly treatMethodsAsReadonly?: boolean;
14
}
15
16
/**
17
* Checks if the given type is readonly. Handles arrays, tuples, objects,
18
* properties, index signatures, unions, intersections, and conditional types.
19
*/
20
function isTypeReadonly(
21
program: ts.Program,
22
type: ts.Type,
23
options?: ReadonlynessOptions
24
): boolean;
25
```
26
27
**Constants:**
28
29
```typescript { .api }
30
const readonlynessOptionsDefaults: ReadonlynessOptions;
31
const readonlynessOptionsSchema: JSONSchema4;
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
import { isTypeReadonly, readonlynessOptionsDefaults } from "@typescript-eslint/type-utils";
38
39
// In an ESLint rule checking for readonly violations
40
export default {
41
create(context) {
42
const services = context.parserServices;
43
const program = services.program;
44
45
return {
46
AssignmentExpression(node) {
47
if (node.left.type === "MemberExpression") {
48
const tsNode = services.esTreeNodeToTSNodeMap.get(node.left.object);
49
const type = program.getTypeChecker().getTypeAtLocation(tsNode);
50
51
if (isTypeReadonly(program, type)) {
52
context.report({
53
node,
54
messageId: "readonlyViolation"
55
});
56
}
57
}
58
},
59
60
CallExpression(node) {
61
// Check method calls on readonly types
62
if (node.callee.type === "MemberExpression") {
63
const tsObject = services.esTreeNodeToTSNodeMap.get(node.callee.object);
64
const objectType = program.getTypeChecker().getTypeAtLocation(tsObject);
65
66
const options = {
67
...readonlynessOptionsDefaults,
68
treatMethodsAsReadonly: true
69
};
70
71
if (isTypeReadonly(program, objectType, options)) {
72
// Check if this method mutates the object
73
const methodName = node.callee.property.name;
74
if (['push', 'pop', 'shift', 'unshift', 'splice'].includes(methodName)) {
75
context.report({
76
node,
77
messageId: "mutatingMethodOnReadonly"
78
});
79
}
80
}
81
}
82
}
83
};
84
}
85
};
86
```
87
88
### Unsafe Assignment Detection
89
90
Functions for detecting unsafe assignments, particularly those involving `any` types that could bypass TypeScript's type checking.
91
92
```typescript { .api }
93
/**
94
* Checks if there's an unsafe assignment of `any` to a non-`any` type.
95
* Also checks generic positions for unsafe sub-assignments.
96
* @returns false if safe, or an object with the two types if unsafe
97
*/
98
function isUnsafeAssignment(
99
type: ts.Type,
100
receiver: ts.Type,
101
checker: ts.TypeChecker,
102
senderNode: TSESTree.Node | null
103
): false | { receiver: ts.Type; sender: ts.Type };
104
```
105
106
**Usage Examples:**
107
108
```typescript
109
import { isUnsafeAssignment } from "@typescript-eslint/type-utils";
110
111
// In an ESLint rule detecting unsafe assignments
112
export default {
113
create(context) {
114
const services = context.parserServices;
115
const checker = services.program.getTypeChecker();
116
117
return {
118
AssignmentExpression(node) {
119
const leftTsNode = services.esTreeNodeToTSNodeMap.get(node.left);
120
const rightTsNode = services.esTreeNodeToTSNodeMap.get(node.right);
121
122
const leftType = checker.getTypeAtLocation(leftTsNode);
123
const rightType = checker.getTypeAtLocation(rightTsNode);
124
125
const unsafeAssignment = isUnsafeAssignment(rightType, leftType, checker, node.right);
126
127
if (unsafeAssignment) {
128
context.report({
129
node,
130
messageId: "unsafeAnyAssignment",
131
data: {
132
sender: checker.typeToString(unsafeAssignment.sender),
133
receiver: checker.typeToString(unsafeAssignment.receiver)
134
}
135
});
136
}
137
},
138
139
VariableDeclarator(node) {
140
if (node.init && node.id.typeAnnotation) {
141
const initTsNode = services.esTreeNodeToTSNodeMap.get(node.init);
142
const idTsNode = services.esTreeNodeToTSNodeMap.get(node.id);
143
144
const initType = checker.getTypeAtLocation(initTsNode);
145
const declaredType = checker.getTypeAtLocation(idTsNode);
146
147
const unsafeAssignment = isUnsafeAssignment(initType, declaredType, checker, node.init);
148
149
if (unsafeAssignment) {
150
context.report({
151
node: node.init,
152
messageId: "unsafeAnyInitialization"
153
});
154
}
155
}
156
}
157
};
158
}
159
};
160
```
161
162
## Advanced Safety Analysis Patterns
163
164
### Readonly Options Configuration
165
166
```typescript
167
// Example: Configuring readonly analysis with custom options
168
import { isTypeReadonly, TypeOrValueSpecifier } from "@typescript-eslint/type-utils";
169
170
const customReadonlyOptions = {
171
allow: [
172
// Allow specific types to be treated as non-readonly
173
{ from: 'lib', name: 'Array' },
174
{ from: 'package', name: 'MutableArray', package: 'custom-utils' }
175
] as TypeOrValueSpecifier[],
176
treatMethodsAsReadonly: false
177
};
178
179
function checkCustomReadonly(program: ts.Program, type: ts.Type): boolean {
180
return isTypeReadonly(program, type, customReadonlyOptions);
181
}
182
```
183
184
### Generic Unsafe Assignment Detection
185
186
```typescript
187
// Example: Detecting unsafe assignments in generic contexts
188
import { isUnsafeAssignment } from "@typescript-eslint/type-utils";
189
190
function analyzeGenericAssignment(
191
services: ParserServicesWithTypeInformation,
192
node: TSESTree.CallExpression
193
) {
194
const checker = services.program.getTypeChecker();
195
196
// Check each argument for unsafe assignments
197
node.arguments.forEach((arg, index) => {
198
const argTsNode = services.esTreeNodeToTSNodeMap.get(arg);
199
const argType = checker.getTypeAtLocation(argTsNode);
200
201
// Get the expected parameter type
202
const callTsNode = services.esTreeNodeToTSNodeMap.get(node);
203
const signature = checker.getResolvedSignature(callTsNode);
204
205
if (signature && signature.parameters[index]) {
206
const paramType = checker.getTypeOfSymbolAtLocation(
207
signature.parameters[index],
208
callTsNode
209
);
210
211
const unsafeAssignment = isUnsafeAssignment(argType, paramType, checker, arg);
212
213
if (unsafeAssignment) {
214
console.log(`Unsafe assignment in argument ${index}`);
215
}
216
}
217
});
218
}
219
```
220
221
### Complex Readonly Analysis
222
223
```typescript
224
// Example: Deep readonly analysis for nested structures
225
import { isTypeReadonly } from "@typescript-eslint/type-utils";
226
227
function analyzeNestedReadonly(
228
program: ts.Program,
229
type: ts.Type,
230
checker: ts.TypeChecker
231
): { isReadonly: boolean; issues: string[] } {
232
const issues: string[] = [];
233
234
// Check top-level readonly
235
const isTopLevelReadonly = isTypeReadonly(program, type);
236
237
if (!isTopLevelReadonly) {
238
issues.push("Top-level type is not readonly");
239
}
240
241
// Check properties for nested structures
242
const properties = checker.getPropertiesOfType(type);
243
244
properties.forEach(prop => {
245
const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration!);
246
247
if (!isTypeReadonly(program, propType)) {
248
issues.push(`Property '${prop.name}' is not readonly`);
249
}
250
});
251
252
return {
253
isReadonly: isTopLevelReadonly && issues.length === 1, // Only top-level issue
254
issues
255
};
256
}
257
```
258
259
### Safe Assignment Validation
260
261
```typescript
262
// Example: Comprehensive assignment safety checking
263
import { isUnsafeAssignment, isTypeReadonly } from "@typescript-eslint/type-utils";
264
265
function validateAssignmentSafety(
266
services: ParserServicesWithTypeInformation,
267
assignment: TSESTree.AssignmentExpression
268
): { safe: boolean; issues: string[] } {
269
const checker = services.program.getTypeChecker();
270
const program = services.program;
271
const issues: string[] = [];
272
273
const leftTsNode = services.esTreeNodeToTSNodeMap.get(assignment.left);
274
const rightTsNode = services.esTreeNodeToTSNodeMap.get(assignment.right);
275
276
const leftType = checker.getTypeAtLocation(leftTsNode);
277
const rightType = checker.getTypeAtLocation(rightTsNode);
278
279
// Check for unsafe any assignments
280
const unsafeAssignment = isUnsafeAssignment(rightType, leftType, checker, assignment.right);
281
if (unsafeAssignment) {
282
issues.push("Unsafe any assignment detected");
283
}
284
285
// Check readonly violations
286
if (assignment.left.type === "MemberExpression") {
287
const objectTsNode = services.esTreeNodeToTSNodeMap.get(assignment.left.object);
288
const objectType = checker.getTypeAtLocation(objectTsNode);
289
290
if (isTypeReadonly(program, objectType)) {
291
issues.push("Assignment to readonly property");
292
}
293
}
294
295
return {
296
safe: issues.length === 0,
297
issues
298
};
299
}
300
```