CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ts-api-utils

Utility functions for working with TypeScript's API, providing comprehensive tools for analyzing and manipulating TypeScript AST nodes, types, and compiler APIs.

79

1.97x
Overview
Eval results
Files

node-analysis.mddocs/

Node Analysis

Node Analysis is the core capability of ts-api-utils, providing comprehensive utilities for working with TypeScript AST (Abstract Syntax Tree) nodes. This module offers type guards, access utilities, and helper functions that are essential for building robust TypeScript tooling, linters, and code analysis tools.

Overview

TypeScript's AST represents source code as a tree of nodes, where each node corresponds to a construct in the language (expressions, statements, declarations, etc.). The Node Analysis module provides three main categories of utilities:

  1. Type Guards: Functions that test whether a node is of a specific type
  2. Access Utilities: Functions for determining how expressions are accessed (read, write, delete)
  3. Node Utilities: Helper functions for working with nodes and their properties

Understanding AST nodes and their relationships is fundamental to TypeScript tooling development, as it allows tools to programmatically analyze and manipulate code structure.

Core Concepts

AST Nodes

Every construct in TypeScript code is represented as a node in the AST. Nodes have:

  • A kind property that identifies the node type
  • Child nodes that represent nested constructs
  • Properties specific to their node type
  • Parent relationships for traversing up the tree

Type Guards

Type guards are functions that narrow TypeScript's type system by checking if a node matches a specific pattern. They return node is SpecificNodeType to provide type safety:

declare const node: ts.Node;

if (isVariableDeclaration(node)) {
  // TypeScript now knows node is ts.VariableDeclaration
  console.log(node.name.getText());
}

Node Access Patterns

Expressions in TypeScript can be accessed in different ways:

  • Read: Getting the value (e.g., console.log(x))
  • Write: Setting the value (e.g., x = 5)
  • Delete: Removing the property (e.g., delete obj.prop)
  • ReadWrite: Both reading and writing (e.g., x++)

Node Type Guards

Type guards enable safe type narrowing for AST nodes. The library provides guards for single nodes, union types, and compound conditions.

Single Node Type Guards

These functions test for specific keyword and token types:

