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

usage-analysis.mddocs/

0

# Usage Analysis

1

2

Usage Analysis is an advanced capability of ts-api-utils that provides comprehensive tools for analyzing variable and symbol usage patterns within TypeScript source files. This module enables sophisticated code analysis by tracking how identifiers are declared and referenced across different scopes and domains, making it essential for building linters, refactoring tools, and other advanced TypeScript tooling.

3

4

## Overview

5

6

TypeScript code analysis often requires understanding not just what variables and types are declared, but how they are used throughout a codebase. The Usage Analysis module provides a systematic approach to:

7

8

1. **Track variable declarations** across different syntactic contexts

9

2. **Monitor usage patterns** for identifiers in type and value spaces

10

3. **Analyze scope boundaries** and their effects on identifier visibility

11

4. **Distinguish between domains** where identifiers exist (namespace, type, value spaces)

12

13

This capability is particularly valuable for tools that need to understand identifier lifecycles, detect unused variables, analyze dependencies, or perform safe refactoring operations.

14

15

## Core Concepts

16

17

### Domains

18

19

TypeScript operates with multiple "domains" or "spaces" where identifiers can exist:

20

21

- **Value Space**: Runtime values, variables, functions

22

- **Type Space**: Types, interfaces, type aliases

23

- **Namespace Space**: Module namespaces and namespace declarations

24

- **Import Domain**: Special handling for imported identifiers

25

26

Understanding these domains is crucial because the same identifier name can exist in different domains simultaneously without conflict.

27

28

### Usage Patterns

29

30

The system tracks two primary aspects of identifier usage:

31

32

1. **Declarations**: Where and how identifiers are introduced into scope

33

2. **References**: Where identifiers are accessed or used

34

35

### Scope Analysis

36

37

Variable usage is analyzed within the context of TypeScript's scoping rules, including:

38

39

- **Function scope boundaries**

40

- **Block scope boundaries**

41

- **Type parameter scopes**

42

- **Conditional type scopes**

43

44

## Usage Collection

45

46

The main entry point for variable usage analysis.

47

48

### collectVariableUsage

49

50

Creates a comprehensive mapping of each declared identifier to its usage information within a source file.

51

52

```typescript

53

function collectVariableUsage(sourceFile: ts.SourceFile): Map<ts.Identifier, UsageInfo> { .api }

54

```

55

56

**Parameters:**

57

- `sourceFile: ts.SourceFile` - The TypeScript source file to analyze

58

59

**Returns:** A Map where keys are identifier nodes and values contain complete usage information including declarations, references, domains, and scope details.

60

61

**Example:**

62

```typescript

63

import { collectVariableUsage } from 'ts-api-utils';

64

65

declare const sourceFile: ts.SourceFile;

66

67

const usage = collectVariableUsage(sourceFile);

68

69

for (const [identifier, information] of usage) {

70

console.log(`${identifier.getText()} is used ${information.uses.length} time(s).`);

71

console.log(` Declared in domain: ${information.domain}`);

72

console.log(` Exported: ${information.exported}`);

73

console.log(` Global scope: ${information.inGlobalScope}`);

74

}

75

```

76

77

## Usage Information Types

78

79

Data structures that represent how variables and types are used throughout code.

80

81

### UsageInfo

82

83

Contains comprehensive information about how an identifier was declared and referenced.

84

85

```typescript

86

interface UsageInfo {

87

declarations: ts.Identifier[];

88

domain: DeclarationDomain;

89

exported: boolean;

90

inGlobalScope: boolean;

91

uses: Usage[];

92

} { .api }

93

```

94

95

**Properties:**

96

- `declarations: ts.Identifier[]` - All locations where the identifier was declared

97

- `domain: DeclarationDomain` - Which domain(s) the identifier exists in

98

- `exported: boolean` - Whether the identifier was exported from its module/namespace scope

99

- `inGlobalScope: boolean` - Whether any declaration was in the global scope

100

- `uses: Usage[]` - All references to the identifier throughout the file

101

102

### Usage

103

104

Represents a single instance of an identifier being referenced.

105

106

