A simpler & friendlier way to traverse the Typescript AST.
Useful links regarding TypeScript AST:
- TypeScript AST Viewer: excellent tool for visualizing and understanding the AST. It will even provide you the code that generates the AST.
- Using the Compiler API
- Authenticate to Github Packages and add your token to your local
~/.npmrc
file by adding the following line. You can set up your token here. Make sure you select the scoperead:packages
.
//npm.pkg.github.com/:_authToken=TOKEN
- Create or edit an
.npmrc
in your project's root directory and add the following line. This will indicatenpm
to fetch the packages from this registry.
@proglang:registry=https://npm.pkg.github.com
You can find more information about Github Packages here
- Finally, run
npm install
as with any other package:
$ npm install @proglang/ts-ast-utils
const tsAstUtils = require('@proglang/ts-ast-utils');
const ts = require('typescript');
const declarationFile = `
export function f(a: string, ...b: number[]): void;
export interface Foo {
a?: string;
}
`;
const ast = tsAstUtils.createFromString(declarationFile);
const tags = new Set();
tsAstUtils.accept(ast, {
[ts.SyntaxKind.DotDotDotToken]: () => {
tags.add('dot-dot-dot-token');
},
[ts.SyntaxKind.QuestionToken]: () => {
tags.add('question-token');
},
[ts.SyntaxKind.ArrayType]: (node) => {
tags.add('array');
node.elementType.kind === ts.SyntaxKind.NumberKeyword && tags.add('array-number');
},
});
console.log(`Tags: ${Array.from(tags).join(', ')}`);
Check out more examples here.
Easily traverse the AST starting from node
. Define a visitor
object that allows you to run a pre
condition, syntax kind specific callbacks and post
conditions (in that order) on each node. You can also define your own traverse
method to control how to traverse the AST.
Type: ts.Node
Type: object
tsAstUtils.accept(ast, {
[ts.SyntaxKind.DotDotDotToken]: (node) => {
// Callback will only be executed for `DotDotDotToken` nodes
},
[ts.SyntaxKind.QuestionToken]: (node) => {
// ...
},
// ...
});
Will be executed before visiting any node.
// preorder traversal
accept(ast, {
pre: (node) => {
console.log(`${node.pos}:${node.end}:${node.kind}`);
},
});
Will be executed after visiting any node.
// postorder traversal
accept(ast, {
post: (node) => {
console.log(`${node.pos}:${node.end}:${node.kind}`);
},
});
If desired, you can return a value in the post()
callback. The return value on the last executed node will be the return value of accept()
.
// Computing the depth of an AST using the default traverse method.
const nodesDepth = new WeakMap();
const depth = accept(ast, {
post: (node) => {
const children = [];
ts.forEachChild(node, (child) => {
children.push(child);
});
if (children.length === 0) {
// Node has not been visited yet
nodesDepth.set(node, 1);
} else {
const childrenDepth = children.map((child) => +(nodesDepth.get(child) || 0));
const nodeDepth = 1 + Math.max(...childrenDepth);
nodesDepth.set(node, nodeDepth);
}
return nodesDepth.get(node) || 0;
},
});
Define the traversal method.
Default: ts.forEachChild()
// BFS traveral of an AST
const queue = [];
const bfsTraverse = (node, visit) => {
const children = [];
ts.forEachChild(node, (child) => {
children.push(child);
});
children.forEach((c) => {
queue.push(c);
});
const last = queue.shift();
last && visit(last);
};
accept(ast, {
pre: (node) => {
console.log(`${node.pos}:${node.end}:${node.kind}`);
},
traverse: bfsTraverse,
});
Type: string
const declarationFile = `
export function f(a string): void;
`;
const ast = createFromString(declarationFile);
Prints an AST.
Type: ts.Node