0
# Node Analysis
1
2
Node Analysis is the core capability of ts-api-utils, providing comprehensive utilities for working with TypeScript AST (Abstract Syntax Tree) nodes. This module offers type guards, access utilities, and helper functions that are essential for building robust TypeScript tooling, linters, and code analysis tools.
3
4
## Overview
5
6
TypeScript's AST represents source code as a tree of nodes, where each node corresponds to a construct in the language (expressions, statements, declarations, etc.). The Node Analysis module provides three main categories of utilities:
7
8
1. **Type Guards**: Functions that test whether a node is of a specific type
9
2. **Access Utilities**: Functions for determining how expressions are accessed (read, write, delete)
10
3. **Node Utilities**: Helper functions for working with nodes and their properties
11
12
Understanding AST nodes and their relationships is fundamental to TypeScript tooling development, as it allows tools to programmatically analyze and manipulate code structure.
13
14
## Core Concepts
15
16
### AST Nodes
17
18
Every construct in TypeScript code is represented as a node in the AST. Nodes have:
19
- A `kind` property that identifies the node type
20
- Child nodes that represent nested constructs
21
- Properties specific to their node type
22
- Parent relationships for traversing up the tree
23
24
### Type Guards
25
26
Type guards are functions that narrow TypeScript's type system by checking if a node matches a specific pattern. They return `node is SpecificNodeType` to provide type safety:
27
28
```typescript
29
declare const node: ts.Node;
30
31
if (isVariableDeclaration(node)) {
32
// TypeScript now knows node is ts.VariableDeclaration
33
console.log(node.name.getText());
34
}
35
```
36
37
### Node Access Patterns
38
39
Expressions in TypeScript can be accessed in different ways:
40
- **Read**: Getting the value (e.g., `console.log(x)`)
41
- **Write**: Setting the value (e.g., `x = 5`)
42
- **Delete**: Removing the property (e.g., `delete obj.prop`)
43
- **ReadWrite**: Both reading and writing (e.g., `x++`)
44
45
## Node Type Guards
46
47
Type guards enable safe type narrowing for AST nodes. The library provides guards for single nodes, union types, and compound conditions.
48
49
### Single Node Type Guards
50
51
These functions test for specific keyword and token types:
52
53
```typescript
54
function isAbstractKeyword(node: ts.Node): node is ts.AbstractKeyword { .api }
55
function isAccessorKeyword(node: ts.Node): node is ts.AccessorKeyword { .api }
56
function isAnyKeyword(node: ts.Node): node is AnyKeyword { .api }
57
function isAssertKeyword(node: ts.Node): node is ts.AssertKeyword { .api }
58
function isAssertsKeyword(node: ts.Node): node is ts.AssertsKeyword { .api }
59
function isAsyncKeyword(node: ts.Node): node is ts.AsyncKeyword { .api }
60
function isAwaitKeyword(node: ts.Node): node is ts.AwaitKeyword { .api }
61
function isBigIntKeyword(node: ts.Node): node is BigIntKeyword { .api }
62
function isBooleanKeyword(node: ts.Node): node is BooleanKeyword { .api }
63
function isColonToken(node: ts.Node): node is ts.ColonToken { .api }
64
function isConstKeyword(node: ts.Node): node is ts.ConstKeyword { .api }
65
function isDeclareKeyword(node: ts.Node): node is ts.DeclareKeyword { .api }
66
function isDefaultKeyword(node: ts.Node): node is ts.DefaultKeyword { .api }
67
function isDotToken(node: ts.Node): node is ts.DotToken { .api }
68
function isEndOfFileToken(node: ts.Node): node is ts.EndOfFileToken { .api }
69
function isEqualsGreaterThanToken(node: ts.Node): node is ts.EqualsGreaterThanToken { .api }
70
function isEqualsToken(node: ts.Node): node is ts.EqualsToken { .api }
71
function isExclamationToken(node: ts.Node): node is ts.ExclamationToken { .api }
72
function isExportKeyword(node: ts.Node): node is ts.ExportKeyword { .api }
73
function isFalseKeyword(node: ts.Node): node is FalseKeyword { .api }
74
function isFalseLiteral(node: ts.Node): node is ts.FalseLiteral { .api }
75
function isImportExpression(node: ts.Node): node is ts.ImportExpression { .api }
76
function isImportKeyword(node: ts.Node): node is ImportKeyword { .api }
77
function isInKeyword(node: ts.Node): node is ts.InKeyword { .api }
78
function isJSDocText(node: ts.Node): node is ts.JSDocText { .api }
79
function isJsonMinusNumericLiteral(node: ts.Node): node is ts.JsonMinusNumericLiteral { .api }
80
function isNeverKeyword(node: ts.Node): node is NeverKeyword { .api }
81
function isNullKeyword(node: ts.Node): node is NullKeyword { .api }
82
function isNullLiteral(node: ts.Node): node is ts.NullLiteral { .api }
83
function isNumberKeyword(node: ts.Node): node is NumberKeyword { .api }
84
function isObjectKeyword(node: ts.Node): node is ObjectKeyword { .api }
85
function isOutKeyword(node: ts.Node): node is ts.OutKeyword { .api }
86
function isOverrideKeyword(node: ts.Node): node is ts.OverrideKeyword { .api }
87
function isPrivateKeyword(node: ts.Node): node is ts.PrivateKeyword { .api }
88
function isProtectedKeyword(node: ts.Node): node is ts.ProtectedKeyword { .api }
89
function isPublicKeyword(node: ts.Node): node is ts.PublicKeyword { .api }
90
function isQuestionDotToken(node: ts.Node): node is ts.QuestionDotToken { .api }
91
function isQuestionToken(node: ts.Node): node is ts.QuestionToken { .api }
92
function isReadonlyKeyword(node: ts.Node): node is ts.ReadonlyKeyword { .api }
93
function isStaticKeyword(node: ts.Node): node is ts.StaticKeyword { .api }
94
function isStringKeyword(node: ts.Node): node is StringKeyword { .api }
95
function isSuperExpression(node: ts.Node): node is ts.SuperExpression { .api }
96
function isSuperKeyword(node: ts.Node): node is SuperKeyword { .api }
97
function isSymbolKeyword(node: ts.Node): node is SymbolKeyword { .api }
98
function isSyntaxList(node: ts.Node): node is ts.SyntaxList { .api }
99
function isThisExpression(node: ts.Node): node is ts.ThisExpression { .api }
100
function isThisKeyword(node: ts.Node): node is ThisKeyword { .api }
101
function isTrueKeyword(node: ts.Node): node is TrueKeyword { .api }
102
function isTrueLiteral(node: ts.Node): node is ts.TrueLiteral { .api }
103
function isUndefinedKeyword(node: ts.Node): node is UndefinedKeyword { .api }
104
function isUnknownKeyword(node: ts.Node): node is UnknownKeyword { .api }
105
function isVoidKeyword(node: ts.Node): node is VoidKeyword { .api }
106
```
107
108
#### Keyword Node Types
109
110
The library defines specific types for TypeScript keywords:
111
112
```typescript
113
type AnyKeyword = ts.KeywordToken<ts.SyntaxKind.AnyKeyword>
114
type BigIntKeyword = ts.KeywordToken<ts.SyntaxKind.BigIntKeyword>
115
type BooleanKeyword = ts.KeywordToken<ts.SyntaxKind.BooleanKeyword>
116
type FalseKeyword = ts.KeywordToken<ts.SyntaxKind.FalseKeyword>
117
type ImportKeyword = ts.KeywordToken<ts.SyntaxKind.ImportKeyword>
118
type NeverKeyword = ts.KeywordToken<ts.SyntaxKind.NeverKeyword>
119
type NullKeyword = ts.KeywordToken<ts.SyntaxKind.NullKeyword>
120
type NumberKeyword = ts.KeywordToken<ts.SyntaxKind.NumberKeyword>
121
type ObjectKeyword = ts.KeywordToken<ts.SyntaxKind.ObjectKeyword>
122
type StringKeyword = ts.KeywordToken<ts.SyntaxKind.StringKeyword>
123
type SuperKeyword = ts.KeywordToken<ts.SyntaxKind.SuperKeyword>
124
type SymbolKeyword = ts.KeywordToken<ts.SyntaxKind.SymbolKeyword>
125
type ThisKeyword = ts.KeywordToken<ts.SyntaxKind.ThisKeyword>
126
type TrueKeyword = ts.KeywordToken<ts.SyntaxKind.TrueKeyword>
127
type UndefinedKeyword = ts.KeywordToken<ts.SyntaxKind.UndefinedKeyword>
128
type UnknownKeyword = ts.KeywordToken<ts.SyntaxKind.UnknownKeyword>
129
type VoidKeyword = ts.KeywordToken<ts.SyntaxKind.VoidKeyword>
130
```
131
132
### Union Type Guards
133
134
These functions test for nodes that match multiple possible types or have certain capabilities:
135
136
```typescript
137
function hasDecorators(node: ts.Node): node is ts.HasDecorators { .api }
138
function hasExpressionInitializer(node: ts.Node): node is ts.HasExpressionInitializer { .api }
139
function hasInitializer(node: ts.Node): node is ts.HasInitializer { .api }
140
function hasJSDoc(node: ts.Node): node is ts.HasJSDoc { .api }
141
function hasModifiers(node: ts.Node): node is ts.HasModifiers { .api }
142
function hasType(node: ts.Node): node is ts.HasType { .api }
143
function hasTypeArguments(node: ts.Node): node is ts.HasTypeArguments { .api }
144
function isAccessExpression(node: ts.Node): node is ts.AccessExpression { .api }
145
function isAccessibilityModifier(node: ts.Node): node is ts.AccessibilityModifier { .api }
146
function isAccessorDeclaration(node: ts.Node): node is ts.AccessorDeclaration { .api }
147
function isArrayBindingElement(node: ts.Node): node is ts.ArrayBindingElement { .api }
148
function isArrayBindingOrAssignmentPattern(node: ts.Node): node is ts.ArrayBindingOrAssignmentPattern { .api }
149
function isAssignmentPattern(node: ts.Node): node is ts.AssignmentPattern { .api }
150
function isBindingOrAssignmentElementRestIndicator(node: ts.Node): node is ts.BindingOrAssignmentElementRestIndicator { .api }
151
function isBindingOrAssignmentElementTarget(node: ts.Node): node is ts.BindingOrAssignmentElementTarget { .api }
152
function isBindingOrAssignmentPattern(node: ts.Node): node is ts.BindingOrAssignmentPattern { .api }
153
function isBindingPattern(node: ts.Node): node is ts.BindingPattern { .api }
154
function isBlockLike(node: ts.Node): node is ts.BlockLike { .api }
155
function isBooleanLiteral(node: ts.Node): node is ts.BooleanLiteral { .api }
156
function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration { .api }
157
function isClassMemberModifier(node: ts.Node): node is ts.ClassMemberModifier { .api }
158
function isDeclarationName(node: ts.Node): node is ts.DeclarationName { .api }
159
function isDeclarationWithTypeParameterChildren(node: ts.Node): node is ts.DeclarationWithTypeParameterChildren { .api }
160
function isDeclarationWithTypeParameters(node: ts.Node): node is ts.DeclarationWithTypeParameters { .api }
161
function isDestructuringPattern(node: ts.Node): node is ts.DestructuringPattern { .api }
162
function isEntityNameExpression(node: ts.Node): node is ts.EntityNameExpression { .api }
163
function isEntityNameOrEntityNameExpression(node: ts.Node): node is ts.EntityNameOrEntityNameExpression { .api }
164
function isForInOrOfStatement(node: ts.Node): node is ts.ForInOrOfStatement { .api }
165
function isFunctionLikeDeclaration(node: ts.Node): node is ts.FunctionLikeDeclaration { .api }
166
function isJSDocComment(node: ts.Node): node is ts.JSDocComment { .api }
167
function isJSDocNamespaceBody(node: ts.Node): node is ts.JSDocNamespaceBody { .api }
168
function isJSDocTypeReferencingNode(node: ts.Node): node is ts.JSDocTypeReferencingNode { .api }
169
function isJsonObjectExpression(node: ts.Node): node is ts.JsonObjectExpression { .api }
170
function isJsxAttributeLike(node: ts.Node): node is ts.JsxAttributeLike { .api }
171
function isJsxAttributeValue(node: ts.Node): node is ts.JsxAttributeValue { .api }
172
function isJsxChild(node: ts.Node): node is ts.JsxChild { .api }
173
function isJsxTagNameExpression(node: ts.Node): node is ts.JsxTagNameExpression { .api }
174
function isLiteralToken(node: ts.Node): node is ts.LiteralToken { .api }
175
function isModuleBody(node: ts.Node): node is ts.ModuleBody { .api }
176
function isModuleName(node: ts.Node): node is ts.ModuleName { .api }
177
function isModuleReference(node: ts.Node): node is ts.ModuleReference { .api }
178
function isNamedImportBindings(node: ts.Node): node is ts.NamedImportBindings { .api }
179
function isNamedImportsOrExports(node: ts.Node): node is ts.NamedImportsOrExports { .api }
180
function isNamespaceBody(node: ts.Node): node is ts.NamespaceBody { .api }
181
function isObjectBindingOrAssignmentElement(node: ts.Node): node is ts.ObjectBindingOrAssignmentElement { .api }
182
function isObjectBindingOrAssignmentPattern(node: ts.Node): node is ts.ObjectBindingOrAssignmentPattern { .api }
183
function isObjectTypeDeclaration(node: ts.Node): node is ts.ObjectTypeDeclaration { .api }
184
function isParameterPropertyModifier(node: ts.Node): node is ts.ParameterPropertyModifier { .api }
185
function isPropertyNameLiteral(node: ts.Node): node is ts.PropertyNameLiteral { .api }
186
function isPseudoLiteralToken(node: ts.Node): node is ts.PseudoLiteralToken { .api }
187
function isSignatureDeclaration(node: ts.Node): node is ts.SignatureDeclaration { .api }
188
function isSuperProperty(node: ts.Node): node is ts.SuperProperty { .api }
189
function isTypeOnlyCompatibleAliasDeclaration(node: ts.Node): node is ts.TypeOnlyCompatibleAliasDeclaration { .api }
190
function isTypeReferenceType(node: ts.Node): node is ts.TypeReferenceType { .api }
191
function isUnionOrIntersectionTypeNode(node: ts.Node): node is ts.UnionOrIntersectionTypeNode { .api }
192
function isVariableLikeDeclaration(node: ts.Node): node is ts.VariableLikeDeclaration { .api }
193
```
194
195
#### Usage Examples
196
197
```typescript
198
import { isAccessExpression, hasModifiers, isClassLikeDeclaration } from 'ts-api-utils';
199
200
// Check if a node is any kind of access expression
201
if (isAccessExpression(node)) {
202
// node is either PropertyAccessExpression or ElementAccessExpression
203
console.log('Accessing:', node.expression.getText());
204
}
205
206
// Check if a declaration has modifiers
207
if (hasModifiers(declaration)) {
208
// declaration has a modifiers array
209
const modifiers = declaration.modifiers?.map(mod => mod.getText()) ?? [];
210
console.log('Modifiers:', modifiers);
211
}
212
213
// Check if it's a class-like declaration
214
if (isClassLikeDeclaration(node)) {
215
// node is ClassDeclaration or ClassExpression
216
console.log('Class name:', node.name?.getText());
217
}
218
```
219
220
### Compound Type Guards
221
222
These functions test for more complex node patterns and combinations:
223
224
```typescript
225
function isConstAssertionExpression(node: ts.AssertionExpression): node is ConstAssertionExpression { .api }
226
function isIterationStatement(node: ts.Node): node is ts.IterationStatement { .api }
227
function isJSDocNamespaceDeclaration(node: ts.Node): node is ts.JSDocNamespaceDeclaration { .api }
228
function isJsxTagNamePropertyAccess(node: ts.Node): node is ts.JsxTagNamePropertyAccess { .api }
229
function isNamedDeclarationWithName(node: ts.Declaration): node is NamedDeclarationWithName { .api }
230
function isNamespaceDeclaration(node: ts.Node): node is ts.NamespaceDeclaration { .api }
231
function isNumericOrStringLikeLiteral(node: ts.Node): node is NumericOrStringLikeLiteral { .api }
232
function isPropertyAccessEntityNameExpression(node: ts.Node): node is ts.PropertyAccessEntityNameExpression { .api }
233
function isSuperElementAccessExpression(node: ts.Node): node is ts.SuperElementAccessExpression { .api }
234
function isSuperPropertyAccessExpression(node: ts.Node): node is ts.SuperPropertyAccessExpression { .api }
235
```
236
237
#### Compound Node Types
238
239
The library defines several compound node types for complex patterns:
240
241
```typescript
242
interface ConstAssertionExpression extends ts.AssertionExpression {
243
type: ts.TypeReferenceNode;
244
typeName: ConstAssertionIdentifier;
245
}
246
247
interface ConstAssertionIdentifier extends ts.Identifier {
248
escapedText: "const" & ts.__String;
249
}
250
251
interface NamedDeclarationWithName extends ts.NamedDeclaration {
252
name: ts.DeclarationName;
253
}
254
255
type NumericOrStringLikeLiteral = ts.NumericLiteral | ts.StringLiteralLike
256
```
257
258
#### Usage Examples
259
260
```typescript
261
import {
262
isConstAssertionExpression,
263
isIterationStatement,
264
isNamedDeclarationWithName
265
} from 'ts-api-utils';
266
267
// Check for const assertions like "as const"
268
if (ts.isAssertionExpression(node) && isConstAssertionExpression(node)) {
269
console.log('Found const assertion');
270
}
271
272
// Check for any kind of loop
273
if (isIterationStatement(node)) {
274
// node is do, for, for-in, for-of, or while statement
275
console.log('Found loop statement');
276
}
277
278
// Safely access names on declarations
279
if (isNamedDeclarationWithName(declaration)) {
280
// declaration.name is guaranteed to exist
281
console.log('Declaration name:', declaration.name.getText());
282
}
283
```
284
285
## Node Access Utilities
286
287
The access utilities help determine how expressions are being used in their context - whether they're being read from, written to, or deleted.
288
289
### AccessKind Enum
290
291
```typescript
292
enum AccessKind {
293
None = 0,
294
Read = 1,
295
Write = 2,
296
Delete = 4,
297
ReadWrite = 3 // Read | Write
298
}
299
```
300
301
### Access Detection
302
303
```typescript { .api }
304
function getAccessKind(node: ts.Expression): AccessKind;
305
```
306
307
This function analyzes an expression's context to determine what operations are being performed on it.
308
309
### Additional Node Utilities
310
311
```typescript { .api }
312
function isBindableObjectDefinePropertyCall(node: ts.CallExpression): boolean;
313
function isInConstContext(
314
node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
315
typeChecker: ts.TypeChecker
316
): boolean;
317
```
318
319
The `isBindableObjectDefinePropertyCall` function determines if a call expression represents a statically analyzable `Object.defineProperty` call. The `isInConstContext` function detects if property assignments are affected by const assertion contexts.
320
321
#### Usage Examples
322
323
```typescript
324
import { getAccessKind, AccessKind } from 'ts-api-utils';
325
326
declare const expression: ts.Expression;
327
328
const access = getAccessKind(expression);
329
330
if (access & AccessKind.Read) {
331
console.log('Expression is being read');
332
}
333
334
if (access & AccessKind.Write) {
335
console.log('Expression is being written to');
336
}
337
338
if (access & AccessKind.Delete) {
339
console.log('Expression is being deleted');
340
}
341
342
// Check for specific access patterns
343
switch (access) {
344
case AccessKind.Read:
345
console.log('Read-only access: const x = expression');
346
break;
347
case AccessKind.Write:
348
console.log('Write-only access: expression = value');
349
break;
350
case AccessKind.ReadWrite:
351
console.log('Read-write access: expression++');
352
break;
353
case AccessKind.Delete:
354
console.log('Delete access: delete expression');
355
break;
356
case AccessKind.None:
357
console.log('No access (e.g., in type position)');
358
break;
359
}
360
```
361
362
#### Access Context Examples
363
364
Different contexts produce different access kinds:
365
366
```typescript
367
// Read access
368
console.log(variable); // AccessKind.Read
369
const x = obj.property; // AccessKind.Read (obj.property)
370
if (condition) { } // AccessKind.Read (condition)
371
372
// Write access
373
variable = 5; // AccessKind.Write
374
obj.property = value; // AccessKind.Write (obj.property)
375
const [a, b] = array; // AccessKind.Write (a, b in destructuring)
376
377
// Read-write access
378
variable++; // AccessKind.ReadWrite
379
++variable; // AccessKind.ReadWrite
380
variable += 10; // AccessKind.ReadWrite
381
382
// Delete access
383
delete obj.property; // AccessKind.Delete (obj.property)
384
385
// No access
386
type T = typeof variable; // AccessKind.None (in type position)
387
```
388
389
## Node Flag Utilities
390
391
Flag utilities test for various TypeScript compiler flags set on nodes, types, and symbols.
392
393
```typescript
394
function isModifierFlagSet(node: ts.Declaration, flag: ts.ModifierFlags): boolean { .api }
395
function isNodeFlagSet(node: ts.Node, flag: ts.NodeFlags): boolean { .api }
396
function isObjectFlagSet(objectType: ts.ObjectType, flag: ts.ObjectFlags): boolean { .api }
397
function isSymbolFlagSet(symbol: ts.Symbol, flag: ts.SymbolFlags): boolean { .api }
398
function isTypeFlagSet(type: ts.Type, flag: ts.TypeFlags): boolean { .api }
399
```
400
401
#### Usage Examples
402
403
```typescript
404
import {
405
isModifierFlagSet,
406
isNodeFlagSet,
407
isSymbolFlagSet,
408
isTypeFlagSet
409
} from 'ts-api-utils';
410
import ts from 'typescript';
411
412
// Check for abstract modifier
413
if (isModifierFlagSet(declaration, ts.ModifierFlags.Abstract)) {
414
console.log('Declaration is abstract');
415
}
416
417
// Check for await context
418
if (isNodeFlagSet(node, ts.NodeFlags.AwaitContext)) {
419
console.log('Node is in await context');
420
}
421
422
// Check symbol properties
423
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Function)) {
424
console.log('Symbol represents a function');
425
}
426
427
// Check type properties
428
if (isTypeFlagSet(type, ts.TypeFlags.Union)) {
429
console.log('Type is a union type');
430
}
431
```
432
433
## Node Utilities
434
435
Additional helper functions for working with nodes in specific contexts.
436
437
### Internal Utilities
438
439
These utilities are used internally by the library but may be useful for advanced use cases:
440
441
```typescript
442
function isBindableObjectDefinePropertyCall(node: ts.CallExpression): boolean { .api }
443
function isInConstContext(
444
node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
445
typeChecker: ts.TypeChecker
446
): boolean { .api }
447
```
448
449
#### Usage Examples
450
451
```typescript
452
import { isBindableObjectDefinePropertyCall, isInConstContext } from 'ts-api-utils';
453
454
// Check for statically analyzable Object.defineProperty calls
455
if (ts.isCallExpression(node) && isBindableObjectDefinePropertyCall(node)) {
456
console.log('Found Object.defineProperty call that can be analyzed');
457
}
458
459
// Check if property is in a const context
460
if (isInConstContext(propertyAssignment, typeChecker)) {
461
console.log('Property assignment is in const context (literal type)');
462
}
463
```
464
465
## Integration with Other Modules
466
467
Node Analysis utilities work seamlessly with other ts-api-utils modules:
468
469
### With Type Analysis
470
471
```typescript
472
import { isVariableDeclaration, isSignatureDeclaration } from 'ts-api-utils';
473
import { getCallSignaturesOfType } from 'ts-api-utils';
474
475
if (isVariableDeclaration(node) && node.type) {
476
const type = typeChecker.getTypeFromTypeNode(node.type);
477
const signatures = getCallSignaturesOfType(type);
478
479
if (signatures.length > 0) {
480
console.log('Variable has callable type');
481
}
482
}
483
```
484
485
### With Usage Analysis
486
487
```typescript
488
import { isVariableDeclaration, collectVariableUsage } from 'ts-api-utils';
489
490
const usage = collectVariableUsage(sourceFile);
491
492
for (const [identifier, info] of usage) {
493
if (info.declarations.some(decl =>
494
isVariableDeclaration(decl.parent) &&
495
hasModifiers(decl.parent.parent)
496
)) {
497
console.log('Variable declared with modifiers:', identifier.text);
498
}
499
}
500
```
501
502
## Best Practices
503
504
### Performance Considerations
505
506
1. **Use specific type guards**: Prefer specific guards like `isPropertyAccessExpression` over generic checks
507
2. **Cache results**: Type guard results don't change for a given node, so cache expensive checks
508
3. **Traverse efficiently**: Use parent/child relationships rather than re-searching the AST
509
510
### Type Safety
511
512
1. **Always use type guards**: Never cast nodes directly - use type guards for safe narrowing
513
2. **Check parent context**: Many node types are only meaningful in specific parent contexts
514
3. **Handle undefined cases**: Many node properties are optional - always check for existence
515
516
### Common Patterns
517
518
```typescript
519
// Safe property access pattern
520
function getPropertyName(node: ts.Node): string | undefined {
521
if (ts.isPropertyAccessExpression(node)) {
522
return node.name.text;
523
}
524
if (ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression)) {
525
return node.argumentExpression.text;
526
}
527
return undefined;
528
}
529
530
// Safe modifier checking pattern
531
function hasPublicModifier(node: ts.Node): boolean {
532
return hasModifiers(node) &&
533
node.modifiers?.some(isPublicKeyword) === true;
534
}
535
536
// Access pattern analysis
537
function analyzeExpression(expr: ts.Expression): void {
538
const access = getAccessKind(expr);
539
540
if (access & AccessKind.Write) {
541
// Handle write operations
542
if (access & AccessKind.Read) {
543
console.log('Read-write operation (e.g., +=, ++)');
544
} else {
545
console.log('Pure write operation (e.g., assignment)');
546
}
547
} else if (access & AccessKind.Read) {
548
console.log('Read-only access');
549
} else if (access & AccessKind.Delete) {
550
console.log('Delete operation');
551
}
552
}
553
```
554
555
The Node Analysis module provides the foundation for all TypeScript AST manipulation and analysis. By combining type guards, access utilities, and flag checking, you can build sophisticated tools that understand and manipulate TypeScript code with confidence and type safety.