```typescript

107

interface Usage {

108

domain: UsageDomain;

109

location: ts.Identifier;

110

} { .api }

111

```

112

113

**Properties:**

114

- `domain: UsageDomain` - Which domain(s) the usage occurs in

115

- `location: ts.Identifier` - The AST node representing the usage location

116

117

### DeclarationDomain

118

119

Enumeration defining the different domains where identifiers can be declared.

120

121

```typescript

122

enum DeclarationDomain {

123

Namespace = 1,

124

Type = 2,

125

Value = 4,

126

Any = 7, // Namespace | Type | Value

127

Import = 8

128

} { .api }

129

```

130

131

**Values:**

132

- `Namespace` - Declared in namespace space (modules, namespace declarations)

133

- `Type` - Declared in type space (interfaces, type aliases, type parameters)

134

- `Value` - Declared in value space (variables, functions, classes)

135

- `Any` - Exists in all three primary domains (enums, classes)

136

- `Import` - Special flag for imported identifiers

137

138

### UsageDomain

139

140

Enumeration defining the different domains where identifier usage can occur.

141

142

```typescript

143

enum UsageDomain {

144

Namespace = 1,

145

Type = 2,

146

Value = 4,

147

Any = 7, // Namespace | Type | Value

148

TypeQuery = 8, // typeof usage

149

ValueOrNamespace = 5 // Value | Namespace

150

} { .api }

151

```

152

153

**Values:**

154

- `Namespace` - Used as a namespace reference

155

- `Type` - Used in a type position

156

- `Value` - Used as a runtime value

157

- `Any` - Could be used in any domain

158

- `TypeQuery` - Used in `typeof` expressions

159

- `ValueOrNamespace` - Ambiguous value or namespace usage

160

161

**Example:**

162

```typescript

163

import { collectVariableUsage, DeclarationDomain, UsageDomain } from 'ts-api-utils';

164

165

function analyzeIdentifierUsage(sourceFile: ts.SourceFile) {

166

const usage = collectVariableUsage(sourceFile);

167

168

for (const [identifier, info] of usage) {

169

const name = identifier.getText();

170

171

// Check what domains the identifier is declared in

172

if (info.domain & DeclarationDomain.Type) {

173

console.log(`${name} is declared as a type`);

174

}

175

if (info.domain & DeclarationDomain.Value) {

176

console.log(`${name} is declared as a value`);

177

}

178

179

// Analyze usage patterns

180

const typeUsages = info.uses.filter(use => use.domain & UsageDomain.Type);

181

const valueUsages = info.uses.filter(use => use.domain & UsageDomain.Value);

182

183

console.log(`${name} used ${typeUsages.length} time(s) in type positions`);

184

console.log(`${name} used ${valueUsages.length} time(s) in value positions`);

185

}

186

}

187

```

188

189

## Domain Analysis

190

191

Functions for understanding the context and domain of identifier usage.

192

193

### getUsageDomain

194

195

Determines which domain(s) an identifier usage occurs within based on its syntactic context.

196

197

```typescript

198

function getUsageDomain(node: ts.Identifier): UsageDomain | undefined { .api }

199

```

200

201

**Parameters:**

202

- `node: ts.Identifier` - The identifier node to analyze

203

204

**Returns:** The usage domain(s) for the identifier, or `undefined` if the context doesn't represent a usage

205

206

**Example:**

207

```typescript

208

import { getUsageDomain, UsageDomain } from 'ts-api-utils';

209

210

function analyzeIdentifierContext(identifier: ts.Identifier) {

211

const domain = getUsageDomain(identifier);

212

213

if (!domain) {

214

console.log('Identifier is not a usage (e.g., in declaration position)');

215

return;

216

}

217

218

if (domain & UsageDomain.Type) {

219

console.log('Used in type context');

220

}

221

222

if (domain & UsageDomain.Value) {

223

console.log('Used in value context');

224

}

225

226

if (domain === UsageDomain.TypeQuery) {

227

console.log('Used in typeof expression');

228

}

229

}

230

```

231

232

### getDeclarationDomain

233

234

