Overview
The Parser converts a stream of tokens from the Scanner into an Abstract Syntax Tree (AST). The AST is a tree representation of the source code structure that can be analyzed and transformed.Creating Source Files
ts.createSourceFile()
Creates a SourceFile AST from source text.string
required
The name of the file
string
required
The source code text
ScriptTarget | CreateSourceFileOptions
required
Either a script target or options object
boolean
Whether to set parent pointers on nodes (defaults to false)
ScriptKind
The kind of script (TS, JS, JSX, etc.)
SourceFile
The parsed source file AST
Example
import * as ts from 'typescript';
// Simple usage
const sourceFile = ts.createSourceFile(
'example.ts',
'const x: number = 42;',
ts.ScriptTarget.Latest,
true // setParentNodes
);
// With options
const sourceFile2 = ts.createSourceFile(
'example.tsx',
'const component = <div>Hello</div>;',
{
languageVersion: ts.ScriptTarget.ES2020,
},
true,
ts.ScriptKind.TSX
);
SourceFile Interface
The SourceFile represents the root of the AST and provides access to the parsed code structure.Key Properties
interface SourceFile extends Node {
fileName: string; // The file name
text: string; // The full source text
statements: NodeArray<Statement>; // Top-level statements
endOfFileToken: Token<SyntaxKind.EndOfFileToken>;
// Helper methods
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
getPositionOfLineAndCharacter(line: number, character: number): number;
getLineStarts(): readonly number[];
getLineEndOfPosition(pos: number): number;
}
Example
const sourceFile = ts.createSourceFile(
'example.ts',
`const x = 1;
const y = 2;`,
ts.ScriptTarget.Latest,
true
);
console.log(`File: ${sourceFile.fileName}`);
console.log(`Statements: ${sourceFile.statements.length}`);
// Get line and character from position
const pos = 15; // position in the source
const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
console.log(`Position ${pos} is at line ${line + 1}, column ${character + 1}`);
// Get position from line and character
const position = sourceFile.getPositionOfLineAndCharacter(1, 0);
console.log(`Line 2, column 1 starts at position ${position}`);
Working with the AST
Node Types
The AST consists of various node types, all extending the baseNode interface:
interface Node {
kind: SyntaxKind; // The type of node
pos: number; // Start position (includes trivia)
end: number; // End position
flags: NodeFlags; // Node flags
parent?: Node; // Parent node (if setParentNodes was true)
// Methods
getSourceFile(): SourceFile;
getChildCount(sourceFile?: SourceFile): number;
getChildAt(index: number, sourceFile?: SourceFile): Node;
getChildren(sourceFile?: SourceFile): Node[];
getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number;
getFullStart(): number;
getEnd(): number;
getWidth(sourceFile?: SourceFile): number;
getFullWidth(): number;
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
getFullText(sourceFile?: SourceFile): string;
getText(sourceFile?: SourceFile): string;
}
Traversing the AST
ts.forEachChild()
Visits each child node of a node.Node
required
The node to visit children of
(node: Node) => T | undefined
required
Callback for each child node
T | undefined
The first truthy value returned by the callback
function visit(node: ts.Node) {
console.log(`Node: ${ts.SyntaxKind[node.kind]}`);
ts.forEachChild(node, visit);
}
const sourceFile = ts.createSourceFile(
'example.ts',
'function add(a: number, b: number) { return a + b; }',
ts.ScriptTarget.Latest,
true
);
visit(sourceFile);
Recursive Visitor Pattern
function visitNode(node: ts.Node, depth: number = 0): void {
const indent = ' '.repeat(depth);
console.log(`${indent}${ts.SyntaxKind[node.kind]}`);
// Visit all children recursively
ts.forEachChild(node, (child) => visitNode(child, depth + 1));
}
const sourceFile = ts.createSourceFile(
'example.ts',
`
class Person {
constructor(public name: string) {}
greet() {
return "Hello, " + this.name;
}
}
`,
ts.ScriptTarget.Latest,
true
);
visitNode(sourceFile);
Type Guards
TypeScript provides type guard functions to check node types:import * as ts from 'typescript';
function analyzeNode(node: ts.Node) {
if (ts.isVariableStatement(node)) {
console.log('Variable statement');
} else if (ts.isFunctionDeclaration(node)) {
console.log('Function declaration');
if (node.name) {
console.log(` Name: ${node.name.text}`);
}
} else if (ts.isClassDeclaration(node)) {
console.log('Class declaration');
if (node.name) {
console.log(` Name: ${node.name.text}`);
}
} else if (ts.isInterfaceDeclaration(node)) {
console.log('Interface declaration');
} else if (ts.isTypeAliasDeclaration(node)) {
console.log('Type alias declaration');
}
}
// Declarations
ts.isFunctionDeclaration(node)
ts.isClassDeclaration(node)
ts.isInterfaceDeclaration(node)
ts.isVariableDeclaration(node)
ts.isMethodDeclaration(node)
ts.isPropertyDeclaration(node)
ts.isParameter(node)
// Statements
ts.isIfStatement(node)
ts.isForStatement(node)
ts.isWhileStatement(node)
ts.isReturnStatement(node)
ts.isExpressionStatement(node)
ts.isBlock(node)
// Expressions
ts.isBinaryExpression(node)
ts.isCallExpression(node)
ts.isPropertyAccessExpression(node)
ts.isElementAccessExpression(node)
ts.isIdentifier(node)
ts.isStringLiteral(node)
ts.isNumericLiteral(node)
ts.isArrowFunction(node)
// Types
ts.isTypeReferenceNode(node)
ts.isArrayTypeNode(node)
ts.isUnionTypeNode(node)
ts.isIntersectionTypeNode(node)
ts.isFunctionTypeNode(node)
Analyzing Specific Constructs
Analyzing Functions
import * as ts from 'typescript';
function analyzeFunctions(sourceFile: ts.SourceFile) {
function visit(node: ts.Node) {
if (ts.isFunctionDeclaration(node)) {
console.log('\nFunction Declaration:');
// Name
if (node.name) {
console.log(` Name: ${node.name.text}`);
}
// Parameters
console.log(' Parameters:');
node.parameters.forEach(param => {
const name = param.name.getText(sourceFile);
const type = param.type ? param.type.getText(sourceFile) : 'any';
console.log(` ${name}: ${type}`);
});
// Return type
if (node.type) {
console.log(` Return type: ${node.type.getText(sourceFile)}`);
}
// Modifiers
if (node.modifiers) {
const modifiers = node.modifiers
.map(m => ts.SyntaxKind[m.kind])
.join(', ');
console.log(` Modifiers: ${modifiers}`);
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
}
const code = `
export async function fetchData(url: string, options?: RequestInit): Promise<Response> {
return fetch(url, options);
}
`;
const sourceFile = ts.createSourceFile('example.ts', code, ts.ScriptTarget.Latest, true);
analyzeFunctions(sourceFile);
Analyzing Classes
import * as ts from 'typescript';
function analyzeClasses(sourceFile: ts.SourceFile) {
function visit(node: ts.Node) {
if (ts.isClassDeclaration(node)) {
console.log('\nClass Declaration:');
// Name
if (node.name) {
console.log(` Name: ${node.name.text}`);
}
// Heritage (extends/implements)
if (node.heritageClauses) {
node.heritageClauses.forEach(clause => {
const keyword = clause.token === ts.SyntaxKind.ExtendsKeyword
? 'extends'
: 'implements';
const types = clause.types
.map(t => t.expression.getText(sourceFile))
.join(', ');
console.log(` ${keyword}: ${types}`);
});
}
// Members
console.log(' Members:');
node.members.forEach(member => {
if (ts.isPropertyDeclaration(member)) {
const name = member.name.getText(sourceFile);
const type = member.type ? member.type.getText(sourceFile) : 'any';
console.log(` Property: ${name}: ${type}`);
} else if (ts.isMethodDeclaration(member)) {
const name = member.name.getText(sourceFile);
console.log(` Method: ${name}()`);
} else if (ts.isConstructorDeclaration(member)) {
console.log(` Constructor`);
}
});
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
}
const code = `
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
bark(): void {
console.log('Woof!');
}
}
`;
const sourceFile = ts.createSourceFile('example.ts', code, ts.ScriptTarget.Latest, true);
analyzeClasses(sourceFile);
Analyzing Imports and Exports
import * as ts from 'typescript';
function analyzeImportsExports(sourceFile: ts.SourceFile) {
function visit(node: ts.Node) {
if (ts.isImportDeclaration(node)) {
console.log('\nImport:');
// Module specifier
const moduleSpecifier = node.moduleSpecifier;
if (ts.isStringLiteral(moduleSpecifier)) {
console.log(` From: ${moduleSpecifier.text}`);
}
// Import clause
if (node.importClause) {
const clause = node.importClause;
// Default import
if (clause.name) {
console.log(` Default: ${clause.name.text}`);
}
// Named imports
if (clause.namedBindings) {
if (ts.isNamedImports(clause.namedBindings)) {
const names = clause.namedBindings.elements
.map(e => e.name.text)
.join(', ');
console.log(` Named: { ${names} }`);
} else if (ts.isNamespaceImport(clause.namedBindings)) {
console.log(` Namespace: * as ${clause.namedBindings.name.text}`);
}
}
}
} else if (ts.isExportDeclaration(node)) {
console.log('\nExport:');
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
const names = node.exportClause.elements
.map(e => e.name.text)
.join(', ');
console.log(` Named: { ${names} }`);
}
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
console.log(` From: ${node.moduleSpecifier.text}`);
}
} else if (ts.isExportAssignment(node)) {
console.log('\nExport default');
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
}
const code = `
import fs from 'fs';
import { readFile, writeFile } from 'fs/promises';
import * as path from 'path';
export { readFile };
export default class MyClass {}
`;
const sourceFile = ts.createSourceFile('example.ts', code, ts.ScriptTarget.Latest, true);
analyzeImportsExports(sourceFile);
Node Position Information
import * as ts from 'typescript';
function showPositions(node: ts.Node, sourceFile: ts.SourceFile) {
// Full start includes leading trivia (whitespace, comments)
const fullStart = node.getFullStart();
// Start excludes leading trivia
const start = node.getStart(sourceFile);
// End position
const end = node.getEnd();
// Widths
const fullWidth = node.getFullWidth();
const width = node.getWidth(sourceFile);
const leadingTriviaWidth = node.getLeadingTriviaWidth(sourceFile);
// Text
const fullText = node.getFullText(sourceFile);
const text = node.getText(sourceFile);
console.log('Position info:');
console.log(` Full range: ${fullStart} - ${end}`);
console.log(` Text range: ${start} - ${end}`);
console.log(` Leading trivia: ${leadingTriviaWidth} chars`);
console.log(` Full text: "${fullText}"`);
console.log(` Text: "${text}"`);
// Line and character
const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
console.log(` Location: line ${line + 1}, column ${character + 1}`);
}
Complete Example: Code Analyzer
import * as ts from 'typescript';
interface CodeStats {
functions: number;
classes: number;
interfaces: number;
variables: number;
imports: number;
exports: number;
lines: number;
}
function analyzeCode(fileName: string, sourceText: string): CodeStats {
const sourceFile = ts.createSourceFile(
fileName,
sourceText,
ts.ScriptTarget.Latest,
true
);
const stats: CodeStats = {
functions: 0,
classes: 0,
interfaces: 0,
variables: 0,
imports: 0,
exports: 0,
lines: sourceFile.getLineStarts().length
};
function visit(node: ts.Node) {
if (ts.isFunctionDeclaration(node)) {
stats.functions++;
} else if (ts.isClassDeclaration(node)) {
stats.classes++;
} else if (ts.isInterfaceDeclaration(node)) {
stats.interfaces++;
} else if (ts.isVariableStatement(node)) {
node.declarationList.declarations.forEach(() => {
stats.variables++;
});
} else if (ts.isImportDeclaration(node)) {
stats.imports++;
} else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
stats.exports++;
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return stats;
}
// Example usage
const code = `
import { Component } from 'react';
interface Props {
name: string;
}
class MyComponent extends Component<Props> {
render() {
return <div>{this.props.name}</div>;
}
}
export default MyComponent;
`;
const stats = analyzeCode('component.tsx', code);
console.log('Code Statistics:');
console.log(` Lines: ${stats.lines}`);
console.log(` Functions: ${stats.functions}`);
console.log(` Classes: ${stats.classes}`);
console.log(` Interfaces: ${stats.interfaces}`);
console.log(` Variables: ${stats.variables}`);
console.log(` Imports: ${stats.imports}`);
console.log(` Exports: ${stats.exports}`);
See Also
- Compiler API Overview
- Scanner API
- TypeChecker API
- AST Viewer - Interactive AST visualization tool