0
# Syntax Utilities
1
2
Syntax Utilities provide a comprehensive set of functions for working with TypeScript's syntax elements, including comments processing, flag utilities, modifier checks, scope boundary detection, and token manipulation. These utilities form the foundation for many advanced TypeScript analysis tasks.
3
4
## Overview
5
6
TypeScript's syntax is rich and complex, with many nuanced rules governing how different language constructs behave. The Syntax Utilities module provides essential building blocks for:
7
8
1. **Comment Processing**: Iterating over and analyzing code comments
9
2. **Flag Utilities**: Testing various TypeScript flags on nodes, types, and symbols
10
3. **Modifier Utilities**: Working with modifier tokens and syntax kinds
11
4. **Scope Utilities**: Determining scope boundaries for variables and functions
12
5. **Syntax Validation**: Checking syntax validity and properties
13
6. **Token Processing**: Iterating over and manipulating syntax tokens
14
15
These utilities are essential for building sophisticated code analysis tools, formatters, and transformers that need to understand the structure and meaning of TypeScript code at a granular level.
16
17
## Comments Processing
18
19
Comments are an important part of code documentation and can contain significant information for analysis tools.
20
21
### Types
22
23
#### ForEachCommentCallback
24
25
```typescript { .api }
26
type ForEachCommentCallback = (fullText: string, comment: ts.CommentRange) => void
27
```
28
29
Callback type used with `forEachComment` to process each comment found in the source code.
30
31
**Parameters:**
32
- `fullText` - The full source text of the file
33
- `comment` - A `ts.CommentRange` object containing position and type information
34
35
### Functions
36
37
#### forEachComment
38
39
```typescript { .api }
40
function forEachComment(
41
node: ts.Node,
42
callback: ForEachCommentCallback,
43
sourceFile?: ts.SourceFile
44
): void
45
```
46
47
Iterates over all comments owned by a node or its children, calling the provided callback for each comment found.
48
49
**Parameters:**
50
- `node` - The AST node to analyze for comments
51
- `callback` - Function called for each comment found
52
- `sourceFile` - Optional source file (inferred from node if not provided)
53
54
**Example - Extracting TODO comments:**
55
```typescript
56
import { forEachComment } from "ts-api-utils";
57
import * as ts from "typescript";
58
59
function extractTodoComments(sourceFile: ts.SourceFile): string[] {
60
const todos: string[] = [];
61
62
forEachComment(sourceFile, (fullText, comment) => {
63
const commentText = fullText.slice(comment.pos, comment.end);
64
65
if (commentText.toLowerCase().includes('todo')) {
66
todos.push(commentText.trim());
67
}
68
}, sourceFile);
69
70
return todos;
71
}
72
73
// Usage
74
const sourceFile = ts.createSourceFile(
75
"example.ts",
76
`
77
// TODO: Implement error handling
78
function process() {
79
/* TODO: Add validation */
80
return null;
81
}
82
`,
83
ts.ScriptTarget.Latest
84
);
85
86
const todos = extractTodoComments(sourceFile);
87
console.log(todos); // ["// TODO: Implement error handling", "/* TODO: Add validation */"]
88
```
89
90
**Example - Comment analysis:**
91
```typescript
92
import { forEachComment } from "ts-api-utils";
93
94
function analyzeComments(node: ts.Node, sourceFile: ts.SourceFile) {
95
const commentStats = {
96
singleLine: 0,
97
multiLine: 0,
98
jsdoc: 0
99
};
100
101
forEachComment(node, (fullText, comment) => {
102
const commentText = fullText.slice(comment.pos, comment.end);
103
104
if (comment.kind === ts.SyntaxKind.SingleLineCommentTrivia) {
105
commentStats.singleLine++;
106
} else if (comment.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
107
if (commentText.startsWith('/**')) {
108
commentStats.jsdoc++;
109
} else {
110
commentStats.multiLine++;
111
}
112
}
113
}, sourceFile);
114
115
return commentStats;
116
}
117
```
118
119
## Flag Utilities
120
121
TypeScript uses various flag systems to track properties of nodes, types, symbols, and other AST elements. These utilities provide safe ways to test these flags.
122
123
### Functions
124
125
#### isModifierFlagSet
126
127
```typescript { .api }
128
function isModifierFlagSet(
129
node: ts.Declaration,
130
flag: ts.ModifierFlags
131
): boolean
132
```
133
134
Tests if the given declaration node has the specified `ModifierFlags` set.
135
136
**Parameters:**
137
- `node` - The declaration to test
138
- `flag` - The modifier flag to check for
139
140
**Returns:** `true` if the flag is set, `false` otherwise
141
142
**Example - Checking access modifiers:**
143
```typescript
144
import { isModifierFlagSet } from "ts-api-utils";
145
import * as ts from "typescript";
146
147
function analyzeClassMember(member: ts.ClassElement) {
148
if (ts.isPropertyDeclaration(member) || ts.isMethodDeclaration(member)) {
149
if (isModifierFlagSet(member, ts.ModifierFlags.Private)) {
150
console.log(`${member.name?.getText()} is private`);
151
}
152
153
if (isModifierFlagSet(member, ts.ModifierFlags.Static)) {
154
console.log(`${member.name?.getText()} is static`);
155
}
156
157
if (isModifierFlagSet(member, ts.ModifierFlags.Abstract)) {
158
console.log(`${member.name?.getText()} is abstract`);
159
}
160
}
161
}
162
```
163
164
#### isNodeFlagSet
165
166
```typescript { .api }
167
function isNodeFlagSet(
168
node: ts.Node,
169
flag: ts.NodeFlags
170
): boolean
171
```
172
173
Tests if the given node has the specified `NodeFlags` set.
174
175
**Parameters:**
176
- `node` - The node to test
177
- `flag` - The node flag to check for
178
179
**Returns:** `true` if the flag is set, `false` otherwise
180
181
**Example - Checking node flags:**
182
```typescript
183
import { isNodeFlagSet } from "ts-api-utils";
184
import * as ts from "typescript";
185
186
function analyzeNode(node: ts.Node) {
187
if (isNodeFlagSet(node, ts.NodeFlags.Synthesized)) {
188
console.log("Node is synthesized (not from original source)");
189
}
190
191
if (isNodeFlagSet(node, ts.NodeFlags.ThisNodeHasError)) {
192
console.log("Node has compilation errors");
193
}
194
195
if (isNodeFlagSet(node, ts.NodeFlags.HasImplicitReturn)) {
196
console.log("Function has implicit return");
197
}
198
}
199
```
200
201
#### isObjectFlagSet
202
203
```typescript { .api }
204
function isObjectFlagSet(
205
objectType: ts.ObjectType,
206
flag: ts.ObjectFlags
207
): boolean
208
```
209
210
Tests if the given object type has the specified `ObjectFlags` set.
211
212
**Parameters:**
213
- `objectType` - The object type to test
214
- `flag` - The object flag to check for
215
216
**Returns:** `true` if the flag is set, `false` otherwise
217
218
**Example - Analyzing object types:**
219
```typescript
220
import { isObjectFlagSet } from "ts-api-utils";
221
import * as ts from "typescript";
222
223
function analyzeObjectType(type: ts.Type, typeChecker: ts.TypeChecker) {
224
if (type.flags & ts.TypeFlags.Object) {
225
const objectType = type as ts.ObjectType;
226
227
if (isObjectFlagSet(objectType, ts.ObjectFlags.Interface)) {
228
console.log("Type is an interface");
229
}
230
231
if (isObjectFlagSet(objectType, ts.ObjectFlags.Class)) {
232
console.log("Type is a class");
233
}
234
235
if (isObjectFlagSet(objectType, ts.ObjectFlags.Tuple)) {
236
console.log("Type is a tuple");
237
}
238
}
239
}
240
```
241
242
#### isSymbolFlagSet
243
244
```typescript { .api }
245
function isSymbolFlagSet(
246
symbol: ts.Symbol,
247
flag: ts.SymbolFlags
248
): boolean
249
```
250
251
Tests if the given symbol has the specified `SymbolFlags` set.
252
253
**Parameters:**
254
- `symbol` - The symbol to test
255
- `flag` - The symbol flag to check for
256
257
**Returns:** `true` if the flag is set, `false` otherwise
258
259
**Example - Symbol analysis:**
260
```typescript
261
import { isSymbolFlagSet } from "ts-api-utils";
262
import * as ts from "typescript";
263
264
function analyzeSymbol(symbol: ts.Symbol) {
265
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Variable)) {
266
console.log("Symbol represents a variable");
267
}
268
269
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Function)) {
270
console.log("Symbol represents a function");
271
}
272
273
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Class)) {
274
console.log("Symbol represents a class");
275
}
276
277
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Exported)) {
278
console.log("Symbol is exported");
279
}
280
}
281
```
282
283
#### isTypeFlagSet
284
285
```typescript { .api }
286
function isTypeFlagSet(
287
type: ts.Type,
288
flag: ts.TypeFlags
289
): boolean
290
```
291
292
Tests if the given type has the specified `TypeFlags` set.
293
294
**Parameters:**
295
- `type` - The type to test
296
- `flag` - The type flag to check for
297
298
**Returns:** `true` if the flag is set, `false` otherwise
299
300
**Example - Type flag checking:**
301
```typescript
302
import { isTypeFlagSet } from "ts-api-utils";
303
import * as ts from "typescript";
304
305
function analyzeType(type: ts.Type) {
306
if (isTypeFlagSet(type, ts.TypeFlags.String)) {
307
console.log("Type is string");
308
}
309
310
if (isTypeFlagSet(type, ts.TypeFlags.Number)) {
311
console.log("Type is number");
312
}
313
314
if (isTypeFlagSet(type, ts.TypeFlags.Union)) {
315
console.log("Type is a union type");
316
}
317
318
if (isTypeFlagSet(type, ts.TypeFlags.Literal)) {
319
console.log("Type is a literal type");
320
}
321
}
322
```
323
324
## Modifier Utilities
325
326
Modifiers control various aspects of declarations like visibility, mutability, and behavior.
327
328
### Functions
329
330
#### includesModifier
331
332
```typescript { .api }
333
function includesModifier(
334
modifiers: Iterable<ts.ModifierLike> | undefined,
335
...kinds: ts.ModifierSyntaxKind[]
336
): boolean
337
```
338
339
Tests if the given iterable of modifiers includes any modifier of the specified kinds.
340
341
**Parameters:**
342
- `modifiers` - An iterable of modifier-like nodes (can be undefined)
343
- `...kinds` - One or more modifier syntax kinds to check for
344
345
**Returns:** `true` if any of the specified modifier kinds are found, `false` otherwise
346
347
**Example - Checking for access modifiers:**
348
```typescript
349
import { includesModifier } from "ts-api-utils";
350
import * as ts from "typescript";
351
352
function analyzeDeclaration(node: ts.Declaration) {
353
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
354
355
if (includesModifier(modifiers, ts.SyntaxKind.PublicKeyword)) {
356
console.log("Declaration is public");
357
}
358
359
if (includesModifier(
360
modifiers,
361
ts.SyntaxKind.PrivateKeyword,
362
ts.SyntaxKind.ProtectedKeyword
363
)) {
364
console.log("Declaration has restricted access");
365
}
366
367
if (includesModifier(modifiers, ts.SyntaxKind.StaticKeyword, ts.SyntaxKind.ReadonlyKeyword)) {
368
console.log("Declaration is static or readonly");
369
}
370
}
371
```
372
373
**Example - Modifier validation:**
374
```typescript
375
import { includesModifier } from "ts-api-utils";
376
import * as ts from "typescript";
377
378
function validateClassMember(member: ts.ClassElement): string[] {
379
const errors: string[] = [];
380
const modifiers = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
381
382
// Check for conflicting access modifiers
383
const accessModifiers = [
384
ts.SyntaxKind.PublicKeyword,
385
ts.SyntaxKind.PrivateKeyword,
386
ts.SyntaxKind.ProtectedKeyword
387
];
388
389
const hasMultipleAccess = accessModifiers.filter(mod =>
390
includesModifier(modifiers, mod)
391
).length > 1;
392
393
if (hasMultipleAccess) {
394
errors.push("Multiple access modifiers are not allowed");
395
}
396
397
// Abstract members cannot be private
398
if (includesModifier(modifiers, ts.SyntaxKind.AbstractKeyword) &&
399
includesModifier(modifiers, ts.SyntaxKind.PrivateKeyword)) {
400
errors.push("Abstract members cannot be private");
401
}
402
403
return errors;
404
}
405
```
406
407
## Scope Utilities
408
409
Scope utilities help determine boundaries where variable scoping rules change, which is crucial for accurate variable analysis.
410
411
### Functions
412
413
#### isFunctionScopeBoundary
414
415
```typescript { .api }
416
function isFunctionScopeBoundary(node: ts.Node): boolean
417
```
418
419
Determines whether a node represents a function scope boundary - a point where variables scoping rules change due to function boundaries.
420
421
**Parameters:**
422
- `node` - The node to test for being a function scope boundary
423
424
**Returns:** `true` if the node is a function scope boundary, `false` otherwise
425
426
**Function scope boundaries include:**
427
- Function declarations
428
- Function expressions
429
- Arrow functions
430
- Method declarations
431
- Constructor declarations
432
- Getter/setter declarations
433
- Module declarations
434
435
**Example - Variable scope analysis:**
436
```typescript
437
import { isFunctionScopeBoundary } from "ts-api-utils";
438
import * as ts from "typescript";
439
440
function analyzeScopes(sourceFile: ts.SourceFile) {
441
const scopes: ts.Node[] = [];
442
443
function visit(node: ts.Node) {
444
if (isFunctionScopeBoundary(node)) {
445
scopes.push(node);
446
console.log(`Found scope boundary: ${ts.SyntaxKind[node.kind]}`);
447
}
448
449
ts.forEachChild(node, visit);
450
}
451
452
visit(sourceFile);
453
return scopes;
454
}
455
```
456
457
**Example - Scope-aware variable tracking:**
458
```typescript
459
import { isFunctionScopeBoundary } from "ts-api-utils";
460
import * as ts from "typescript";
461
462
class ScopeTracker {
463
private scopeStack: ts.Node[] = [];
464
465
visit(node: ts.Node) {
466
if (isFunctionScopeBoundary(node)) {
467
this.scopeStack.push(node);
468
}
469
470
if (ts.isIdentifier(node)) {
471
console.log(`Variable '${node.text}' in scope depth: ${this.scopeStack.length}`);
472
}
473
474
ts.forEachChild(node, (child) => this.visit(child));
475
476
if (isFunctionScopeBoundary(node)) {
477
this.scopeStack.pop();
478
}
479
}
480
}
481
```
482
483
## Syntax Validation Utilities
484
485
These utilities help validate and analyze syntax properties and patterns.
486
487
### Functions
488
489
#### isAssignmentKind
490
491
```typescript { .api }
492
function isAssignmentKind(kind: ts.SyntaxKind): boolean
493
```
494
495
Tests whether the given syntax kind represents an assignment operation.
496
497
**Parameters:**
498
- `kind` - The syntax kind to test
499
500
**Returns:** `true` if the kind represents an assignment, `false` otherwise
501
502
**Assignment kinds include:**
503
- `EqualsToken` (`=`)
504
- `PlusEqualsToken` (`+=`)
505
- `MinusEqualsToken` (`-=`)
506
- `AsteriskEqualsToken` (`*=`)
507
- And other compound assignment operators
508
509
**Example - Assignment detection:**
510
```typescript
511
import { isAssignmentKind } from "ts-api-utils";
512
import * as ts from "typescript";
513
514
function analyzeAssignments(sourceFile: ts.SourceFile) {
515
function visit(node: ts.Node) {
516
if (ts.isBinaryExpression(node) && isAssignmentKind(node.operatorToken.kind)) {
517
console.log(`Assignment: ${node.left.getText()} ${node.operatorToken.getText()} ${node.right.getText()}`);
518
}
519
520
ts.forEachChild(node, visit);
521
}
522
523
visit(sourceFile);
524
}
525
```
526
527
#### isNumericPropertyName
528
529
```typescript { .api }
530
function isNumericPropertyName(name: string | ts.__String): boolean
531
```
532
533
Tests if a string represents a numeric property name (like array indices).
534
535
**Parameters:**
536
- `name` - The property name to test (string or TypeScript internal string)
537
538
**Returns:** `true` if the name is numeric, `false` otherwise
539
540
**Example - Property analysis:**
541
```typescript
542
import { isNumericPropertyName } from "ts-api-utils";
543
import * as ts from "typescript";
544
545
function analyzeObjectLiteral(node: ts.ObjectLiteralExpression) {
546
for (const property of node.properties) {
547
if (ts.isPropertyAssignment(property) && property.name) {
548
const nameText = property.name.getText();
549
550
if (isNumericPropertyName(nameText)) {
551
console.log(`Numeric property: ${nameText}`);
552
} else {
553
console.log(`Named property: ${nameText}`);
554
}
555
}
556
}
557
}
558
559
// Example usage
560
const code = `{
561
0: "first",
562
"1": "second",
563
name: "value",
564
"42": "answer"
565
}`;
566
```
567
568
#### isValidPropertyAccess
569
570
```typescript { .api }
571
function isValidPropertyAccess(
572
text: string,
573
languageVersion?: ts.ScriptTarget
574
): boolean
575
```
576
577
Determines whether the given text can be used to access a property with a `PropertyAccessExpression` while preserving the property's name.
578
579
**Parameters:**
580
- `text` - The property name text to validate
581
- `languageVersion` - Optional TypeScript language version (affects identifier rules)
582
583
**Returns:** `true` if the text can be used in property access syntax, `false` otherwise
584
585
**Example - Property access validation:**
586
```typescript
587
import { isValidPropertyAccess } from "ts-api-utils";
588
589
// Valid identifiers for property access
590
console.log(isValidPropertyAccess("name")); // true
591
console.log(isValidPropertyAccess("_private")); // true
592
console.log(isValidPropertyAccess("$jquery")); // true
593
594
// Invalid - must use bracket notation
595
console.log(isValidPropertyAccess("123")); // false (starts with number)
596
console.log(isValidPropertyAccess("my-prop")); // false (contains hyphen)
597
console.log(isValidPropertyAccess("class")); // false (reserved keyword)
598
599
// Usage in code transformation
600
function createPropertyAccess(object: string, property: string): string {
601
if (isValidPropertyAccess(property)) {
602
return `${object}.${property}`;
603
} else {
604
return `${object}[${JSON.stringify(property)}]`;
605
}
606
}
607
608
console.log(createPropertyAccess("obj", "name")); // "obj.name"
609
console.log(createPropertyAccess("obj", "123")); // "obj["123"]"
610
console.log(createPropertyAccess("obj", "my-prop")); // "obj["my-prop"]"
611
```
612
613
## Token Processing
614
615
Token processing utilities allow iteration over and manipulation of syntax tokens, which are the smallest units of syntax.
616
617
### Types
618
619
#### ForEachTokenCallback
620
621
```typescript { .api }
622
type ForEachTokenCallback = (token: ts.Node) => void
623
```
624
625
Callback type used with `forEachToken` to process each token found in the AST.
626
627
**Parameters:**
628
- `token` - The token node being processed
629
630
### Functions
631
632
#### forEachToken
633
634
```typescript { .api }
635
function forEachToken(
636
node: ts.Node,
637
callback: ForEachTokenCallback,
638
sourceFile?: ts.SourceFile
639
): void
640
```
641
642
Iterates over all tokens of a node, calling the provided callback for each token found.
643
644
**Parameters:**
645
- `node` - The AST node to analyze for tokens
646
- `callback` - Function called for each token found
647
- `sourceFile` - Optional source file (inferred from node if not provided)
648
649
**Example - Token counting:**
650
```typescript
651
import { forEachToken } from "ts-api-utils";
652
import * as ts from "typescript";
653
654
function countTokens(node: ts.Node, sourceFile: ts.SourceFile): number {
655
let count = 0;
656
657
forEachToken(node, (token) => {
658
count++;
659
}, sourceFile);
660
661
return count;
662
}
663
664
// Usage
665
const sourceFile = ts.createSourceFile(
666
"example.ts",
667
"function hello() { return 'world'; }",
668
ts.ScriptTarget.Latest
669
);
670
671
const tokenCount = countTokens(sourceFile, sourceFile);
672
console.log(`Total tokens: ${tokenCount}`);
673
```
674
675
**Example - Token analysis:**
676
```typescript
677
import { forEachToken } from "ts-api-utils";
678
import * as ts from "typescript";
679
680
function analyzeTokens(node: ts.Node, sourceFile: ts.SourceFile) {
681
const tokenStats = new Map<ts.SyntaxKind, number>();
682
683
forEachToken(node, (token) => {
684
const kind = token.kind;
685
tokenStats.set(kind, (tokenStats.get(kind) || 0) + 1);
686
}, sourceFile);
687
688
// Report most common tokens
689
const sorted = Array.from(tokenStats.entries())
690
.sort(([,a], [,b]) => b - a)
691
.slice(0, 5);
692
693
console.log("Most common tokens:");
694
sorted.forEach(([kind, count]) => {
695
console.log(`${ts.SyntaxKind[kind]}: ${count}`);
696
});
697
}
698
```
699
700
**Example - Syntax highlighting preparation:**
701
```typescript
702
import { forEachToken } from "ts-api-utils";
703
import * as ts from "typescript";
704
705
interface TokenInfo {
706
kind: ts.SyntaxKind;
707
text: string;
708
start: number;
709
end: number;
710
category: string;
711
}
712
713
function prepareTokensForHighlighting(sourceFile: ts.SourceFile): TokenInfo[] {
714
const tokens: TokenInfo[] = [];
715
716
forEachToken(sourceFile, (token) => {
717
const category = categorizeToken(token.kind);
718
719
tokens.push({
720
kind: token.kind,
721
text: token.getText(sourceFile),
722
start: token.getStart(sourceFile),
723
end: token.getEnd(),
724
category
725
});
726
}, sourceFile);
727
728
return tokens;
729
}
730
731
function categorizeToken(kind: ts.SyntaxKind): string {
732
if (kind >= ts.SyntaxKind.FirstKeyword && kind <= ts.SyntaxKind.LastKeyword) {
733
return "keyword";
734
}
735
if (kind === ts.SyntaxKind.StringLiteral) {
736
return "string";
737
}
738
if (kind === ts.SyntaxKind.NumericLiteral) {
739
return "number";
740
}
741
if (kind === ts.SyntaxKind.Identifier) {
742
return "identifier";
743
}
744
return "punctuation";
745
}
746
```
747
748
## Practical Examples
749
750
### Complete Syntax Analyzer
751
752
```typescript
753
import {
754
forEachComment,
755
forEachToken,
756
isModifierFlagSet,
757
includesModifier,
758
isFunctionScopeBoundary,
759
isAssignmentKind,
760
isValidPropertyAccess
761
} from "ts-api-utils";
762
import * as ts from "typescript";
763
764
class ComprehensiveSyntaxAnalyzer {
765
private sourceFile: ts.SourceFile;
766
767
constructor(sourceCode: string, fileName: string = "analysis.ts") {
768
this.sourceFile = ts.createSourceFile(
769
fileName,
770
sourceCode,
771
ts.ScriptTarget.Latest,
772
true
773
);
774
}
775
776
analyze() {
777
const results = {
778
comments: this.analyzeComments(),
779
tokens: this.analyzeTokens(),
780
scopes: this.analyzeScopes(),
781
assignments: this.analyzeAssignments(),
782
modifiers: this.analyzeModifiers()
783
};
784
785
return results;
786
}
787
788
private analyzeComments() {
789
const comments: string[] = [];
790
791
forEachComment(this.sourceFile, (fullText, comment) => {
792
const text = fullText.slice(comment.pos, comment.end).trim();
793
comments.push(text);
794
}, this.sourceFile);
795
796
return {
797
count: comments.length,
798
comments: comments
799
};
800
}
801
802
private analyzeTokens() {
803
const tokenCounts = new Map<string, number>();
804
805
forEachToken(this.sourceFile, (token) => {
806
const kindName = ts.SyntaxKind[token.kind];
807
tokenCounts.set(kindName, (tokenCounts.get(kindName) || 0) + 1);
808
}, this.sourceFile);
809
810
return Object.fromEntries(tokenCounts);
811
}
812
813
private analyzeScopes() {
814
const scopes: string[] = [];
815
816
const visit = (node: ts.Node) => {
817
if (isFunctionScopeBoundary(node)) {
818
scopes.push(ts.SyntaxKind[node.kind]);
819
}
820
ts.forEachChild(node, visit);
821
};
822
823
visit(this.sourceFile);
824
return scopes;
825
}
826
827
private analyzeAssignments() {
828
const assignments: string[] = [];
829
830
const visit = (node: ts.Node) => {
831
if (ts.isBinaryExpression(node) && isAssignmentKind(node.operatorToken.kind)) {
832
assignments.push(node.getText());
833
}
834
ts.forEachChild(node, visit);
835
};
836
837
visit(this.sourceFile);
838
return assignments;
839
}
840
841
private analyzeModifiers() {
842
const modifierUsage = new Map<string, number>();
843
844
const visit = (node: ts.Node) => {
845
if (ts.canHaveModifiers(node)) {
846
const modifiers = ts.getModifiers(node);
847
if (modifiers) {
848
for (const modifier of modifiers) {
849
const modifierName = ts.SyntaxKind[modifier.kind];
850
modifierUsage.set(modifierName, (modifierUsage.get(modifierName) || 0) + 1);
851
}
852
}
853
}
854
ts.forEachChild(node, visit);
855
};
856
857
visit(this.sourceFile);
858
return Object.fromEntries(modifierUsage);
859
}
860
}
861
862
// Usage
863
const code = `
864
// Main application class
865
class Application {
866
private static instance: Application;
867
868
/**
869
* Creates new instance
870
*/
871
constructor() {
872
this.count = 0;
873
}
874
875
public start(): void {
876
console.log("Starting...");
877
}
878
}
879
`;
880
881
const analyzer = new ComprehensiveSyntaxAnalyzer(code);
882
const results = analyzer.analyze();
883
console.log(results);
884
```
885
886
## Best Practices
887
888
### Safe Flag Checking
889
890
```typescript
891
// ✅ Good: Use utility functions for flag checking
892
if (isModifierFlagSet(node, ts.ModifierFlags.Static)) {
893
// Handle static member
894
}
895
896
// ❌ Avoid: Direct flag manipulation (error-prone)
897
if (node.modifierFlagsCache & ts.ModifierFlags.Static) {
898
// This accesses internal implementation details
899
}
900
```
901
902
### Comprehensive Token Analysis
903
904
```typescript
905
// ✅ Good: Use forEachToken for complete analysis
906
function analyzeAllTokens(node: ts.Node, sourceFile: ts.SourceFile) {
907
forEachToken(node, (token) => {
908
// Process each token individually
909
processToken(token);
910
}, sourceFile);
911
}
912
913
// ❌ Avoid: Manual token traversal (misses edge cases)
914
function manualTokenWalk(node: ts.Node) {
915
// This approach can miss tokens in complex expressions
916
}
917
```
918
919
### Proper Scope Tracking
920
921
```typescript
922
// ✅ Good: Use isFunctionScopeBoundary for accurate scope detection
923
class ScopeAnalyzer {
924
private scopeDepth = 0;
925
926
visit(node: ts.Node) {
927
if (isFunctionScopeBoundary(node)) {
928
this.scopeDepth++;
929
}
930
931
// Analyze node at current scope depth
932
933
ts.forEachChild(node, (child) => this.visit(child));
934
935
if (isFunctionScopeBoundary(node)) {
936
this.scopeDepth--;
937
}
938
}
939
}
940
```
941
942
The Syntax Utilities provide essential building blocks for sophisticated TypeScript code analysis, enabling developers to build tools that understand the nuanced structure and semantics of TypeScript syntax at both high and low levels of detail.