Determines which domain(s) an identifier declaration exists within based on its declaration context.

235

236

```typescript

237

function getDeclarationDomain(node: ts.Identifier): DeclarationDomain | undefined { .api }

238

```

239

240

**Parameters:**

241

- `node: ts.Identifier` - The identifier node in a declaration position

242

243

**Returns:** The declaration domain(s) for the identifier, or `undefined` if not a declaration

244

245

**Example:**

246

```typescript

247

import { getDeclarationDomain, DeclarationDomain } from 'ts-api-utils';

248

249

function analyzeDeclarationContext(identifier: ts.Identifier) {

250

const domain = getDeclarationDomain(identifier);

251

252

if (!domain) {

253

console.log('Identifier is not in declaration position');

254

return;

255

}

256

257

if (domain & DeclarationDomain.Type && domain & DeclarationDomain.Value) {

258

console.log('Declares both type and value (e.g., class or enum)');

259

} else if (domain & DeclarationDomain.Type) {

260

console.log('Type declaration (interface, type alias)');

261

} else if (domain & DeclarationDomain.Value) {

262

console.log('Value declaration (variable, function)');

263

}

264

265

if (domain & DeclarationDomain.Import) {

266

console.log('Import declaration');

267

}

268

}

269

```

270

271

## Scope Analysis

272

273

Understanding and working with TypeScript's scoping rules for accurate usage analysis.

274

275

### Scope Interface

276

277

The core interface for representing scopes in the usage analysis system.

278

279

```typescript

280

interface Scope {

281

addUse(use: Usage, scope?: Scope): void;

282

addVariable(

283

identifier: string,

284

name: ts.PropertyName,

285

selector: ScopeBoundarySelector,

286

exported: boolean,

287

domain: DeclarationDomain

288

): void;

289

createOrReuseEnumScope(name: string, exported: boolean): EnumScope;

290

createOrReuseNamespaceScope(

291

name: string,

292

exported: boolean,

293

ambient: boolean,

294

hasExportStatement: boolean

295

): NamespaceScope;

296

end(cb: UsageInfoCallback): void;

297

getDestinationScope(selector: ScopeBoundarySelector): Scope;

298

getFunctionScope(): Scope;

299

getVariables(): Map<string, InternalUsageInfo>;

300

markExported(name: ts.ModuleExportName, as?: ts.ModuleExportName): void;

301

} { .api }

302

```

303

304

### ScopeBoundary

305

306

Enumeration defining different types of scope boundaries in TypeScript.

307

308

```typescript

309

enum ScopeBoundary {

310

None = 0,

311

Function = 1,

312

Block = 2,

313

Type = 4,

314

ConditionalType = 8

315

} { .api }

316

```

317

318

**Values:**

319

- `None` - No scope boundary

320

- `Function` - Function scope boundary (functions, methods, constructors)

321

- `Block` - Block scope boundary (blocks, loops, conditionals)

322

- `Type` - Type parameter scope boundary

323

- `ConditionalType` - Conditional type scope boundary (`T extends U ? X : Y`)

324

325

### ScopeBoundarySelector

326

327

Enumeration for selecting which scope boundaries to consider during analysis.

328

329

```typescript

330

enum ScopeBoundarySelector {

331

Function = 1, // ScopeBoundary.Function

332

Block = 3, // Function | Block

333

InferType = 8, // ScopeBoundary.ConditionalType

334

Type = 7 // Block | Type

335

} { .api }

336

```

337

338

### isBlockScopeBoundary

339

340

Determines if a node represents a block scope boundary.

341

342

```typescript

343

function isBlockScopeBoundary(node: ts.Node): ScopeBoundary { .api }

344

```

345

346

**Parameters:**

347

- `node: ts.Node` - The AST node to check

348

349

**Returns:** The type of scope boundary represented by the node

350

351

**Example:**

352

