Utility functions for working with TypeScript's Abstract Syntax Tree (AST) and type system
—
Comprehensive utilities for navigating, analyzing, and manipulating TypeScript AST nodes. These functions provide efficient traversal patterns, token iteration, comment processing, position mapping, and node relationship analysis.
Utilities for iterating through tokens and trivia in TypeScript source code.
/**
* Iterate through all tokens in a node
* @param node - AST node to traverse
* @param cb - Callback function called for each token
* @param sourceFile - Source file (optional, inferred from node if not provided)
*/
function forEachToken(node: ts.Node, cb: (node: ts.Node) => void, sourceFile?: ts.SourceFile): void;
/**
* Iterate through all tokens including trivia (whitespace, comments)
* @param node - AST node to traverse
* @param cb - Callback function called for each token with trivia information
* @param sourceFile - Source file (optional, inferred from node if not provided)
*/
function forEachTokenWithTrivia(node: ts.Node, cb: ForEachTokenCallback, sourceFile?: ts.SourceFile): void;
/**
* Callback type for token iteration with trivia
* @param fullText - Full text of the source file
* @param kind - Syntax kind of the token
* @param range - Text range of the token
* @param parent - Parent node of the token
*/
type ForEachTokenCallback = (fullText: string, kind: ts.SyntaxKind, range: ts.TextRange, parent: ts.Node) => void;Utilities for working with comments in TypeScript source code.
/**
* Iterate through all comments in a node
* @param node - AST node to search for comments
* @param cb - Callback function called for each comment
* @param sourceFile - Source file (optional, inferred from node if not provided)
*/
function forEachComment(node: ts.Node, cb: ForEachCommentCallback, sourceFile?: ts.SourceFile): void;
/**
* Callback type for comment iteration
* @param fullText - Full text of the source file
* @param comment - Comment range information
*/
type ForEachCommentCallback = (fullText: string, comment: ts.CommentRange) => void;
/**
* Get comment at specific position
* @param sourceFile - Source file to search
* @param pos - Position to check for comments
* @param parent - Optional parent node to limit search scope
* @returns Comment range if found, undefined otherwise
*/
function getCommentAtPosition(sourceFile: ts.SourceFile, pos: number, parent?: ts.Node): ts.CommentRange | undefined;
/**
* Check if position is inside a comment
* @param sourceFile - Source file to check
* @param pos - Position to check
* @param parent - Optional parent node to limit search scope
* @returns true if position is within a comment
*/
function isPositionInComment(sourceFile: ts.SourceFile, pos: number, parent?: ts.Node): boolean;
/**
* Extract text content from a comment range
* @param sourceText - Full source text
* @param comment - Comment range to extract
* @returns Comment text without comment syntax markers
*/
function commentText(sourceText: string, comment: ts.CommentRange): string;Utilities for navigating between related AST nodes.
/**
* Get the previous token before a node
* @param node - Starting node
* @param sourceFile - Source file (optional, inferred from node if not provided)
* @returns Previous token or undefined if none exists
*/
function getPreviousToken(node: ts.Node, sourceFile?: ts.SourceFile): ts.Node | undefined;
/**
* Get the next token after a node
* @param node - Starting node
* @param sourceFile - Source file (optional, inferred from node if not provided)
* @returns Next token or undefined if none exists
*/
function getNextToken(node: ts.Node, sourceFile?: ts.SourceFile): ts.Node | undefined;
/**
* Get token at or following a specific position
* @param parent - Parent node to search within
* @param pos - Position to find token at
* @param sourceFile - Source file (optional, inferred from parent if not provided)
* @param allowJsDoc - Whether to include JSDoc comments in search
* @returns Token at position or undefined if none found
*/
function getTokenAtPosition(parent: ts.Node, pos: number, sourceFile?: ts.SourceFile, allowJsDoc?: boolean): ts.Node | undefined;
/**
* Find child node of specific syntax kind
* @param node - Parent node to search
* @param kind - Syntax kind to find
* @param sourceFile - Source file (optional, inferred from node if not provided)
* @returns Child token of specified kind or undefined
*/
function getChildOfKind<T extends ts.SyntaxKind>(node: ts.Node, kind: T, sourceFile?: ts.SourceFile): ts.Token<T> | undefined;Utilities for navigating between statements in block-like structures.
/**
* Get the previous statement in a block
* @param statement - Current statement
* @returns Previous statement or undefined if first statement
*/
function getPreviousStatement(statement: ts.Statement): ts.Statement | undefined;
/**
* Get the next statement in a block
* @param statement - Current statement
* @returns Next statement or undefined if last statement
*/
function getNextStatement(statement: ts.Statement): ts.Statement | undefined;Utilities for finding nodes and analyzing positions within source code.
/**
* Get the deepest AST node at a specific position
* @param node - Root node to search from
* @param pos - Position to find node at
* @returns Deepest node at position or undefined if position is outside node range
*/
function getAstNodeAtPosition(node: ts.Node, pos: number): ts.Node | undefined;
/**
* Get wrapped node at position (for converted AST)
* @param wrap - Wrapped node to search
* @param pos - Position to find node at
* @returns Wrapped node at position or undefined
*/
function getWrappedNodeAtPosition(wrap: NodeWrap, pos: number): NodeWrap | undefined;Utilities for checking and categorizing syntax kinds.
/**
* Check if syntax kind represents a token
* @param kind - Syntax kind to check
* @returns true if kind is a token
*/
function isTokenKind(kind: ts.SyntaxKind): boolean;
/**
* Check if syntax kind represents a node
* @param kind - Syntax kind to check
* @returns true if kind is a node
*/
function isNodeKind(kind: ts.SyntaxKind): boolean;
/**
* Check if syntax kind represents an assignment operator
* @param kind - Syntax kind to check
* @returns true if kind is an assignment operator
*/
function isAssignmentKind(kind: ts.SyntaxKind): boolean;
/**
* Check if syntax kind represents a type node
* @param kind - Syntax kind to check
* @returns true if kind is a type node
*/
function isTypeNodeKind(kind: ts.SyntaxKind): boolean;
/**
* Check if syntax kind represents a JSDoc node
* @param kind - Syntax kind to check
* @returns true if kind is a JSDoc node
*/
function isJsDocKind(kind: ts.SyntaxKind): boolean;
/**
* Check if syntax kind represents a keyword
* @param kind - Syntax kind to check
* @returns true if kind is a keyword
*/
function isKeywordKind(kind: ts.SyntaxKind): boolean;Utilities for converting AST to wrapped format for advanced analysis.
/**
* Convert TypeScript AST to wrapped format with navigation aids
* @param sourceFile - Source file to convert
* @returns Converted AST with wrapped nodes and flat array
*/
function convertAst(sourceFile: ts.SourceFile): ConvertedAst;
/**
* Wrapped node with navigation properties
*/
interface NodeWrap {
node: ts.Node;
kind: ts.SyntaxKind;
children: NodeWrap[];
next?: NodeWrap;
skip?: NodeWrap;
parent?: NodeWrap;
}
/**
* Wrapped AST root node
*/
interface WrappedAst extends NodeWrap {
node: ts.SourceFile;
next: NodeWrap;
skip: undefined;
parent: undefined;
}
/**
* Result of AST conversion
*/
interface ConvertedAst {
wrapped: WrappedAst;
flat: ReadonlyArray<ts.Node>;
}Usage Examples:
import * as ts from "typescript";
import {
forEachToken,
forEachComment,
getPreviousToken,
getAstNodeAtPosition,
convertAst
} from "tsutils/util";
// Token iteration example
function analyzeTokens(sourceFile: ts.SourceFile) {
forEachToken(sourceFile, (token) => {
console.log(`Token: ${ts.SyntaxKind[token.kind]} at ${token.pos}-${token.end}`);
if (token.kind === ts.SyntaxKind.Identifier) {
const identifier = token as ts.Identifier;
console.log(` Identifier text: ${identifier.text}`);
}
});
}
// Comment analysis example
function analyzeComments(sourceFile: ts.SourceFile) {
forEachComment(sourceFile, (fullText, comment) => {
const commentText = fullText.substring(comment.pos, comment.end);
console.log(`Comment: ${commentText}`);
console.log(` Type: ${comment.kind === ts.SyntaxKind.SingleLineCommentTrivia ? 'single-line' : 'multi-line'}`);
});
}
// Navigation example
function analyzeNodeContext(node: ts.Node, sourceFile: ts.SourceFile) {
const previousToken = getPreviousToken(node, sourceFile);
const nextToken = getNextToken(node, sourceFile);
console.log(`Node: ${ts.SyntaxKind[node.kind]}`);
console.log(` Previous token: ${previousToken ? ts.SyntaxKind[previousToken.kind] : 'none'}`);
console.log(` Next token: ${nextToken ? ts.SyntaxKind[nextToken.kind] : 'none'}`);
}
// Position-based analysis example
function findNodeAtCursor(sourceFile: ts.SourceFile, cursorPosition: number) {
const nodeAtPosition = getAstNodeAtPosition(sourceFile, cursorPosition);
if (nodeAtPosition) {
console.log(`Node at cursor: ${ts.SyntaxKind[nodeAtPosition.kind]}`);
console.log(` Text: ${nodeAtPosition.getText(sourceFile)}`);
console.log(` Range: ${nodeAtPosition.pos}-${nodeAtPosition.end}`);
}
}
// AST conversion example
function analyzeWithWrappedAst(sourceFile: ts.SourceFile) {
const { wrapped, flat } = convertAst(sourceFile);
console.log(`Total nodes: ${flat.length}`);
// Navigate using wrapped structure
let current = wrapped.next;
while (current) {
console.log(`Node: ${ts.SyntaxKind[current.kind]}`);
current = current.next;
}
}Install with Tessl CLI
npx tessl i tessl/npm-tsutils