0
# Property Type Utilities
1
2
Functions for extracting and analyzing property types from complex type structures. These utilities help understand object properties, their types, and relationships within TypeScript's type system.
3
4
## Capabilities
5
6
### Property Type Extraction
7
8
Functions for getting property types from complex objects and type structures.
9
10
```typescript { .api }
11
/**
12
* Gets the type of a property by name, handling symbolic names correctly
13
*/
14
function getTypeOfPropertyOfName(
15
checker: ts.TypeChecker,
16
type: ts.Type,
17
name: string,
18
escapedName?: ts.__String
19
): ts.Type | undefined;
20
21
/**
22
* Gets the type of a property using a symbol
23
*/
24
function getTypeOfPropertyOfType(
25
checker: ts.TypeChecker,
26
type: ts.Type,
27
property: ts.Symbol
28
): ts.Type | undefined;
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";
35
36
// In an ESLint rule analyzing object property access
37
export default {
38
create(context) {
39
const services = context.parserServices;
40
const checker = services.program.getTypeChecker();
41
42
return {
43
MemberExpression(node) {
44
if (node.computed === false && node.property.type === "Identifier") {
45
const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object);
46
const objectType = checker.getTypeAtLocation(objectTsNode);
47
48
// Get property type by name
49
const propertyType = getTypeOfPropertyOfName(
50
checker,
51
objectType,
52
node.property.name
53
);
54
55
if (propertyType) {
56
const propertyTypeName = checker.typeToString(propertyType);
57
console.log(`Property ${node.property.name} has type: ${propertyTypeName}`);
58
59
// Example: Check for specific property types
60
if (checker.typeToString(propertyType) === "any") {
61
context.report({
62
node: node.property,
63
messageId: "anyPropertyType"
64
});
65
}
66
} else {
67
context.report({
68
node: node.property,
69
messageId: "unknownProperty",
70
data: { propertyName: node.property.name }
71
});
72
}
73
}
74
},
75
76
TSPropertySignature(node) {
77
if (node.key.type === "Identifier") {
78
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
79
const symbol = checker.getSymbolAtLocation(tsNode);
80
81
if (symbol && symbol.parent) {
82
const parentType = checker.getTypeOfSymbolAtLocation(symbol.parent, tsNode);
83
84
// Get property type using symbol
85
const propertyType = getTypeOfPropertyOfType(checker, parentType, symbol);
86
87
if (propertyType) {
88
console.log(`Property symbol type: ${checker.typeToString(propertyType)}`);
89
}
90
}
91
}
92
}
93
};
94
}
95
};
96
```
97
98
## Advanced Property Analysis Patterns
99
100
### Object Property Analysis
101
102
```typescript
103
// Example: Comprehensive object property analysis
104
import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";
105
106
interface PropertyInfo {
107
name: string;
108
type: string;
109
isOptional: boolean;
110
isReadonly: boolean;
111
symbol?: ts.Symbol;
112
}
113
114
function analyzeObjectProperties(
115
checker: ts.TypeChecker,
116
objectType: ts.Type
117
): PropertyInfo[] {
118
const properties: PropertyInfo[] = [];
119
const propertySymbols = checker.getPropertiesOfType(objectType);
120
121
propertySymbols.forEach(symbol => {
122
const propertyType = getTypeOfPropertyOfType(checker, objectType, symbol);
123
124
if (propertyType) {
125
const isOptional = (symbol.flags & ts.SymbolFlags.Optional) !== 0;
126
const isReadonly = symbol.valueDeclaration ?
127
ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Readonly : false;
128
129
properties.push({
130
name: symbol.name,
131
type: checker.typeToString(propertyType),
132
isOptional,
133
isReadonly: !!isReadonly,
134
symbol
135
});
136
}
137
});
138
139
return properties;
140
}
141
142
// Usage in ESLint rule
143
function validateObjectStructure(
144
services: ParserServicesWithTypeInformation,
145
objectLiteral: TSESTree.ObjectExpression
146
) {
147
const checker = services.program.getTypeChecker();
148
const tsNode = services.esTreeNodeToTSNodeMap.get(objectLiteral);
149
const objectType = checker.getTypeAtLocation(tsNode);
150
151
const properties = analyzeObjectProperties(checker, objectType);
152
153
properties.forEach(prop => {
154
if (prop.type === "any") {
155
console.log(`Property ${prop.name} has any type`);
156
}
157
158
if (!prop.isOptional && prop.type.includes("undefined")) {
159
console.log(`Required property ${prop.name} includes undefined`);
160
}
161
});
162
}
163
```
164
165
### Dynamic Property Access Analysis
166
167
```typescript
168
// Example: Analyzing dynamic property access patterns
169
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";
170
171
function analyzeDynamicPropertyAccess(
172
services: ParserServicesWithTypeInformation,
173
memberExpression: TSESTree.MemberExpression
174
): {
175
isValidAccess: boolean;
176
propertyType: string | null;
177
suggestions: string[];
178
} {
179
const checker = services.program.getTypeChecker();
180
const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);
181
const objectType = checker.getTypeAtLocation(objectTsNode);
182
183
let propertyName: string;
184
let isValidAccess = false;
185
let propertyType: string | null = null;
186
const suggestions: string[] = [];
187
188
if (memberExpression.computed && memberExpression.property.type === "Literal") {
189
// obj["propertyName"]
190
propertyName = String(memberExpression.property.value);
191
} else if (!memberExpression.computed && memberExpression.property.type === "Identifier") {
192
// obj.propertyName
193
propertyName = memberExpression.property.name;
194
} else {
195
return { isValidAccess: false, propertyType: null, suggestions: [] };
196
}
197
198
// Check if property exists
199
const type = getTypeOfPropertyOfName(checker, objectType, propertyName);
200
201
if (type) {
202
isValidAccess = true;
203
propertyType = checker.typeToString(type);
204
} else {
205
// Property doesn't exist, suggest similar properties
206
const allProperties = checker.getPropertiesOfType(objectType);
207
208
allProperties.forEach(symbol => {
209
const similarity = calculateSimilarity(propertyName, symbol.name);
210
if (similarity > 0.6) { // Basic similarity threshold
211
suggestions.push(symbol.name);
212
}
213
});
214
}
215
216
return { isValidAccess, propertyType, suggestions };
217
}
218
219
function calculateSimilarity(a: string, b: string): number {
220
// Simple similarity calculation (Levenshtein-based)
221
const longer = a.length > b.length ? a : b;
222
const shorter = a.length > b.length ? b : a;
223
224
if (longer.length === 0) return 1.0;
225
226
const distance = levenshteinDistance(longer, shorter);
227
return (longer.length - distance) / longer.length;
228
}
229
230
function levenshteinDistance(str1: string, str2: string): number {
231
const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
232
233
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
234
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
235
236
for (let j = 1; j <= str2.length; j++) {
237
for (let i = 1; i <= str1.length; i++) {
238
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
239
matrix[j][i] = Math.min(
240
matrix[j][i - 1] + 1,
241
matrix[j - 1][i] + 1,
242
matrix[j - 1][i - 1] + indicator
243
);
244
}
245
}
246
247
return matrix[str2.length][str1.length];
248
}
249
```
250
251
### Nested Property Type Analysis
252
253
```typescript
254
// Example: Deep property type analysis for nested objects
255
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";
256
257
interface NestedPropertyPath {
258
path: string[];
259
type: string;
260
depth: number;
261
}
262
263
function analyzeNestedProperties(
264
checker: ts.TypeChecker,
265
rootType: ts.Type,
266
maxDepth: number = 3
267
): NestedPropertyPath[] {
268
const paths: NestedPropertyPath[] = [];
269
270
function traverse(type: ts.Type, currentPath: string[], depth: number) {
271
if (depth >= maxDepth) return;
272
273
const properties = checker.getPropertiesOfType(type);
274
275
properties.forEach(symbol => {
276
const propertyType = getTypeOfPropertyOfName(checker, type, symbol.name);
277
278
if (propertyType) {
279
const newPath = [...currentPath, symbol.name];
280
const typeString = checker.typeToString(propertyType);
281
282
paths.push({
283
path: newPath,
284
type: typeString,
285
depth: depth + 1
286
});
287
288
// Recursively analyze object properties
289
const propertySymbols = checker.getPropertiesOfType(propertyType);
290
if (propertySymbols.length > 0 && depth < maxDepth - 1) {
291
traverse(propertyType, newPath, depth + 1);
292
}
293
}
294
});
295
}
296
297
traverse(rootType, [], 0);
298
return paths;
299
}
300
301
// Usage example
302
function validateNestedObjectAccess(
303
services: ParserServicesWithTypeInformation,
304
node: TSESTree.MemberExpression
305
) {
306
const checker = services.program.getTypeChecker();
307
308
// Build property access path
309
const path: string[] = [];
310
let current: TSESTree.Node = node;
311
312
while (current.type === "MemberExpression") {
313
if (!current.computed && current.property.type === "Identifier") {
314
path.unshift(current.property.name);
315
}
316
current = current.object;
317
}
318
319
// Get root object type
320
const rootTsNode = services.esTreeNodeToTSNodeMap.get(current);
321
const rootType = checker.getTypeAtLocation(rootTsNode);
322
323
// Validate each step in the path
324
let currentType = rootType;
325
326
for (const propertyName of path) {
327
const propertyType = getTypeOfPropertyOfName(checker, currentType, propertyName);
328
329
if (!propertyType) {
330
console.log(`Property ${propertyName} not found in path ${path.join('.')}`);
331
break;
332
}
333
334
currentType = propertyType;
335
}
336
}
337
```
338
339
### Index Signature Analysis
340
341
```typescript
342
// Example: Analyzing index signatures and dynamic properties
343
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";
344
345
function analyzeIndexSignatures(
346
checker: ts.TypeChecker,
347
type: ts.Type
348
): {
349
hasStringIndex: boolean;
350
hasNumberIndex: boolean;
351
stringIndexType?: string;
352
numberIndexType?: string;
353
dynamicPropertyAccess: boolean;
354
} {
355
const stringIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.String);
356
const numberIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
357
358
return {
359
hasStringIndex: !!stringIndexType,
360
hasNumberIndex: !!numberIndexType,
361
stringIndexType: stringIndexType ? checker.typeToString(stringIndexType) : undefined,
362
numberIndexType: numberIndexType ? checker.typeToString(numberIndexType) : undefined,
363
dynamicPropertyAccess: !!(stringIndexType || numberIndexType)
364
};
365
}
366
367
// Check if dynamic property access is safe
368
function validateDynamicAccess(
369
services: ParserServicesWithTypeInformation,
370
memberExpression: TSESTree.MemberExpression
371
) {
372
if (!memberExpression.computed) return;
373
374
const checker = services.program.getTypeChecker();
375
const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);
376
const objectType = checker.getTypeAtLocation(objectTsNode);
377
378
const indexInfo = analyzeIndexSignatures(checker, objectType);
379
380
if (!indexInfo.dynamicPropertyAccess) {
381
console.log("Object doesn't support dynamic property access");
382
return;
383
}
384
385
// Validate property key type
386
const propertyTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.property);
387
const propertyType = checker.getTypeAtLocation(propertyTsNode);
388
const propertyTypeString = checker.typeToString(propertyType);
389
390
if (indexInfo.hasStringIndex && propertyTypeString !== "string") {
391
console.log("String index signature requires string key");
392
}
393
394
if (indexInfo.hasNumberIndex && propertyTypeString !== "number") {
395
console.log("Number index signature requires number key");
396
}
397
}
398
```