```typescript

353

import { isBlockScopeBoundary, ScopeBoundary } from 'ts-api-utils';

354

355

function analyzeScope(node: ts.Node) {

356

const boundary = isBlockScopeBoundary(node);

357

358

if (boundary & ScopeBoundary.Function) {

359

console.log('Function scope boundary');

360

}

361

362

if (boundary & ScopeBoundary.Block) {

363

console.log('Block scope boundary');

364

}

365

366

if (boundary & ScopeBoundary.Type) {

367

console.log('Type parameter scope boundary');

368

}

369

}

370

```

371

372

## Advanced Usage Patterns

373

374

### Detecting Unused Variables

375

376

```typescript

377

import { collectVariableUsage } from 'ts-api-utils';

378

379

function findUnusedVariables(sourceFile: ts.SourceFile): ts.Identifier[] {

380

const usage = collectVariableUsage(sourceFile);

381

const unused: ts.Identifier[] = [];

382

383

for (const [identifier, info] of usage) {

384

// Skip exported variables - they might be used externally

385

if (info.exported) continue;

386

387

// Skip global declarations - they might be used by other files

388

if (info.inGlobalScope) continue;

389

390

// Variable is unused if it has no usage references

391

if (info.uses.length === 0) {

392

unused.push(identifier);

393

}

394

}

395

396

return unused;

397

}

398

```

399

400

### Analyzing Cross-Domain Usage

401

402

```typescript

403

import {

404

collectVariableUsage,

405

DeclarationDomain,

406

UsageDomain

407

} from 'ts-api-utils';

408

409

function analyzeCrossDomainUsage(sourceFile: ts.SourceFile) {

410

const usage = collectVariableUsage(sourceFile);

411

412

for (const [identifier, info] of usage) {

413

const name = identifier.getText();

414

415

// Find identifiers that exist in multiple domains

416

if (info.domain === DeclarationDomain.Any) {

417

console.log(`${name} exists in all domains (likely enum or class)`);

418

419

// Analyze how it's actually used

420

const typeUses = info.uses.filter(use => use.domain & UsageDomain.Type);

421

const valueUses = info.uses.filter(use => use.domain & UsageDomain.Value);

422

423

if (typeUses.length > 0 && valueUses.length > 0) {

424

console.log(` Used in both type and value contexts`);

425

} else if (typeUses.length > 0) {

426

console.log(` Only used in type contexts`);

427

} else if (valueUses.length > 0) {

428

console.log(` Only used in value contexts`);

429

}

430

}

431

}

432

}

433

```

434

435

### Scope-Aware Refactoring

436

437

```typescript

438

import { collectVariableUsage, DeclarationDomain } from 'ts-api-utils';

439

440

function canSafelyRename(

441

sourceFile: ts.SourceFile,

442

targetIdentifier: ts.Identifier,

443

newName: string

444

): boolean {

445

const usage = collectVariableUsage(sourceFile);

446

const targetInfo = usage.get(targetIdentifier);

447

448

if (!targetInfo) {

449

return false; // Identifier not found

450

}

451

452

// Check if new name conflicts with existing declarations

453

for (const [identifier, info] of usage) {

454

if (identifier.getText() === newName) {

455

// Check for domain conflicts

456

if (info.domain & targetInfo.domain) {

457

// Names would conflict in overlapping domains

458

return false;

459

}

460

}

461

}

462

463

return true;

464

}

465

```

466

467

### Type-Only Import Detection

468

469

```typescript

470

import {

471

collectVariableUsage,

472

DeclarationDomain,

473

UsageDomain

474

} from 'ts-api-utils';

475

476

function findTypeOnlyImports(sourceFile: ts.SourceFile): string[] {

477

const usage = collectVariableUsage(sourceFile);

478

const typeOnlyImports: string[] = [];

479

480

for (const [identifier, info] of usage) {

481

// Check if it's an import

482

if (!(info.domain & DeclarationDomain.Import)) continue;

483

484

// Check if all uses are in type contexts

485

const hasValueUsage = info.uses.some(use =>

486

use.domain & (UsageDomain.Value | UsageDomain.ValueOrNamespace)

487

);

488

489

if (!hasValueUsage && info.uses.length > 0) {

490

typeOnlyImports.push(identifier.getText());

491

}

492

}

493

494

return typeOnlyImports;

495

}

496

```

