or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compiler-options.mdindex.mdnode-analysis.mdsyntax-utilities.mdtype-system.mdusage-analysis.md

syntax-utilities.mddocs/

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.