# TypeScript Compiler and Language Services API TypeScript is a language for application-scale JavaScript development that adds optional types to JavaScript. The TypeScript compiler provides a comprehensive API for programmatic access to parsing, type checking, transforming, and emitting JavaScript code. This enables developers to build powerful tools like IDEs, linters, bundlers, documentation generators, and code transformation utilities that leverage TypeScript's sophisticated type system and AST manipulation capabilities. The TypeScript API is organized into several core components: the Compiler API for creating programs and emitting code, the Language Service API for editor integrations providing completions and diagnostics, the Type Checker for semantic analysis, and various utility APIs for AST manipulation, module resolution, and incremental compilation. All APIs are exported through the main `typescript` module and provide both low-level AST access and high-level abstractions for common use cases. ## Minimal Compiler - Basic Program Creation and Compilation Create a simple TypeScript compiler that compiles files and reports diagnostics with proper error handling and exit codes. ```typescript import * as ts from "typescript"; function compile(fileNames: string[], options: ts.CompilerOptions): void { let program = ts.createProgram(fileNames, options); let emitResult = program.emit(); let allDiagnostics = ts .getPreEmitDiagnostics(program) .concat(emitResult.diagnostics); allDiagnostics.forEach(diagnostic => { if (diagnostic.file) { let { line, character } = ts.getLineAndCharacterOfPosition( diagnostic.file, diagnostic.start! ); let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); console.log( `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` ); } else { console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")); } }); let exitCode = emitResult.emitSkipped ? 1 : 0; console.log(`Process exiting with code '${exitCode}'.`); process.exit(exitCode); } compile(process.argv.slice(2), { noEmitOnError: true, noImplicitAny: true, target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS }); ``` ## Quick Transpilation - String to String Transformation Transpile TypeScript code to JavaScript without type checking using the transpileModule API for fast code transformation. ```typescript import * as ts from "typescript"; const source = "let x: string = 'string'"; let result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2015 } }); console.log(JSON.stringify(result)); // Output: {"outputText":"let x = 'string';\n","diagnostics":[],"sourceMapText":"..."} ``` ## Declaration Generation - Generate Type Definitions Without Type Checking Generate TypeScript declaration files (.d.ts) from TypeScript source without running the full type checker using transpileDeclaration for fast isolated declaration emit. ```typescript import * as ts from "typescript"; const source = ` export const greet = (name: string) => { return "Hello, " + name; }; export class Calculator { add(a: number, b: number) { return a + b; } } `; let result = ts.transpileDeclaration(source, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2020 } }); console.log(result.outputText); // Output: // export declare const greet: (name: string) => string; // export declare class Calculator { // add(a: number, b: number): number; // } ``` ## Declaration File Generation - Extract Types from JavaScript Generate TypeScript declaration files (.d.ts) from JavaScript source files with JSDoc annotations for automatic type definition creation. ```typescript import * as ts from "typescript"; function compile(fileNames: string[], options: ts.CompilerOptions): void { const createdFiles: Record = {}; const host = ts.createCompilerHost(options); host.writeFile = (fileName: string, contents: string) => { createdFiles[fileName] = contents; }; const program = ts.createProgram(fileNames, options, host); program.emit(); fileNames.forEach(file => { console.log("### JavaScript\n"); console.log(host.readFile(file)); console.log("### Type Definition\n"); const dts = file.replace(".js", ".d.ts"); console.log(createdFiles[dts]); }); } compile(process.argv.slice(2), { allowJs: true, declaration: true, emitDeclarationOnly: true, }); ``` ## AST Traversal and Printing - Extract Code Sections Parse TypeScript files, traverse the AST to find specific declarations, and print them using the printer API for code extraction and documentation generation. ```typescript import * as ts from "typescript"; function extract(file: string, identifiers: string[]): void { let program = ts.createProgram([file], { allowJs: true }); const sourceFile = program.getSourceFile(file); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const unfoundNodes: [string, ts.Node][] = []; const foundNodes: [string, ts.Node][] = []; ts.forEachChild(sourceFile!, node => { let name = ""; if (ts.isFunctionDeclaration(node) && node.name) { name = node.name.text; node.body = undefined; // Hide implementation } else if (ts.isVariableStatement(node)) { name = node.declarationList.declarations[0].name.getText(sourceFile!); } else if (ts.isInterfaceDeclaration(node)) { name = node.name.text; } const container = identifiers.includes(name) ? foundNodes : unfoundNodes; container.push([name, node]); }); if (!foundNodes.length) { console.log( `Could not find any of ${identifiers.join(", ")} in ${file}, found: ${ unfoundNodes.filter(f => f[0]).map(f => f[0]).join(", ") }.` ); process.exitCode = 1; } else { foundNodes.forEach(f => { const [name, node] = f; console.log("### " + name + "\n"); console.log(printer.printNode(ts.EmitHint.Unspecified, node, sourceFile!)); }); } } extract(process.argv[2], process.argv.slice(3)); ``` ## Simple Linter - AST-Based Code Analysis Build a custom linter that traverses the AST recursively to enforce code style rules with position-aware error reporting. ```typescript import { readFileSync } from "fs"; import * as ts from "typescript"; export function delint(sourceFile: ts.SourceFile) { delintNode(sourceFile); function delintNode(node: ts.Node) { switch (node.kind) { case ts.SyntaxKind.ForStatement: case ts.SyntaxKind.ForInStatement: case ts.SyntaxKind.WhileStatement: case ts.SyntaxKind.DoStatement: if ((node as ts.IterationStatement).statement.kind !== ts.SyntaxKind.Block) { report( node, 'A looping statement\'s contents should be wrapped in a block body.' ); } break; case ts.SyntaxKind.IfStatement: const ifStatement = node as ts.IfStatement; if (ifStatement.thenStatement.kind !== ts.SyntaxKind.Block) { report( ifStatement.thenStatement, 'An if statement\'s contents should be wrapped in a block body.' ); } if ( ifStatement.elseStatement && ifStatement.elseStatement.kind !== ts.SyntaxKind.Block && ifStatement.elseStatement.kind !== ts.SyntaxKind.IfStatement ) { report( ifStatement.elseStatement, 'An else statement\'s contents should be wrapped in a block body.' ); } break; case ts.SyntaxKind.BinaryExpression: const op = (node as ts.BinaryExpression).operatorToken.kind; if (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken) { report(node, 'Use \'===\' and \'!==\'.'); } break; } ts.forEachChild(node, delintNode); } function report(node: ts.Node, message: string) { const { line, character } = sourceFile.getLineAndCharacterOfPosition( node.getStart() ); console.log(`${sourceFile.fileName} (${line + 1},${character + 1}): ${message}`); } } const fileNames = process.argv.slice(2); fileNames.forEach(fileName => { const sourceFile = ts.createSourceFile( fileName, readFileSync(fileName).toString(), ts.ScriptTarget.ES2015, true ); delint(sourceFile); }); ``` ## Watch Mode - Incremental Compilation Create a watch program with incremental builder for continuous compilation that only rebuilds changed files and their dependencies. ```typescript import ts = require("typescript"); const formatHost: ts.FormatDiagnosticsHost = { getCanonicalFileName: path => path, getCurrentDirectory: ts.sys.getCurrentDirectory, getNewLine: () => ts.sys.newLine }; function watchMain() { const configPath = ts.findConfigFile( "./", ts.sys.fileExists, "tsconfig.json" ); if (!configPath) { throw new Error("Could not find a valid 'tsconfig.json'."); } const createProgram = ts.createSemanticDiagnosticsBuilderProgram; const host = ts.createWatchCompilerHost( configPath, {}, ts.sys, createProgram, reportDiagnostic, reportWatchStatusChanged ); const origCreateProgram = host.createProgram; host.createProgram = (rootNames, options, host, oldProgram) => { console.log("** We're about to create the program! **"); return origCreateProgram(rootNames, options, host, oldProgram); }; const origPostProgramCreate = host.afterProgramCreate; host.afterProgramCreate = program => { console.log("** We finished making the program! **"); origPostProgramCreate!(program); }; ts.createWatchProgram(host); } function reportDiagnostic(diagnostic: ts.Diagnostic) { console.error( "Error", diagnostic.code, ":", ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()) ); } function reportWatchStatusChanged(diagnostic: ts.Diagnostic) { console.info(ts.formatDiagnostic(diagnostic, formatHost)); } watchMain(); ``` ## Language Service - Editor Integration Implement incremental compilation using the Language Service API for editor-like functionality with file watching and on-demand emit. ```typescript import * as fs from "fs"; import * as ts from "typescript"; function watch(rootFileNames: string[], options: ts.CompilerOptions) { const files: Record = {}; rootFileNames.forEach(fileName => { files[fileName] = { version: 0 }; }); const servicesHost: ts.LanguageServiceHost = { getScriptFileNames: () => rootFileNames, getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(), getScriptSnapshot: fileName => { if (!fs.existsSync(fileName)) { return undefined; } return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); }, getCurrentDirectory: () => process.cwd(), getCompilationSettings: () => options, getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, readDirectory: ts.sys.readDirectory, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories, }; const services = ts.createLanguageService( servicesHost, ts.createDocumentRegistry() ); rootFileNames.forEach(fileName => { emitFile(fileName); fs.watchFile(fileName, { persistent: true, interval: 250 }, (curr, prev) => { if (+curr.mtime <= +prev.mtime) { return; } files[fileName].version++; emitFile(fileName); }); }); function emitFile(fileName: string) { let output = services.getEmitOutput(fileName); if (!output.emitSkipped) { console.log(`Emitting ${fileName}`); } else { console.log(`Emitting ${fileName} failed`); logErrors(fileName); } output.outputFiles.forEach(o => { fs.writeFileSync(o.name, o.text, "utf8"); }); } function logErrors(fileName: string) { let allDiagnostics = services .getCompilerOptionsDiagnostics() .concat(services.getSyntacticDiagnostics(fileName)) .concat(services.getSemanticDiagnostics(fileName)); allDiagnostics.forEach(diagnostic => { let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); if (diagnostic.file) { let { line, character } = diagnostic.file.getLineAndCharacterOfPosition( diagnostic.start! ); console.log( ` Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` ); } else { console.log(` Error: ${message}`); } }); } } const currentDirectoryFiles = fs .readdirSync(process.cwd()) .filter(fileName => fileName.endsWith(".ts")); watch(currentDirectoryFiles, { module: ts.ModuleKind.CommonJS }); ``` ## Custom Module Resolution Override the default module resolution strategy by implementing custom CompilerHost.resolveModuleNames for specialized module lookup paths. ```typescript import * as ts from "typescript"; import * as path from "path"; function createCompilerHost( options: ts.CompilerOptions, moduleSearchLocations: string[] ): ts.CompilerHost { return { getSourceFile, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content) => ts.sys.writeFile(fileName, content), getCurrentDirectory: () => ts.sys.getCurrentDirectory(), getDirectories: path => ts.sys.getDirectories(path), getCanonicalFileName: fileName => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), getNewLine: () => ts.sys.newLine, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, fileExists, readFile, resolveModuleNames }; function fileExists(fileName: string): boolean { return ts.sys.fileExists(fileName); } function readFile(fileName: string): string | undefined { return ts.sys.readFile(fileName); } function getSourceFile( fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void ) { const sourceText = ts.sys.readFile(fileName); return sourceText !== undefined ? ts.createSourceFile(fileName, sourceText, languageVersion) : undefined; } function resolveModuleNames( moduleNames: string[], containingFile: string ): (ts.ResolvedModule | undefined)[] { const resolvedModules: (ts.ResolvedModule | undefined)[] = []; for (const moduleName of moduleNames) { let result = ts.resolveModuleName(moduleName, containingFile, options, { fileExists, readFile }); if (result.resolvedModule) { resolvedModules.push(result.resolvedModule); } else { for (const location of moduleSearchLocations) { const modulePath = path.join(location, moduleName + ".d.ts"); if (fileExists(modulePath)) { resolvedModules.push({ resolvedFileName: modulePath }); break; } } } } return resolvedModules; } } function compile(sourceFiles: string[], moduleSearchLocations: string[]): void { const options: ts.CompilerOptions = { module: ts.ModuleKind.AMD, target: ts.ScriptTarget.ES5 }; const host = createCompilerHost(options, moduleSearchLocations); const program = ts.createProgram(sourceFiles, options, host); program.emit(); } compile(["app.ts"], ["./custom_modules", "./node_modules/@types"]); ``` ## AST Creation and Printing - Generate Code Use the factory API to programmatically create TypeScript AST nodes and print them to source code for code generation tools. ```typescript import ts = require("typescript"); function makeFactorialFunction() { const functionName = ts.factory.createIdentifier("factorial"); const paramName = ts.factory.createIdentifier("n"); const parameter = ts.factory.createParameterDeclaration( undefined, undefined, undefined, paramName ); const condition = ts.factory.createBinaryExpression( paramName, ts.SyntaxKind.LessThanEqualsToken, ts.factory.createNumericLiteral(1) ); const ifBody = ts.factory.createBlock( [ts.factory.createReturnStatement(ts.factory.createNumericLiteral(1))], true ); const decrementedArg = ts.factory.createBinaryExpression( paramName, ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(1) ); const recurse = ts.factory.createBinaryExpression( paramName, ts.SyntaxKind.AsteriskToken, ts.factory.createCallExpression(functionName, undefined, [decrementedArg]) ); const statements = [ ts.factory.createIfStatement(condition, ifBody), ts.factory.createReturnStatement(recurse) ]; return ts.factory.createFunctionDeclaration( undefined, [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], undefined, functionName, undefined, [parameter], ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), ts.factory.createBlock(statements, true) ); } const resultFile = ts.createSourceFile( "someFileName.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS ); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const result = printer.printNode( ts.EmitHint.Unspecified, makeFactorialFunction(), resultFile ); console.log(result); // Output: // export function factorial(n): number { // if (n <= 1) { // return 1; // } // return n * factorial(n - 1); // } ``` ## Type Checker - Extract Class Documentation Use the TypeChecker API to walk the AST, resolve symbols and types, and extract JSDoc documentation for automated API documentation generation. ```typescript import * as ts from "typescript"; import * as fs from "fs"; interface DocEntry { name?: string; fileName?: string; documentation?: string; type?: string; constructors?: DocEntry[]; parameters?: DocEntry[]; returnType?: string; } function generateDocumentation( fileNames: string[], options: ts.CompilerOptions ): void { let program = ts.createProgram(fileNames, options); let checker = program.getTypeChecker(); let output: DocEntry[] = []; for (const sourceFile of program.getSourceFiles()) { if (!sourceFile.isDeclarationFile) { ts.forEachChild(sourceFile, visit); } } fs.writeFileSync("classes.json", JSON.stringify(output, undefined, 4)); function visit(node: ts.Node) { if (!isNodeExported(node)) { return; } if (ts.isClassDeclaration(node) && node.name) { let symbol = checker.getSymbolAtLocation(node.name); if (symbol) { output.push(serializeClass(symbol)); } } else if (ts.isModuleDeclaration(node)) { ts.forEachChild(node, visit); } } function serializeSymbol(symbol: ts.Symbol): DocEntry { return { name: symbol.getName(), documentation: ts.displayPartsToString( symbol.getDocumentationComment(checker) ), type: checker.typeToString( checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!) ) }; } function serializeClass(symbol: ts.Symbol) { let details = serializeSymbol(symbol); let constructorType = checker.getTypeOfSymbolAtLocation( symbol, symbol.valueDeclaration! ); details.constructors = constructorType .getConstructSignatures() .map(serializeSignature); return details; } function serializeSignature(signature: ts.Signature) { return { parameters: signature.parameters.map(serializeSymbol), returnType: checker.typeToString(signature.getReturnType()), documentation: ts.displayPartsToString( signature.getDocumentationComment(checker) ) }; } function isNodeExported(node: ts.Node): boolean { return ( (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) ); } } generateDocumentation(process.argv.slice(2), { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS }); ``` ## Summary The TypeScript Compiler API serves as the foundation for the entire TypeScript ecosystem, powering everything from VS Code's IntelliSense to build tools like Webpack and bundlers like esbuild. The primary use cases include building compilers and transpilers with `createProgram` and `emit`, implementing type-aware linters and code analyzers using the `TypeChecker`, creating editor integrations with the `LanguageService` for completions and diagnostics, generating code with the `factory` API, and building custom transformers for code manipulation. The API's incremental compilation capabilities through Builder and Watch modes enable efficient rebuilds in large codebases by only reprocessing changed files and their dependencies. Integration patterns typically involve choosing the right API level for your use case: use `transpileModule` for simple string-to-string JavaScript transformations without type checking, use `transpileDeclaration` for fast isolated declaration file generation without full type checking, use `createProgram` for full compilation with type checking and emit, use `createLanguageService` for editor-like features with incremental updates, and use `createWatchProgram` for continuous compilation. Custom `CompilerHost` implementations allow for virtual file systems and custom module resolution strategies, while the `TypeChecker` provides deep semantic analysis for extracting type information, resolving symbols, and understanding type relationships. All APIs work with TypeScript's immutable AST representation, making it safe to traverse and analyze code across multiple tools in a build pipeline.