497

498

## Utility Functions

499

500

### identifierToKeywordKind

501

502

TypeScript version compatibility utility for working with identifier keyword kinds.

503

504

```typescript { .api }

505

function identifierToKeywordKind(node: ts.Identifier): ts.SyntaxKind | undefined;

506

```

507

508

**Parameters:**

509

- `node: ts.Identifier` - The identifier node to check

510

511

**Returns:** The syntax kind if the identifier represents a keyword, `undefined` otherwise

512

513

**Description:** This function provides compatibility support for TypeScript versions before 5.0 that don't have the built-in `identifierToKeywordKind` function.

514

515

**Example:**

516

```typescript

517

import { identifierToKeywordKind } from 'ts-api-utils';

518

519

function isKeywordIdentifier(identifier: ts.Identifier): boolean {

520

return identifierToKeywordKind(identifier) !== undefined;

521

}

522

```

523

524

## Integration with Other Modules

525

526

Usage Analysis works seamlessly with other ts-api-utils modules for comprehensive code analysis.

527

528

### With Node Analysis

529

530

```typescript

531

import {

532

collectVariableUsage,

533

isVariableDeclaration,

534

hasModifiers

535

} from 'ts-api-utils';

536

537

function analyzeVariableModifiers(sourceFile: ts.SourceFile) {

538

const usage = collectVariableUsage(sourceFile);

539

540

for (const [identifier, info] of usage) {

541

// Check each declaration location

542

for (const declaration of info.declarations) {

543

const parent = declaration.parent;

544

545

if (isVariableDeclaration(parent)) {

546

const variableStatement = parent.parent?.parent;

547

if (hasModifiers(variableStatement)) {

548

console.log(`${identifier.getText()} has modifiers`);

549

}

550

}

551

}

552

}

553

}

554

```

555

556

### With Type System Analysis

557

558

```typescript

559

import {

560

collectVariableUsage,

561

getCallSignaturesOfType

562

} from 'ts-api-utils';

563

564

function analyzeCallableVariables(

565

sourceFile: ts.SourceFile,

566

typeChecker: ts.TypeChecker

567

) {

568

const usage = collectVariableUsage(sourceFile);

569

570

for (const [identifier, info] of usage) {

571

// Only analyze value declarations

572

if (!(info.domain & DeclarationDomain.Value)) continue;

573

574

const type = typeChecker.getTypeAtLocation(identifier);

575

const signatures = getCallSignaturesOfType(type);

576

577

if (signatures.length > 0) {

578

console.log(`${identifier.getText()} is callable with ${signatures.length} signature(s)`);

579

}

580

}

581

}

582

```

583

584

## Best Practices

585

586

### Performance Considerations

587

588

1. **Cache results**: Usage analysis is expensive - cache results when analyzing multiple files

589

2. **Scope appropriately**: Only analyze files that need usage information

590

3. **Filter early**: Use domain filters to focus on relevant identifiers

591

592

### Accuracy Guidelines

593

594

1. **Consider all domains**: Remember that identifiers can exist in multiple domains simultaneously

595

2. **Handle imports specially**: Import declarations have special domain semantics

596

3. **Respect scope boundaries**: Usage analysis respects TypeScript's scoping rules

597

598

### Common Patterns

599

600

```typescript

601

// Safe usage information access

602

function getUsageInfo(

603

usage: Map<ts.Identifier, UsageInfo>,

604

identifier: ts.Identifier

605

): UsageInfo | undefined {

606

return usage.get(identifier);

607

}

608

609

// Domain-specific usage filtering

610

function getTypeUsages(info: UsageInfo): Usage[] {

611

return info.uses.filter(use => use.domain & UsageDomain.Type);

612

}

613

614

// Exported identifier detection

615

function isExported(info: UsageInfo): boolean {

616

return info.exported || info.inGlobalScope;

617

}

618

```

619

620

The Usage Analysis module provides the foundation for sophisticated TypeScript code analysis, enabling tools to understand identifier lifecycles, detect patterns, and perform safe transformations while respecting TypeScript's complex scoping and domain rules.