Open-Source Wikis

/

TypeScript

/

Primitives

/

Node

microsoft/TypeScript

Node

The fundamental unit of the AST. Every parsed token, expression, statement, and declaration is a Node tagged with a SyntaxKind.

Definition

The base interface lives in src/compiler/types.ts:

export interface Node extends ReadonlyTextRange {
  readonly kind: SyntaxKind;
  readonly flags: NodeFlags;
  /* @internal */ modifierFlagsCache: ModifierFlags;
  /* @internal */ readonly transformFlags: TransformFlags;
  readonly decorators?: NodeArray<Decorator>;
  readonly modifiers?: ModifiersArray;
  /* @internal */ original?: Node;
  /* @internal */ symbol: Symbol;
  /* @internal */ locals?: SymbolTable;
  /* @internal */ nextContainer?: Node;
  /* @internal */ localSymbol?: Symbol;
  /* @internal */ flowNode?: FlowNode;
  /* @internal */ emitNode?: EmitNode;
  /* @internal */ contextualType?: Type;
  /* @internal */ inferenceContext?: InferenceContext;
  parent: Node;
}

SyntaxKind (also in types.ts) is a single enum that covers tokens, keywords, punctuation, and node kinds — with marker values like FirstToken, LastKeyword, FirstStatement to identify ranges.

Subtypes

Node is the base of a vast type hierarchy:

graph TD
    Node --> Token
    Node --> Declaration
    Node --> Statement
    Node --> Expression
    Node --> TypeNode
    Declaration --> NamedDeclaration
    NamedDeclaration --> VariableDeclaration
    NamedDeclaration --> ClassDeclaration
    NamedDeclaration --> FunctionDeclaration
    Statement --> Block
    Statement --> IfStatement
    Statement --> ForStatement
    Expression --> CallExpression
    Expression --> BinaryExpression
    Expression --> Identifier
    TypeNode --> TypeReferenceNode
    TypeNode --> UnionTypeNode
    TypeNode --> MappedTypeNode

Every concrete node interface (e.g., IfStatement) lists its children as readonly properties. Nodes are created exclusively via src/compiler/factory/nodeFactory.ts.

Predicates

src/compiler/factory/nodeTests.ts ships type-narrowing predicates for every kind: isVariableDeclaration(n), isCallExpression(n), isClassDeclaration(n), etc. These are the canonical way to check a node's kind because they double as TypeScript type guards.

Position information

Node extends ReadonlyTextRange:

  • pos — start of leading trivia (whitespace, comments) for this node.
  • end — end of node text.
  • getStart(sourceFile, includeJsDoc?) — start of the actual token text (skipping trivia).

The leading-vs-token distinction matters because tools that show "go to definition" or insert text before a node need to honour comments and whitespace correctly.

Synthetic nodes (those created during a transform, not parsed from source) have pos === end === -1. The emitter knows to skip source-map entries for them.

parent pointers

Every node has a parent pointer, set up by the parser via setParentRecursive. Walks like find-references rely on it heavily. When a transformer creates a new tree, it must call setParentRecursive (or use factory methods that handle it automatically) before the result is consumed by the checker.

flags and modifierFlags

NodeFlags is a packed bit-set containing things like:

  • Let, Const, Using, AwaitUsing — for variable declarations.
  • OptionalChain, ExportContext, ContainsThis.
  • Ambient — declared in an ambient context.
  • JSDoc, Synthesized — origin markers.
  • ThisNodeHasError, HasAggregatedChildData — internal cache markers.

ModifierFlags (computed from the modifiers list) covers public/private/protected/readonly/static/abstract/async/override/etc.

Walking the tree

Two primitives:

  • forEachChild(node, cb, cbNodes?) — read-only walk; calls cb for each child.
  • visitEachChild(node, visitor, context) — transforming walk; replaces children that the visitor changes.

These are defined in src/compiler/parser.ts and src/compiler/visitorPublic.ts respectively. Both have many corner cases (decorators, type parameters, JSDoc) baked in — never hand-roll a walker.

Memory and identity

Nodes are not flyweighted. Two parsed instances of 42 are two distinct NumericLiteral nodes. However, identity is preserved across update* factory calls when no children change — factory.updateBinaryExpression(node, ...) returns the original node if the new children are identical.

This is what makes incremental parses cheap: a transformer that walks the tree but doesn't change anything returns the original tree by reference.

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Node – TypeScript wiki | Factory