function isAbstractKeyword(node: ts.Node): node is ts.AbstractKeyword { .api }
function isAccessorKeyword(node: ts.Node): node is ts.AccessorKeyword { .api }
function isAnyKeyword(node: ts.Node): node is AnyKeyword { .api }
function isAssertKeyword(node: ts.Node): node is ts.AssertKeyword { .api }
function isAssertsKeyword(node: ts.Node): node is ts.AssertsKeyword { .api }
function isAsyncKeyword(node: ts.Node): node is ts.AsyncKeyword { .api }
function isAwaitKeyword(node: ts.Node): node is ts.AwaitKeyword { .api }
function isBigIntKeyword(node: ts.Node): node is BigIntKeyword { .api }
function isBooleanKeyword(node: ts.Node): node is BooleanKeyword { .api }
function isColonToken(node: ts.Node): node is ts.ColonToken { .api }
function isConstKeyword(node: ts.Node): node is ts.ConstKeyword { .api }
function isDeclareKeyword(node: ts.Node): node is ts.DeclareKeyword { .api }
function isDefaultKeyword(node: ts.Node): node is ts.DefaultKeyword { .api }
function isDotToken(node: ts.Node): node is ts.DotToken { .api }
function isEndOfFileToken(node: ts.Node): node is ts.EndOfFileToken { .api }
function isEqualsGreaterThanToken(node: ts.Node): node is ts.EqualsGreaterThanToken { .api }
function isEqualsToken(node: ts.Node): node is ts.EqualsToken { .api }
function isExclamationToken(node: ts.Node): node is ts.ExclamationToken { .api }
function isExportKeyword(node: ts.Node): node is ts.ExportKeyword { .api }
function isFalseKeyword(node: ts.Node): node is FalseKeyword { .api }
function isFalseLiteral(node: ts.Node): node is ts.FalseLiteral { .api }
function isImportExpression(node: ts.Node): node is ts.ImportExpression { .api }
function isImportKeyword(node: ts.Node): node is ImportKeyword { .api }
function isInKeyword(node: ts.Node): node is ts.InKeyword { .api }
function isJSDocText(node: ts.Node): node is ts.JSDocText { .api }
function isJsonMinusNumericLiteral(node: ts.Node): node is ts.JsonMinusNumericLiteral { .api }
function isNeverKeyword(node: ts.Node): node is NeverKeyword { .api }
function isNullKeyword(node: ts.Node): node is NullKeyword { .api }
function isNullLiteral(node: ts.Node): node is ts.NullLiteral { .api }
function isNumberKeyword(node: ts.Node): node is NumberKeyword { .api }
function isObjectKeyword(node: ts.Node): node is ObjectKeyword { .api }
function isOutKeyword(node: ts.Node): node is ts.OutKeyword { .api }
function isOverrideKeyword(node: ts.Node): node is ts.OverrideKeyword { .api }
function isPrivateKeyword(node: ts.Node): node is ts.PrivateKeyword { .api }
function isProtectedKeyword(node: ts.Node): node is ts.ProtectedKeyword { .api }
function isPublicKeyword(node: ts.Node): node is ts.PublicKeyword { .api }
function isQuestionDotToken(node: ts.Node): node is ts.QuestionDotToken { .api }
function isQuestionToken(node: ts.Node): node is ts.QuestionToken { .api }
function isReadonlyKeyword(node: ts.Node): node is ts.ReadonlyKeyword { .api }
function isStaticKeyword(node: ts.Node): node is ts.StaticKeyword { .api }
function isStringKeyword(node: ts.Node): node is StringKeyword { .api }
function isSuperExpression(node: ts.Node): node is ts.SuperExpression { .api }
function isSuperKeyword(node: ts.Node): node is SuperKeyword { .api }
function isSymbolKeyword(node: ts.Node): node is SymbolKeyword { .api }
function isSyntaxList(node: ts.Node): node is ts.SyntaxList { .api }
function isThisExpression(node: ts.Node): node is ts.ThisExpression { .api }
function isThisKeyword(node: ts.Node): node is ThisKeyword { .api }
function isTrueKeyword(node: ts.Node): node is TrueKeyword { .api }
function isTrueLiteral(node: ts.Node): node is ts.TrueLiteral { .api }
function isUndefinedKeyword(node: ts.Node): node is UndefinedKeyword { .api }
function isUnknownKeyword(node: ts.Node): node is UnknownKeyword { .api }
function isVoidKeyword(node: ts.Node): node is VoidKeyword { .api }

Keyword Node Types

The library defines specific types for TypeScript keywords:

type AnyKeyword = ts.KeywordToken<ts.SyntaxKind.AnyKeyword>
type BigIntKeyword = ts.KeywordToken<ts.SyntaxKind.BigIntKeyword>
type BooleanKeyword = ts.KeywordToken<ts.SyntaxKind.BooleanKeyword>
type FalseKeyword = ts.KeywordToken<ts.SyntaxKind.FalseKeyword>
type ImportKeyword = ts.KeywordToken<ts.SyntaxKind.ImportKeyword>
type NeverKeyword = ts.KeywordToken<ts.SyntaxKind.NeverKeyword>
type NullKeyword = ts.KeywordToken<ts.SyntaxKind.NullKeyword>
type NumberKeyword = ts.KeywordToken<ts.SyntaxKind.NumberKeyword>
type ObjectKeyword = ts.KeywordToken<ts.SyntaxKind.ObjectKeyword>
type StringKeyword = ts.KeywordToken<ts.SyntaxKind.StringKeyword>
type SuperKeyword = ts.KeywordToken<ts.SyntaxKind.SuperKeyword>
type SymbolKeyword = ts.KeywordToken<ts.SyntaxKind.SymbolKeyword>
type ThisKeyword = ts.KeywordToken<ts.SyntaxKind.ThisKeyword>
type TrueKeyword = ts.KeywordToken<ts.SyntaxKind.TrueKeyword>
type UndefinedKeyword = ts.KeywordToken<ts.SyntaxKind.UndefinedKeyword>
type UnknownKeyword = ts.KeywordToken<ts.SyntaxKind.UnknownKeyword>
type VoidKeyword = ts.KeywordToken<ts.SyntaxKind.VoidKeyword>

Union Type Guards

These functions test for nodes that match multiple possible types or have certain capabilities:

function hasDecorators(node: ts.Node): node is ts.HasDecorators { .api }
function hasExpressionInitializer(node: ts.Node): node is ts.HasExpressionInitializer { .api }
function hasInitializer(node: ts.Node): node is ts.HasInitializer { .api }
function hasJSDoc(node: ts.Node): node is ts.HasJSDoc { .api }
function hasModifiers(node: ts.Node): node is ts.HasModifiers { .api }
function hasType(node: ts.Node): node is ts.HasType { .api }
function hasTypeArguments(node: ts.Node): node is ts.HasTypeArguments { .api }
function isAccessExpression(node: ts.Node): node is ts.AccessExpression { .api }
function isAccessibilityModifier(node: ts.Node): node is ts.AccessibilityModifier { .api }
function isAccessorDeclaration(node: ts.Node): node is ts.AccessorDeclaration { .api }
function isArrayBindingElement(node: ts.Node): node is ts.ArrayBindingElement { .api }
function isArrayBindingOrAssignmentPattern(node: ts.Node): node is ts.ArrayBindingOrAssignmentPattern { .api }
function isAssignmentPattern(node: ts.Node): node is ts.AssignmentPattern { .api }
function isBindingOrAssignmentElementRestIndicator(node: ts.Node): node is ts.BindingOrAssignmentElementRestIndicator { .api }
function isBindingOrAssignmentElementTarget(node: ts.Node): node is ts.BindingOrAssignmentElementTarget { .api }
function isBindingOrAssignmentPattern(node: ts.Node): node is ts.BindingOrAssignmentPattern { .api }
function isBindingPattern(node: ts.Node): node is ts.BindingPattern { .api }
function isBlockLike(node: ts.Node): node is ts.BlockLike { .api }
function isBooleanLiteral(node: ts.Node): node is ts.BooleanLiteral { .api }
function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration { .api }
function isClassMemberModifier(node: ts.Node): node is ts.ClassMemberModifier { .api }
function isDeclarationName(node: ts.Node): node is ts.DeclarationName { .api }
function isDeclarationWithTypeParameterChildren(node: ts.Node): node is ts.DeclarationWithTypeParameterChildren { .api }
function isDeclarationWithTypeParameters(node: ts.Node): node is ts.DeclarationWithTypeParameters { .api }
function isDestructuringPattern(node: ts.Node): node is ts.DestructuringPattern { .api }
function isEntityNameExpression(node: ts.Node): node is ts.EntityNameExpression { .api }
function isEntityNameOrEntityNameExpression(node: ts.Node): node is ts.EntityNameOrEntityNameExpression { .api }
function isForInOrOfStatement(node: ts.Node): node is ts.ForInOrOfStatement { .api }
function isFunctionLikeDeclaration(node: ts.Node): node is ts.FunctionLikeDeclaration { .api }
function isJSDocComment(node: ts.Node): node is ts.JSDocComment { .api }
function isJSDocNamespaceBody(node: ts.Node): node is ts.JSDocNamespaceBody { .api }
function isJSDocTypeReferencingNode(node: ts.Node): node is ts.JSDocTypeReferencingNode { .api }
function isJsonObjectExpression(node: ts.Node): node is ts.JsonObjectExpression { .api }
function isJsxAttributeLike(node: ts.Node): node is ts.JsxAttributeLike { .api }
function isJsxAttributeValue(node: ts.Node): node is ts.JsxAttributeValue { .api }
function isJsxChild(node: ts.Node): node is ts.JsxChild { .api }
function isJsxTagNameExpression(node: ts.Node): node is ts.JsxTagNameExpression { .api }
function isLiteralToken(node: ts.Node): node is ts.LiteralToken { .api }
function isModuleBody(node: ts.Node): node is ts.ModuleBody { .api }
function isModuleName(node: ts.Node): node is ts.ModuleName { .api }
function isModuleReference(node: ts.Node): node is ts.ModuleReference { .api }
function isNamedImportBindings(node: ts.Node): node is ts.NamedImportBindings { .api }
function isNamedImportsOrExports(node: ts.Node): node is ts.NamedImportsOrExports { .api }
function isNamespaceBody(node: ts.Node): node is ts.NamespaceBody { .api }
function isObjectBindingOrAssignmentElement(node: ts.Node): node is ts.ObjectBindingOrAssignmentElement { .api }
function isObjectBindingOrAssignmentPattern(node: ts.Node): node is ts.ObjectBindingOrAssignmentPattern { .api }
function isObjectTypeDeclaration(node: ts.Node): node is ts.ObjectTypeDeclaration { .api }
function isParameterPropertyModifier(node: ts.Node): node is ts.ParameterPropertyModifier { .api }
function isPropertyNameLiteral(node: ts.Node): node is ts.PropertyNameLiteral { .api }
function isPseudoLiteralToken(node: ts.Node): node is ts.PseudoLiteralToken { .api }
function isSignatureDeclaration(node: ts.Node): node is ts.SignatureDeclaration { .api }
function isSuperProperty(node: ts.Node): node is ts.SuperProperty { .api }
function isTypeOnlyCompatibleAliasDeclaration(node: ts.Node): node is ts.TypeOnlyCompatibleAliasDeclaration { .api }
function isTypeReferenceType(node: ts.Node): node is ts.TypeReferenceType { .api }
function isUnionOrIntersectionTypeNode(node: ts.Node): node is ts.UnionOrIntersectionTypeNode { .api }
function isVariableLikeDeclaration(node: ts.Node): node is ts.VariableLikeDeclaration { .api }

Usage Examples

import { isAccessExpression, hasModifiers, isClassLikeDeclaration } from 'ts-api-utils';

// Check if a node is any kind of access expression
if (isAccessExpression(node)) {
  // node is either PropertyAccessExpression or ElementAccessExpression
  console.log('Accessing:', node.expression.getText());
}

// Check if a declaration has modifiers
if (hasModifiers(declaration)) {
  // declaration has a modifiers array
  const modifiers = declaration.modifiers?.map(mod => mod.getText()) ?? [];
  console.log('Modifiers:', modifiers);
}

// Check if it's a class-like declaration
if (isClassLikeDeclaration(node)) {
  // node is ClassDeclaration or ClassExpression
  console.log('Class name:', node.name?.getText());
}

Compound Type Guards

These functions test for more complex node patterns and combinations:

function isConstAssertionExpression(node: ts.AssertionExpression): node is ConstAssertionExpression { .api }
function isIterationStatement(node: ts.Node): node is ts.IterationStatement { .api }
function isJSDocNamespaceDeclaration(node: ts.Node): node is ts.JSDocNamespaceDeclaration { .api }
function isJsxTagNamePropertyAccess(node: ts.Node): node is ts.JsxTagNamePropertyAccess { .api }
function isNamedDeclarationWithName(node: ts.Declaration): node is NamedDeclarationWithName { .api }
function isNamespaceDeclaration(node: ts.Node): node is ts.NamespaceDeclaration { .api }
function isNumericOrStringLikeLiteral(node: ts.Node): node is NumericOrStringLikeLiteral { .api }
function isPropertyAccessEntityNameExpression(node: ts.Node): node is ts.PropertyAccessEntityNameExpression { .api }
function isSuperElementAccessExpression(node: ts.Node): node is ts.SuperElementAccessExpression { .api }
function isSuperPropertyAccessExpression(node: ts.Node): node is ts.SuperPropertyAccessExpression { .api }

Compound Node Types

The library defines several compound node types for complex patterns:

interface ConstAssertionExpression extends ts.AssertionExpression {
  type: ts.TypeReferenceNode;
  typeName: ConstAssertionIdentifier;
}

interface ConstAssertionIdentifier extends ts.Identifier {
  escapedText: "const" & ts.__String;
}

interface NamedDeclarationWithName extends ts.NamedDeclaration {
  name: ts.DeclarationName;
}

type NumericOrStringLikeLiteral = ts.NumericLiteral | ts.StringLiteralLike

Usage Examples

import { 
  isConstAssertionExpression, 
  isIterationStatement, 
  isNamedDeclarationWithName 
} from 'ts-api-utils';

// Check for const assertions like "as const"
if (ts.isAssertionExpression(node) && isConstAssertionExpression(node)) {
  console.log('Found const assertion');
}

// Check for any kind of loop
if (isIterationStatement(node)) {
  // node is do, for, for-in, for-of, or while statement
  console.log('Found loop statement');
}

// Safely access names on declarations
if (isNamedDeclarationWithName(declaration)) {
  // declaration.name is guaranteed to exist
  console.log('Declaration name:', declaration.name.getText());
}

Node Access Utilities

The access utilities help determine how expressions are being used in their context - whether they're being read from, written to, or deleted.

AccessKind Enum

enum AccessKind {
  None = 0,
  Read = 1,
  Write = 2,
  Delete = 4,
  ReadWrite = 3  // Read | Write
}

Access Detection

function getAccessKind(node: ts.Expression): AccessKind;

This function analyzes an expression's context to determine what operations are being performed on it.

Additional Node Utilities

function isBindableObjectDefinePropertyCall(node: ts.CallExpression): boolean;
function isInConstContext(
  node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment, 
  typeChecker: ts.TypeChecker
): boolean;

The isBindableObjectDefinePropertyCall function determines if a call expression represents a statically analyzable Object.defineProperty call. The isInConstContext function detects if property assignments are affected by const assertion contexts.

Usage Examples

import { getAccessKind, AccessKind } from 'ts-api-utils';

declare const expression: ts.Expression;

const access = getAccessKind(expression);

if (access & AccessKind.Read) {
  console.log('Expression is being read');
}

if (access & AccessKind.Write) {
  console.log('Expression is being written to');
}

if (access & AccessKind.Delete) {
  console.log('Expression is being deleted');
}

// Check for specific access patterns
switch (access) {
  case AccessKind.Read:
    console.log('Read-only access: const x = expression');
    break;
  case AccessKind.Write:
    console.log('Write-only access: expression = value');
    break;
  case AccessKind.ReadWrite:
    console.log('Read-write access: expression++');
    break;
  case AccessKind.Delete:
    console.log('Delete access: delete expression');
    break;
  case AccessKind.None:
    console.log('No access (e.g., in type position)');
    break;
}

Access Context Examples

Different contexts produce different access kinds:

// Read access
console.log(variable);          // AccessKind.Read
const x = obj.property;         // AccessKind.Read (obj.property)
if (condition) { }              // AccessKind.Read (condition)

// Write access  
variable = 5;                   // AccessKind.Write
obj.property = value;           // AccessKind.Write (obj.property)
const [a, b] = array;          // AccessKind.Write (a, b in destructuring)

// Read-write access
variable++;                     // AccessKind.ReadWrite
++variable;                     // AccessKind.ReadWrite
variable += 10;                 // AccessKind.ReadWrite

// Delete access
delete obj.property;            // AccessKind.Delete (obj.property)

// No access
type T = typeof variable;       // AccessKind.None (in type position)

Node Flag Utilities

Flag utilities test for various TypeScript compiler flags set on nodes, types, and symbols.

function isModifierFlagSet(node: ts.Declaration, flag: ts.ModifierFlags): boolean { .api }
function isNodeFlagSet(node: ts.Node, flag: ts.NodeFlags): boolean { .api }
function isObjectFlagSet(objectType: ts.ObjectType, flag: ts.ObjectFlags): boolean { .api }
function isSymbolFlagSet(symbol: ts.Symbol, flag: ts.SymbolFlags): boolean { .api }
function isTypeFlagSet(type: ts.Type, flag: ts.TypeFlags): boolean { .api }

Usage Examples

import { 
  isModifierFlagSet, 
  isNodeFlagSet, 
  isSymbolFlagSet,
  isTypeFlagSet 
} from 'ts-api-utils';
import ts from 'typescript';

// Check for abstract modifier
if (isModifierFlagSet(declaration, ts.ModifierFlags.Abstract)) {
  console.log('Declaration is abstract');
}

// Check for await context
if (isNodeFlagSet(node, ts.NodeFlags.AwaitContext)) {
  console.log('Node is in await context');
}

// Check symbol properties
if (isSymbolFlagSet(symbol, ts.SymbolFlags.Function)) {
  console.log('Symbol represents a function');
}

// Check type properties
if (isTypeFlagSet(type, ts.TypeFlags.Union)) {
  console.log('Type is a union type');
}

Node Utilities

Additional helper functions for working with nodes in specific contexts.

Internal Utilities

These utilities are used internally by the library but may be useful for advanced use cases:

function isBindableObjectDefinePropertyCall(node: ts.CallExpression): boolean { .api }
function isInConstContext(
  node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment, 
  typeChecker: ts.TypeChecker
): boolean { .api }

Usage Examples

import { isBindableObjectDefinePropertyCall, isInConstContext } from 'ts-api-utils';

// Check for statically analyzable Object.defineProperty calls
if (ts.isCallExpression(node) && isBindableObjectDefinePropertyCall(node)) {
  console.log('Found Object.defineProperty call that can be analyzed');
}

// Check if property is in a const context
if (isInConstContext(propertyAssignment, typeChecker)) {
  console.log('Property assignment is in const context (literal type)');
}

Integration with Other Modules

Node Analysis utilities work seamlessly with other ts-api-utils modules:

With Type Analysis

import { isVariableDeclaration, isSignatureDeclaration } from 'ts-api-utils';
import { getCallSignaturesOfType } from 'ts-api-utils';

if (isVariableDeclaration(node) && node.type) {
  const type = typeChecker.getTypeFromTypeNode(node.type);
  const signatures = getCallSignaturesOfType(type);
  
  if (signatures.length > 0) {
    console.log('Variable has callable type');
  }
}

With Usage Analysis

import { isVariableDeclaration, collectVariableUsage } from 'ts-api-utils';

const usage = collectVariableUsage(sourceFile);

for (const [identifier, info] of usage) {
  if (info.declarations.some(decl => 
    isVariableDeclaration(decl.parent) && 
    hasModifiers(decl.parent.parent)
  )) {
    console.log('Variable declared with modifiers:', identifier.text);
  }
}

Best Practices

Performance Considerations

  1. Use specific type guards: Prefer specific guards like isPropertyAccessExpression over generic checks
  2. Cache results: Type guard results don't change for a given node, so cache expensive checks
  3. Traverse efficiently: Use parent/child relationships rather than re-searching the AST

Type Safety

  1. Always use type guards: Never cast nodes directly - use type guards for safe narrowing
  2. Check parent context: Many node types are only meaningful in specific parent contexts
  3. Handle undefined cases: Many node properties are optional - always check for existence

Common Patterns

// Safe property access pattern
function getPropertyName(node: ts.Node): string | undefined {
  if (ts.isPropertyAccessExpression(node)) {
    return node.name.text;
  }
  if (ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression)) {
    return node.argumentExpression.text;
  }
  return undefined;
}

// Safe modifier checking pattern
function hasPublicModifier(node: ts.Node): boolean {
  return hasModifiers(node) && 
         node.modifiers?.some(isPublicKeyword) === true;
}

// Access pattern analysis
function analyzeExpression(expr: ts.Expression): void {
  const access = getAccessKind(expr);
  
  if (access & AccessKind.Write) {
    // Handle write operations
    if (access & AccessKind.Read) {
      console.log('Read-write operation (e.g., +=, ++)');
    } else {
      console.log('Pure write operation (e.g., assignment)');
    }
  } else if (access & AccessKind.Read) {
    console.log('Read-only access');
  } else if (access & AccessKind.Delete) {
    console.log('Delete operation');
  }
}

The Node Analysis module provides the foundation for all TypeScript AST manipulation and analysis. By combining type guards, access utilities, and flag checking, you can build sophisticated tools that understand and manipulate TypeScript code with confidence and type safety.

Install with Tessl CLI

npx tessl i tessl/npm-ts-api-utils

docs

compiler-options.md

index.md

node-analysis.md

syntax-utilities.md

type-system.md

usage-analysis.md

tile.json