Using the TypeScript AST
Updated: 24 June 2026
Generating Typescript using AST’s
Some parsers other that the Typescript one is Esprima, Acorn, these are Javascript Parsers> Other languages also have parsers as well as tools called
Parser Generatorsthat enable you to generate a parser for a given language or usecase
Before getting started, you should first install the typescript package into your project with:
1yarn add typescriptUsing the typescript compiler you are able to parse TS into an AST as well as build code using the TS AST
You can also generate the
ts.code using the Typescript to AST Converter
Generate Types
1export type User = {2 name: string3 age: number4}To generate a type like above using the TS compiler you can use the following:
1import ts, { factory } from 'typescript'2import { writeFileSync } from 'fs'3
4// create name property5const nameProp = factory.createPropertySignature(6 undefined,7 factory.createIdentifier('name'),8 undefined,9 factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)10)11
12// create age property13const ageProp = factory.createPropertySignature(14 undefined,15 factory.createIdentifier('age'),16 undefined,17 factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)18)19
20// create User type21const userType = factory.createTypeAliasDeclaration(22 undefined,23 [factory.createModifier(ts.SyntaxKind.ExportKeyword)],24 factory.createIdentifier('User'),25 undefined,26 factory.createTypeLiteralNode([nameProp, ageProp])27)We can then use the generated type to build a more complex type:
1export type Admin = {2 user: User3}1const userProp = factory.createPropertySignature(2 undefined,3 factory.createIdentifier('user'),4 undefined,5 factory.createTypeReferenceNode(factory.createIdentifier('User'), undefined)6)7
8const adminType = factory.createTypeAliasDeclaration(9 undefined,10 [factory.createModifier(ts.SyntaxKind.ExportKeyword)],11 factory.createIdentifier('Admin'),12 undefined,13 factory.createTypeLiteralNode([userProp])14)Next, we’ll creata a NodeArray our of the two type declarations we want in our file like so:
1const nodes = factory.createNodeArray([userType, adminType])Printing to Code
We can then print the two types out as Typescript code with the following:
1const sourceFile = ts.createSourceFile(2 'placeholder.ts',3 '',4 ts.ScriptTarget.ESNext,5 true,6 ts.ScriptKind.TS7)The above sourcefile is a way for us to store some basic settings for the file we’re going to be saving our file content into, we’ve got the name placeholder.ts in the above case but this doesn’t really output the file we’re going to be outputing
Next, we can create a Printer instance and use it to generate our output file:
1const printer = ts.createPrinter()2
3const outputFile = printer.printList(ts.ListFormat.MultiLine, nodes, sourceFile)Lastly, we can print the outputFile using fs:
1import { writeFileSync } from 'fs'2
3//... other code from above4
5writeFileSync('./output.ts', outputFile)Which will output the following:
1export type User = {2 name: string3 age: number4}5export type Admin = {6 user: User7}Update 24 June 2026
Here’s a small implementation of a generic type visitor that can be used to scrape files for information using ts-morph
describe.ts
1/**2 * Basic pattern for visiting nodes and scraping metadata from them3 */4import {5 Project,6 SyntaxKind,7 Node,8 type ImplementedKindToNodeMappings,9} from 'ts-morph';10
11// this is the data that the visitor needs to return12interface Visited {13 kind: string;14 name?: string;15 children?: Visited[];16}17
18// some helpers to make the tree traversal statically typed19type SyntaxKindNames = keyof typeof SyntaxKind;20type SyntaxKindFromName<N extends SyntaxKindNames> = (typeof SyntaxKind)[N] &21 keyof ImplementedKindToNodeMappings;22
23type Visitors<T = unknown> = {24 [K in SyntaxKindNames]: (25 node: ImplementedKindToNodeMappings[SyntaxKindFromName<K>],26 ) => T | undefined;27};28
29// add any visitors here as needed30const visitors: Partial<Visitors<Visited>> = {31 Identifier: (node) => {32 return { kind: node.getKindName(), name: node.getText() };33 },34 SyntaxList: (node) => {35 return {36 kind: node.getKindName(),37 name: node.getKindName(),38 children: visitChildren(node),39 };40 },41 ImportDeclaration: (node) => {42 return {43 kind: node.getKindName(),44 name: node.getModuleSpecifier().getLiteralText(),45 children: [46 visitNode(node.getDefaultImport()),47 ...node.getNamedImports().map(visitNode),48 ].filter(exits),49 };50 },51 ImportSpecifier: (node) => {52 return {53 kind: node.getKindName(),54 name: node.getName(),55 isTypeOnly: node.isTypeOnly(),56 };57 },58 ClassDeclaration: (node) => {59 return {60 kind: node.getKindName(),61 name: node.getName(),62 children: [63 ...node.getProperties().map(visitNode),64 ...node.getMembers().map(visitNode),65 ],66 };67 },68 ObjectLiteralExpression: (node) => {69 return {70 kind: node.getKindName(),71 children: node.getProperties().map(visitNode),72 };73 },74 PropertyAssignment: (node) => {75 return {76 kind: node.getKindName(),77 name: node.getName(),78 type: node.getType().getText(),79 };80 },81 PropertyDeclaration: (node) => {82 return {83 kind: node.getKindName(),84 name: node.getName(),85 isReadonly: node.isReadonly,86 type: node.getType().getText(),87 };88 },89 MethodDeclaration: (node) => {90 return {91 kind: node.getKindName(),92 name: node.getName(),93 isReadonly: node.isReadonly,94 type: node.getType().getText(),95 };96 },97 StringLiteral: (node) => {98 return {99 kind: node.getKindName(),100 value: node.getLiteralValue(),101 };102 },103};104
105function exits<T>(v?: T): v is Exclude<T, undefined> {106 return typeof v !== 'undefined';107}108
109function visitNode(c?: Node) {110 if (!c) {111 return undefined;112 }113
114 const handle = visitors[c.getKindName() as keyof Visitors];115
116 if (!handle) {117 console.warn('No handler for', c.getKindName());118 }119
120 const typedHandle = handle as (c: Node) => Visited | undefined;121
122 return typedHandle?.(c);123}124
125function visitChildren(node: Node) {126 return node127 .getChildren()128 .map(visitNode)129 .filter((c) => !!c);130}131
132const path = process.argv[process.argv.length - 1];133const project = new Project();134
135const sourceFile = project.addSourceFileAtPath(path);136
137const result = visitChildren(sourceFile);138
139// just print the result as JSON, but this can of course be whatever140console.log(JSON.stringify(result));This can be used by running it directly with node. Piping to jq can also provide some nice highlighting:
1node describe.ts path/to/file/to/describe/example.ts | jqReferences
In addition to using the AST Direcly, some libraries that are also handy for working with the Typescript AST are:
And some more general AST related tools: