diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1712783850..21aabe935b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,6 +23,7 @@ jobs:
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: build
@@ -36,6 +37,7 @@ jobs:
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
 
       - run: npm ci
 
@@ -90,6 +92,7 @@ jobs:
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
         with:
           node-version: 'lts/*'
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: test
@@ -103,7 +106,16 @@ jobs:
 
       - name: Tests
         id: test
-        run: npx hereby test:all
+        run: npx hereby test
+
+      - name: Benchmarks
+        run: npx hereby test:benchmarks
+
+      - name: Tools Tests
+        run: npx hereby test:tools
+
+      - name: API Tests
+        run: npx hereby test:api
 
       - run: git add .
 
@@ -150,6 +162,7 @@ jobs:
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: lint${{ (matrix.noembed && '-noembed') || ''}}
@@ -163,6 +176,7 @@ jobs:
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: format
@@ -179,6 +193,7 @@ jobs:
         with:
           submodules: true
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: generate
@@ -211,6 +226,7 @@ jobs:
         with:
           submodules: true
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: smoke
@@ -242,6 +258,7 @@ jobs:
         with:
           submodules: true
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: baselines
diff --git a/.github/workflows/merge-queue.yml b/.github/workflows/merge-queue.yml
index 22b64b7e4c..0e83867847 100644
--- a/.github/workflows/merge-queue.yml
+++ b/.github/workflows/merge-queue.yml
@@ -22,6 +22,7 @@ jobs:
       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
         with:
           node-version: 'lts/*'
+      - uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
       - uses: ./.github/actions/setup-go
         with:
           cache-name: merge-queue-test
diff --git a/Herebyfile.mjs b/Herebyfile.mjs
index 592247f3ab..1be19e0c07 100644
--- a/Herebyfile.mjs
+++ b/Herebyfile.mjs
@@ -314,18 +314,37 @@ async function runTestTools() {
     await $test({ cwd: path.join(__dirname, "_tools") })`${gotestsum("tools")} ./...`;
 }
 
+async function runTestAPI() {
+    await $`npm run -w @typescript/api test`;
+}
+
 export const testTools = task({
     name: "test:tools",
     run: runTestTools,
 });
 
+export const buildAPITests = task({
+    name: "build:api:test",
+    run: async () => {
+        await $`npm run -w @typescript/api build:test`;
+    },
+});
+
+export const testAPI = task({
+    name: "test:api",
+    dependencies: [tsgo, buildAPITests],
+    run: runTestAPI,
+});
+
 export const testAll = task({
     name: "test:all",
+    dependencies: [tsgo, buildAPITests],
     run: async () => {
         // Prevent interleaving by running these directly instead of in parallel.
         await runTests();
         await runTestBenchmarks();
         await runTestTools();
+        await runTestAPI();
     },
 });
 
diff --git a/README.md b/README.md
index adf4ca78fa..5b7f75b8af 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Interested developers can clone and run locally to try out things as they become
 
 ## How to Build and Run
 
-This repo uses [Go 1.24 or higher](https://go.dev/dl/), [Node.js with npm](https://nodejs.org/), and [`hereby`](https://www.npmjs.com/package/hereby).
+This repo uses [Go 1.24 or higher](https://go.dev/dl/), [Rust 1.85 or higher](https://www.rust-lang.org/tools/install), [Node.js with npm](https://nodejs.org/), and [`hereby`](https://www.npmjs.com/package/hereby).
 
 For tests and code generation, this repo contains a git submodule to the main TypeScript repo pointing to the commit being ported.
 When cloning, you'll want to clone with submodules:
diff --git a/_packages/api/package.json b/_packages/api/package.json
new file mode 100644
index 0000000000..1b8e2f2c80
--- /dev/null
+++ b/_packages/api/package.json
@@ -0,0 +1,51 @@
+{
+    "private": true,
+    "name": "@typescript/api",
+    "version": "1.0.0",
+    "type": "module",
+    "imports": {
+        "#symbolFlags": {
+            "@typescript/source": {
+                "types": "./src/symbolFlags.enum.ts",
+                "default": "./src/symbolFlags.ts"
+            },
+            "types": "./dist/symbolFlags.enum.d.ts",
+            "default": "./dist/symbolFlags.js"
+        },
+        "#typeFlags": {
+            "@typescript/source": {
+                "types": "./src/typeFlags.enum.ts",
+                "default": "./src/typeFlags.ts"
+            },
+            "types": "./dist/typeFlags.enum.d.ts",
+            "default": "./dist/typeFlags.js"
+        }
+    },
+    "exports": {
+        ".": {
+            "@typescript/source": "./src/api.ts",
+            "default": "./dist/api.js"
+        },
+        "./fs": {
+            "@typescript/source": "./src/fs.ts",
+            "default": "./dist/fs.js"
+        },
+        "./proto": {
+            "@typescript/source": "./src/proto.ts",
+            "default": "./dist/proto.js"
+        }
+    },
+    "scripts": {
+        "build": "tsc -b",
+        "build:test": "tsc -b test",
+        "bench": "node --experimental-strip-types --no-warnings --conditions @typescript/source test/api.bench.ts",
+        "test": "node --test --experimental-strip-types --no-warnings --conditions @typescript/source ./test/**/*.test.ts"
+    },
+    "devDependencies": {
+        "tinybench": "^3.1.1"
+    },
+    "dependencies": {
+        "@typescript/ast": "1.0.0",
+        "libsyncrpc": "github:microsoft/libsyncrpc#bb02d84"
+    }
+}
diff --git a/_packages/api/src/api.ts b/_packages/api/src/api.ts
new file mode 100644
index 0000000000..fbfc323917
--- /dev/null
+++ b/_packages/api/src/api.ts
@@ -0,0 +1,179 @@
+/// <reference path="./node.ts" preserve="true" />
+import { SymbolFlags } from "#symbolFlags";
+import { TypeFlags } from "#typeFlags";
+import type {
+    Node,
+    SourceFile,
+} from "@typescript/ast";
+import { Client } from "./client.ts";
+import type { FileSystem } from "./fs.ts";
+import { RemoteSourceFile } from "./node.ts";
+import { ObjectRegistry } from "./objectRegistry.ts";
+import type {
+    ConfigResponse,
+    ProjectResponse,
+    SymbolResponse,
+    TypeResponse,
+} from "./proto.ts";
+
+export { SymbolFlags, TypeFlags };
+
+export interface APIOptions {
+    tsserverPath: string;
+    cwd?: string;
+    logFile?: string;
+    fs?: FileSystem;
+}
+
+export class API {
+    private client: Client;
+    private objectRegistry: ObjectRegistry;
+    constructor(options: APIOptions) {
+        this.client = new Client(options);
+        this.objectRegistry = new ObjectRegistry(this.client);
+    }
+
+    parseConfigFile(fileName: string): ConfigResponse {
+        return this.client.request("parseConfigFile", { fileName });
+    }
+
+    loadProject(configFileName: string): Project {
+        const data = this.client.request("loadProject", { configFileName });
+        return this.objectRegistry.getProject(data);
+    }
+
+    echo(message: string): string {
+        return this.client.echo(message);
+    }
+
+    echoBinary(message: Uint8Array): Uint8Array {
+        return this.client.echoBinary(message);
+    }
+
+    close(): void {
+        this.client.close();
+    }
+}
+
+export class DisposableObject {
+    private disposed: boolean = false;
+    protected objectRegistry: ObjectRegistry;
+    constructor(objectRegistry: ObjectRegistry) {
+        this.objectRegistry = objectRegistry;
+    }
+    [globalThis.Symbol.dispose](): void {
+        this.objectRegistry.release(this);
+        this.disposed = true;
+    }
+    dispose(): void {
+        this[globalThis.Symbol.dispose]();
+    }
+    isDisposed(): boolean {
+        return this.disposed;
+    }
+    ensureNotDisposed(): this {
+        if (this.disposed) {
+            throw new Error(`${this.constructor.name} is disposed`);
+        }
+        return this;
+    }
+}
+
+export class Project extends DisposableObject {
+    private decoder = new TextDecoder();
+    private client: Client;
+
+    id: string;
+    configFileName!: string;
+    compilerOptions!: Record<string, unknown>;
+    rootFiles!: readonly string[];
+
+    constructor(client: Client, objectRegistry: ObjectRegistry, data: ProjectResponse) {
+        super(objectRegistry);
+        this.id = data.id;
+        this.client = client;
+        this.loadData(data);
+    }
+
+    loadData(data: ProjectResponse): void {
+        this.configFileName = data.configFileName;
+        this.compilerOptions = data.compilerOptions;
+        this.rootFiles = data.rootFiles;
+    }
+
+    reload(): void {
+        this.ensureNotDisposed();
+        this.loadData(this.client.request("loadProject", { configFileName: this.configFileName }));
+    }
+
+    getSourceFile(fileName: string): SourceFile | undefined {
+        this.ensureNotDisposed();
+        const data = this.client.requestBinary("getSourceFile", { project: this.id, fileName });
+        return data ? new RemoteSourceFile(data, this.decoder) as unknown as SourceFile : undefined;
+    }
+
+    getSymbolAtLocation(node: Node): Symbol | undefined;
+    getSymbolAtLocation(nodes: readonly Node[]): (Symbol | undefined)[];
+    getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Symbol | (Symbol | undefined)[] | undefined {
+        this.ensureNotDisposed();
+        if (Array.isArray(nodeOrNodes)) {
+            const data = this.client.request("getSymbolsAtLocations", { project: this.id, locations: nodeOrNodes.map(node => node.id) });
+            return data.map((d: SymbolResponse | null) => d ? this.objectRegistry.getSymbol(d) : undefined);
+        }
+        const data = this.client.request("getSymbolAtLocation", { project: this.id, location: (nodeOrNodes as Node).id });
+        return data ? this.objectRegistry.getSymbol(data) : undefined;
+    }
+
+    getSymbolAtPosition(fileName: string, position: number): Symbol | undefined;
+    getSymbolAtPosition(fileName: string, positions: readonly number[]): (Symbol | undefined)[];
+    getSymbolAtPosition(fileName: string, positionOrPositions: number | readonly number[]): Symbol | (Symbol | undefined)[] | undefined {
+        this.ensureNotDisposed();
+        if (typeof positionOrPositions === "number") {
+            const data = this.client.request("getSymbolAtPosition", { project: this.id, fileName, position: positionOrPositions });
+            return data ? this.objectRegistry.getSymbol(data) : undefined;
+        }
+        const data = this.client.request("getSymbolsAtPositions", { project: this.id, fileName, positions: positionOrPositions });
+        return data.map((d: SymbolResponse | null) => d ? this.objectRegistry.getSymbol(d) : undefined);
+    }
+
+    getTypeOfSymbol(symbol: Symbol): Type | undefined;
+    getTypeOfSymbol(symbols: readonly Symbol[]): (Type | undefined)[];
+    getTypeOfSymbol(symbolOrSymbols: Symbol | readonly Symbol[]): Type | (Type | undefined)[] | undefined {
+        this.ensureNotDisposed();
+        if (Array.isArray(symbolOrSymbols)) {
+            const data = this.client.request("getTypesOfSymbols", { project: this.id, symbols: symbolOrSymbols.map(symbol => symbol.ensureNotDisposed().id) });
+            return data.map((d: TypeResponse | null) => d ? this.objectRegistry.getType(d) : undefined);
+        }
+        const data = this.client.request("getTypeOfSymbol", { project: this.id, symbol: (symbolOrSymbols as Symbol).ensureNotDisposed().id });
+        return data ? this.objectRegistry.getType(data) : undefined;
+    }
+}
+
+export class Symbol extends DisposableObject {
+    private client: Client;
+    id: string;
+    name: string;
+    flags: SymbolFlags;
+    checkFlags: number;
+
+    constructor(client: Client, objectRegistry: ObjectRegistry, data: SymbolResponse) {
+        super(objectRegistry);
+        this.client = client;
+        this.id = data.id;
+        this.name = data.name;
+        this.flags = data.flags;
+        this.checkFlags = data.checkFlags;
+    }
+}
+
+export class Type extends DisposableObject {
+    private client: Client;
+    id: string;
+    flags: TypeFlags;
+    constructor(client: Client, objectRegistry: ObjectRegistry, data: TypeResponse) {
+        super(objectRegistry);
+        this.client = client;
+        this.id = data.id;
+        this.flags = data.flags;
+    }
+}
diff --git a/_packages/api/src/client.ts b/_packages/api/src/client.ts
new file mode 100644
index 0000000000..c99d0945f4
--- /dev/null
+++ b/_packages/api/src/client.ts
@@ -0,0 +1,65 @@
+import { SyncRpcChannel } from "libsyncrpc";
+import type { FileSystem } from "./fs.ts";
+
+export interface ClientOptions {
+    tsserverPath: string;
+    cwd?: string;
+    logFile?: string;
+    fs?: FileSystem;
+}
+
+export class Client {
+    private channel: SyncRpcChannel;
+    private decoder = new TextDecoder();
+    private encoder = new TextEncoder();
+
+    constructor(options: ClientOptions) {
+        this.channel = new SyncRpcChannel(options.tsserverPath, [
+            "api",
+            "-cwd",
+            options.cwd ?? process.cwd(),
+        ]);
+
+        this.channel.requestSync(
+            "configure",
+            JSON.stringify({
+                logFile: options.logFile,
+                callbacks: Object.keys(options.fs ?? {}),
+            }),
+        );
+
+        if (options.fs) {
+            for (const [key, callback] of Object.entries(options.fs)) {
+                this.channel.registerCallback(key, (_, arg) => {
+                    const result = callback(JSON.parse(arg));
+                    return JSON.stringify(result) ?? "";
+                });
+            }
+        }
+    }
+
+    request(method: string, payload: any): any {
+        const encodedPayload = JSON.stringify(payload);
+        const result = this.channel.requestSync(method, encodedPayload);
+        if (result.length) {
+            const decodedResult = JSON.parse(result);
+            return decodedResult;
+        }
+    }
+
+    requestBinary(method: string, payload: any): Uint8Array {
+        return this.channel.requestBinarySync(method, this.encoder.encode(JSON.stringify(payload)));
+    }
+
+    echo(payload: string): string {
+        return this.channel.requestSync("echo", payload);
+    }
+
+    echoBinary(payload: Uint8Array): Uint8Array {
+        return this.channel.requestBinarySync("echo", payload);
+    }
+
+    close(): void {
+        this.channel.close();
+    }
+}
diff --git a/_packages/api/src/fs.ts b/_packages/api/src/fs.ts
new file mode 100644
index 0000000000..f84d35da82
--- /dev/null
+++ b/_packages/api/src/fs.ts
@@ -0,0 +1,142 @@
+import { getPathComponents } from "./path.ts";
+
+export interface FileSystemEntries {
+    files: string[];
+    directories: string[];
+}
+
+export interface FileSystem {
+    directoryExists?: (directoryName: string) => boolean | undefined;
+    fileExists?: (fileName: string) => boolean | undefined;
+    getAccessibleEntries?: (directoryName: string) => FileSystemEntries | undefined;
+    readFile?: (fileName: string) => string | null | undefined;
+    realpath?: (path: string) => string | undefined;
+}
+
+export function createVirtualFileSystem(files: Record<string, string>): FileSystem {
+    interface VDirectory {
+        type: "directory";
+        children: Record<string, VNode>;
+    }
+
+    interface VFile {
+        type: "file";
+        content: string;
+    }
+
+    type VNode = VDirectory | VFile;
+
+    const root: VDirectory = {
+        type: "directory",
+        children: {},
+    };
+
+    for (const [filePath, fileContent] of Object.entries(files)) {
+        createFile(filePath, fileContent);
+    }
+
+    return {
+        directoryExists,
+        fileExists,
+        getAccessibleEntries,
+        readFile,
+        realpath: path => path,
+    };
+
+    /**
+     * Traverse the tree from the root according to path segments.
+     * Returns the node if found, or null if any segment doesn't exist.
+     */
+    function getNodeFromPath(path: string): VNode | undefined {
+        if (!path || path === "/") {
+            return root;
+        }
+        const segments = getPathComponents(path).slice(1);
+        let current: VNode = root;
+
+        for (const segment of segments) {
+            if (current.type !== "directory") {
+                return undefined;
+            }
+            const child: VNode = current.children[segment];
+            if (!child) {
+                return undefined; // segment not found
+            }
+            current = child;
+        }
+
+        return current;
+    }
+
+    /**
+     * Ensure that the directory path (given by `segments`) exists,
+     * creating subdirectories as needed. Returns the final directory node.
+     */
+    function ensureDirectory(segments: string[]): VDirectory {
+        let current: VDirectory = root;
+        for (const segment of segments) {
+            if (!current.children[segment]) {
+                // Create a new directory node
+                current.children[segment] = { type: "directory", children: {} };
+            }
+            else if (current.children[segment].type !== "directory") {
+                // A file with the same name already exists
+                throw new Error(`Cannot create directory: a file already exists at "/${segments.join("/")}"`);
+            }
+            current = current.children[segment] as VDirectory;
+        }
+        return current;
+    }
+
+    /**
+     * Create (or overwrite) a file at the given path with provided content.
+     * Automatically creates parent directories if needed.
+     */
+    function createFile(path: string, content: string) {
+        const segments = getPathComponents(path).slice(1);
+        if (segments.length === 0) {
+            throw new Error(`Invalid file path: "${path}"`);
+        }
+        const filename = segments.pop()!;
+        const directorySegments = segments;
+        const dirNode = ensureDirectory(directorySegments);
+        dirNode.children[filename] = { type: "file", content };
+    }
+
+    function directoryExists(directoryName: string): boolean {
+        const node = getNodeFromPath(directoryName);
+        return !!node && node.type === "directory";
+    }
+
+    function fileExists(fileName: string): boolean {
+        const node = getNodeFromPath(fileName);
+        return !!node && node.type === "file";
+    }
+
+    function getAccessibleEntries(directoryName: string): FileSystemEntries | undefined {
+        const node = getNodeFromPath(directoryName);
+        if (!node || node.type !== "directory") {
+            // Not found or not a directory
+            return undefined;
+        }
+        const files: string[] = [];
+        const directories: string[] = [];
+        for (const [name, child] of Object.entries(node.children)) {
+            if (child.type === "file") {
+                files.push(name);
+            }
+            else {
+                directories.push(name);
+            }
+        }
+        return { files, directories };
+    }
+
+    function readFile(fileName: string): string | undefined {
+        const node = getNodeFromPath(fileName);
+        if (!node || node.type !== "file") {
+            return undefined; // doesn't exist or is not a file
+        }
+        return node.content;
+    }
+}
diff --git a/_packages/api/src/node.ts b/_packages/api/src/node.ts
new file mode 100644
index 0000000000..d906b3310b
--- /dev/null
+++ b/_packages/api/src/node.ts
@@ -0,0 +1,947 @@
+import {
+    type Node,
+    type NodeArray,
+    type SourceFile,
+    SyntaxKind,
+} from "@typescript/ast";
+
+declare module "@typescript/ast" {
+    export interface Node {
+        readonly id: string;
+        forEachChild<T>(visitor: (node: Node) => T): T | undefined;
+        getSourceFile(): SourceFile;
+    }
+
+    export interface NodeArray<T> {
+        at(index: number): T;
+    }
+}
+
+const popcount8 = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8];
+
+const childProperties: Readonly<Partial<Record<SyntaxKind, readonly string[]>>> = {
+    [SyntaxKind.QualifiedName]: ["left", "right"],
+    [SyntaxKind.TypeParameter]: ["modifiers", "name", "constraint", "defaultType"],
+    [SyntaxKind.IfStatement]: ["expression", "thenStatement", "elseStatement"],
+    [SyntaxKind.DoStatement]: ["statement", "expression"],
+    [SyntaxKind.WhileStatement]: ["expression", "statement"],
+    [SyntaxKind.ForStatement]: ["initializer", "condition", "incrementor", "statement"],
+    [SyntaxKind.ForInStatement]: ["awaitModifier", "initializer", "expression", "statement"],
+    [SyntaxKind.ForOfStatement]: ["awaitModifier", "initializer", "expression", "statement"],
+    [SyntaxKind.WithStatement]: ["expression", "statement"],
+    [SyntaxKind.SwitchStatement]: ["expression", "caseBlock"],
+    [SyntaxKind.CaseClause]: ["expression", "statements"],
+    [SyntaxKind.DefaultClause]: ["expression", "statements"],
+    [SyntaxKind.TryStatement]: ["tryBlock", "catchClause", "finallyBlock"],
+    [SyntaxKind.CatchClause]: ["variableDeclaration", "block"],
+    [SyntaxKind.LabeledStatement]: ["label", "statement"],
+    [SyntaxKind.VariableStatement]: ["modifiers", "declarationList"],
+    [SyntaxKind.VariableDeclaration]: ["name", "exclamationToken", "type", "initializer"],
+    [SyntaxKind.Parameter]: ["modifiers", "dotDotDotToken", "name", "questionToken", "type", "initializer"],
+    [SyntaxKind.BindingElement]: ["dotDotDotToken", "propertyName", "name", "initializer"],
+    [SyntaxKind.FunctionDeclaration]: ["modifiers", "asteriskToken", "name", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.InterfaceDeclaration]: ["modifiers", "name", "typeParameters", "heritageClauses", "members"],
+    [SyntaxKind.TypeAliasDeclaration]: ["modifiers", "name", "typeParameters", "type"],
+    [SyntaxKind.EnumMember]: ["name", "initializer"],
+    [SyntaxKind.EnumDeclaration]: ["modifiers", "name", "members"],
+    [SyntaxKind.ModuleDeclaration]: ["modifiers", "name", "body"],
+    [SyntaxKind.ImportEqualsDeclaration]: ["modifiers", "name", "moduleReference"],
+    [SyntaxKind.ImportDeclaration]: ["modifiers", "importClause", "moduleSpecifier", "attributes"],
+    [SyntaxKind.ImportSpecifier]: ["propertyName", "name"],
+    [SyntaxKind.ImportClause]: ["name", "namedBindings"],
+    [SyntaxKind.ExportAssignment]: ["modifiers", "expression"],
+    [SyntaxKind.NamespaceExportDeclaration]: ["modifiers", "name"],
+    [SyntaxKind.ExportDeclaration]: ["modifiers", "exportClause", "moduleSpecifier", "attributes"],
+    [SyntaxKind.ExportSpecifier]: ["propertyName", "name"],
+    [SyntaxKind.CallSignature]: ["typeParameters", "parameters", "type"],
+    [SyntaxKind.ConstructSignature]: ["typeParameters", "parameters", "type"],
+    [SyntaxKind.Constructor]: ["modifiers", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.GetAccessor]: ["modifiers", "name", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.SetAccessor]: ["modifiers", "name", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.IndexSignature]: ["modifiers", "parameters", "type"],
+    [SyntaxKind.MethodSignature]: ["modifiers", "name", "postfixToken", "typeParameters", "parameters", "type"],
+    [SyntaxKind.MethodDeclaration]: ["modifiers", "asteriskToken", "name", "postfixToken", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.PropertySignature]: ["modifiers", "name", "postfixToken", "type", "initializer"],
+    [SyntaxKind.PropertyDeclaration]: ["modifiers", "name", "postfixToken", "type", "initializer"],
+    [SyntaxKind.BinaryExpression]: ["left", "operatorToken", "right"],
+    [SyntaxKind.YieldExpression]: ["asteriskToken", "expression"],
+    [SyntaxKind.ArrowFunction]: ["modifiers", "typeParameters", "parameters", "type", "equalsGreaterThanToken", "body"],
+    [SyntaxKind.FunctionExpression]: ["modifiers", "asteriskToken", "name", "typeParameters", "parameters", "type", "body"],
+    [SyntaxKind.AsExpression]: ["expression", "type"],
+    [SyntaxKind.SatisfiesExpression]: ["expression", "type"],
+    [SyntaxKind.ConditionalExpression]: ["condition", "questionToken", "whenTrue", "colonToken", "whenFalse"],
+    [SyntaxKind.PropertyAccessExpression]: ["expression", "questionDotToken", "name"],
+    [SyntaxKind.ElementAccessExpression]: ["expression", "questionDotToken", "argumentExpression"],
+    [SyntaxKind.CallExpression]: ["expression", "questionDotToken", "typeArguments", "arguments"],
+    [SyntaxKind.NewExpression]: ["expression", "typeArguments", "arguments"],
+    [SyntaxKind.TemplateExpression]: ["head", "templateSpans"],
+    [SyntaxKind.TemplateSpan]: ["expression", "literal"],
+    [SyntaxKind.TaggedTemplateExpression]: ["tag", "questionDotToken", "typeArguments", "template"],
+    [SyntaxKind.PropertyAssignment]: ["modifiers", "name", "postfixToken", "initializer"],
+    [SyntaxKind.ShorthandPropertyAssignment]: ["modifiers", "name", "postfixToken", "equalsToken", "objectAssignmentInitializer"],
+    [SyntaxKind.TypeAssertionExpression]: ["type", "expression"],
+    [SyntaxKind.ConditionalType]: ["checkType", "extendsType", "trueType", "falseType"],
+    [SyntaxKind.IndexedAccessType]: ["objectType", "indexType"],
+    [SyntaxKind.TypeReference]: ["typeName", "typeArguments"],
+    [SyntaxKind.ExpressionWithTypeArguments]: ["expression", "typeArguments"],
+    [SyntaxKind.TypePredicate]: ["assertsModifier", "parameterName", "type"],
+    [SyntaxKind.ImportType]: ["argument", "attributes", "qualifier", "typeArguments"],
+    [SyntaxKind.ImportAttribute]: ["name", "value"],
+    [SyntaxKind.TypeQuery]: ["exprName", "typeArguments"],
+    [SyntaxKind.MappedType]: ["readonlyToken", "typeParameter", "nameType", "questionToken", "type", "members"],
+    [SyntaxKind.NamedTupleMember]: ["dotDotDotToken", "name", "questionToken", "type"],
+    [SyntaxKind.FunctionType]: ["typeParameters", "parameters", "type"],
+    [SyntaxKind.ConstructorType]: ["modifiers", "typeParameters", "parameters", "type"],
+    [SyntaxKind.TemplateLiteralType]: ["head", "templateSpans"],
+    [SyntaxKind.TemplateLiteralTypeSpan]: ["type", "literal"],
+    [SyntaxKind.JsxElement]: ["openingElement", "children", "closingElement"],
+    [SyntaxKind.JsxNamespacedName]: ["name", "namespace"],
+    [SyntaxKind.JsxOpeningElement]: ["tagName", "typeArguments", "attributes"],
+    [SyntaxKind.JsxSelfClosingElement]: ["tagName", "typeArguments", "attributes"],
+    [SyntaxKind.JsxFragment]: ["openingFragment", "children", "closingFragment"],
+    [SyntaxKind.JsxAttribute]: ["name", "initializer"],
+    [SyntaxKind.JsxExpression]: ["dotDotDotToken", "expression"],
+    [SyntaxKind.JSDoc]: ["comment", "tags"],
+    [SyntaxKind.JSDocTypeTag]: ["tagName", "typeExpression", "comment"],
+    [SyntaxKind.JSDocTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocTemplateTag]: ["tagName", "constraint", "typeParameters", "comment"],
+    [SyntaxKind.JSDocReturnTag]: ["tagName", "typeExpression", "comment"],
+    [SyntaxKind.JSDocPublicTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocPrivateTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocProtectedTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocReadonlyTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocOverrideTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocDeprecatedTag]: ["tagName", "comment"],
+    [SyntaxKind.JSDocSeeTag]: ["tagName", "nameExpression", "comment"],
+    [SyntaxKind.JSDocImplementsTag]: ["tagName", "className", "comment"],
+    [SyntaxKind.JSDocAugmentsTag]: ["tagName", "className", "comment"],
+    [SyntaxKind.JSDocSatisfiesTag]: ["tagName", "typeExpression", "comment"],
+    [SyntaxKind.JSDocThisTag]: ["tagName", "typeExpression", "comment"],
+    [SyntaxKind.JSDocImportTag]: ["tagName", "importClause", "moduleSpecifier", "attributes", "comment"],
+    [SyntaxKind.JSDocCallbackTag]: ["tagName", "typeExpression", "fullName", "comment"],
+    [SyntaxKind.JSDocOverloadTag]: ["tagName", "typeExpression", "comment"],
+    [SyntaxKind.JSDocTypedefTag]: ["tagName", "typeExpression", "fullName", "comment"],
+    [SyntaxKind.JSDocSignature]: ["typeParameters", "parameters", "type"],
+    [SyntaxKind.ClassStaticBlockDeclaration]: ["modifiers", "body"],
+    [SyntaxKind.ClassDeclaration]: ["modifiers", "name", "typeParameters", "heritageClauses", "members"],
+
+    // Later properties are in variable order, needs special handling
+    [SyntaxKind.JSDocPropertyTag]: [undefined!, undefined!],
+    [SyntaxKind.JSDocParameterTag]: ["tagName", undefined!, undefined!, "comment"],
+};
+
+const HEADER_OFFSET_RESERVED = 0;
+const HEADER_OFFSET_STRING_TABLE_OFFSETS = 4;
+const HEADER_OFFSET_STRING_TABLE = 8;
+const HEADER_OFFSET_EXTENDED_DATA = 12;
+const HEADER_OFFSET_NODES = 16;
+const HEADER_SIZE = 20;
+
+type NodeDataType = typeof NODE_DATA_TYPE_CHILDREN | typeof NODE_DATA_TYPE_STRING | typeof NODE_DATA_TYPE_EXTENDED;
+const NODE_DATA_TYPE_CHILDREN = 0x00000000;
+const NODE_DATA_TYPE_STRING = 0x40000000;
+const NODE_DATA_TYPE_EXTENDED = 0x80000000;
+const NODE_DATA_TYPE_MASK = 0xc0_00_00_00;
+const NODE_CHILD_MASK = 0x00_00_00_ff;
+const NODE_STRING_INDEX_MASK = 0x00_ff_ff_ff;
+const NODE_EXTENDED_DATA_MASK = 0x00_ff_ff_ff;
+
+const NODE_OFFSET_KIND = 0;
+const NODE_OFFSET_POS = 4;
+const NODE_OFFSET_END = 8;
+const NODE_OFFSET_NEXT = 12;
+const NODE_OFFSET_PARENT = 16;
+const NODE_OFFSET_DATA = 20;
+const NODE_LEN = 24;
+
+const KIND_NODE_LIST = 2 ** 32 - 1;
+
+export class RemoteNodeBase {
+    parent: RemoteNode;
+    protected view: DataView;
+    protected decoder: TextDecoder;
+    protected index: number;
+    /** Keys are positions */
+    protected _children: Map<number, RemoteNode | RemoteNodeList> | undefined;
+
+    constructor(view: DataView, decoder: TextDecoder, index: number, parent: RemoteNode) {
+        this.view = view;
+        this.decoder = decoder;
+        this.index = index;
+        this.parent = parent;
+    }
+
+    get kind(): SyntaxKind {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_KIND, true);
+    }
+
+    get pos(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_POS, true);
+    }
+
+    get end(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_END, true);
+    }
+
+    get next(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_NEXT, true);
+    }
+
+    protected get byteIndex(): number {
+        return this.offsetNodes + this.index * NODE_LEN;
+    }
+
+    protected get offsetStringTableOffsets(): number {
+        return this.view.getUint32(HEADER_OFFSET_STRING_TABLE_OFFSETS, true);
+    }
+
+    protected get offsetStringTable(): number {
+        return this.view.getUint32(HEADER_OFFSET_STRING_TABLE, true);
+    }
+
+    protected get offsetExtendedData(): number {
+        return this.view.getUint32(HEADER_OFFSET_EXTENDED_DATA, true);
+    }
+
+    protected get offsetNodes(): number {
+        return this.view.getUint32(HEADER_OFFSET_NODES, true);
+    }
+
+    protected get parentIndex(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_PARENT, true);
+    }
+
+    protected get data(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_DATA, true);
+    }
+
+    protected get dataType(): NodeDataType {
+        return (this.data & NODE_DATA_TYPE_MASK) as NodeDataType;
+    }
+
+    protected get childMask(): number {
+        if (this.dataType !== NODE_DATA_TYPE_CHILDREN) {
+            return 0;
+        }
+        return this.data & NODE_CHILD_MASK;
+    }
+
+    protected getFileText(start: number, end: number): string {
+        return this.decoder.decode(new Uint8Array(this.view.buffer, this.offsetStringTable + start, end - start));
+    }
+}
+
+export class RemoteNodeList extends Array<RemoteNode> implements NodeArray<RemoteNode> {
+    parent: RemoteNode;
+    protected view: DataView;
+    protected decoder: TextDecoder;
+    protected index: number;
+    /** Keys are positions */
+    protected _children: Map<number, RemoteNode | RemoteNodeList> | undefined;
+
+    get pos(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_POS, true);
+    }
+
+    get end(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_END, true);
+    }
+
+    get next(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_NEXT, true);
+    }
+
+    private get data(): number {
+        return this.view.getUint32(this.byteIndex + NODE_OFFSET_DATA, true);
+    }
+
+    private get offsetNodes(): number {
+        return this.view.getUint32(HEADER_OFFSET_NODES, true);
+    }
+
+    private get byteIndex(): number {
+        return this.offsetNodes + this.index * NODE_LEN;
+    }
+
+    constructor(view: DataView, decoder: TextDecoder, index: number, parent: RemoteNode) {
+        super();
+        this.view = view;
+        this.decoder = decoder;
+        this.index = index;
+        this.parent = parent;
+        this.length = this.data;
+
+        const length = this.length;
+        for (let i = 0; i < length; i++) {
+            Object.defineProperty(this, i, {
+                get() {
+                    return this.at(i);
+                },
+            });
+        }
+    }
+
+    *[Symbol.iterator](): ArrayIterator<RemoteNode> {
+        let next = this.index + 1;
+        while (next) {
+            const child = this.getOrCreateChildAtNodeIndex(next);
+            next = child.next;
+            yield child as RemoteNode;
+        }
+    }
+
+    at(index: number): RemoteNode {
+        if (!Number.isInteger(index)) {
+            return undefined!;
+        }
+        if (index < 0) {
+            index = this.length - index;
+        }
+        let next = this.index + 1;
+        for (let i = 0; i < index; i++) {
+            const child = this.getOrCreateChildAtNodeIndex(next);
+            next = child.next;
+        }
+        return this.getOrCreateChildAtNodeIndex(next) as RemoteNode;
+    }
+
+    private getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {
+        const pos = this.view.getUint32(this.offsetNodes + index * NODE_LEN + NODE_OFFSET_POS, true);
+        let child = (this._children ??= new Map()).get(pos);
+        if (!child) {
+            const kind = this.view.getUint32(this.offsetNodes + index * NODE_LEN + NODE_OFFSET_KIND, true);
+            if (kind === KIND_NODE_LIST) {
+                throw new Error("NodeList cannot directly contain another NodeList");
+            }
+            child = new RemoteNode(this.view, this.decoder, index, this.parent);
+            this._children.set(pos, child);
+        }
+        return child;
+    }
+
+    __print(): string {
+        const result = [];
+        result.push(`kind: NodeList`);
+        result.push(`index: ${this.index}`);
+        result.push(`byteIndex: ${this.byteIndex}`);
+        result.push(`length: ${this.length}`);
+        return result.join("\n");
+    }
+}
+
+export class RemoteNode extends RemoteNodeBase implements Node {
+    protected static NODE_LEN: number = NODE_LEN;
+    private sourceFile: SourceFile;
+    id: string;
+
+    constructor(view: DataView, decoder: TextDecoder, index: number, parent: RemoteNode) {
+        super(view, decoder, index, parent);
+        let sourceFile: RemoteNode = this;
+        while (sourceFile && sourceFile.kind !== SyntaxKind.SourceFile) {
+            sourceFile = sourceFile.parent;
+        }
+        if (!sourceFile) {
+            throw new Error("SourceFile not found");
+        }
+        this.sourceFile = sourceFile as unknown as SourceFile;
+        this.id = `${sourceFile.id}.${this.pos}.${this.kind}`;
+    }
+
+    forEachChild<T>(visitNode: (node: Node) => T, visitList?: (list: NodeArray<Node>) => T): T | undefined {
+        if (this.hasChildren()) {
+            let next = this.index + 1;
+            do {
+                const child = this.getOrCreateChildAtNodeIndex(next);
+                if (child instanceof RemoteNodeList) {
+                    if (visitList) {
+                        const result = visitList(child);
+                        if (result) {
+                            return result;
+                        }
+                    }
+                    for (const node of child) {
+                        const result = visitNode(node);
+                        if (result) {
+                            return result;
+                        }
+                    }
+                }
+                else {
+                    const result = visitNode(child);
+                    if (result) {
+                        return result;
+                    }
+                }
+                next = child.next;
+            }
+            while (next);
+        }
+    }
+
+    getSourceFile(): SourceFile {
+        return this.sourceFile;
+    }
+
+    protected getString(index: number): string {
+        const start = this.view.getUint32(this.offsetStringTableOffsets + index * 4, true);
+        const end = this.view.getUint32(this.offsetStringTableOffsets + (index + 1) * 4, true);
+        const text = new Uint8Array(this.view.buffer, this.offsetStringTable + start, end - start);
+        return this.decoder.decode(text);
+    }
+
+    private getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {
+        const pos = this.view.getUint32(this.offsetNodes + index * NODE_LEN + NODE_OFFSET_POS, true);
+        let child = (this._children ??= new Map()).get(pos);
+        if (!child) {
+            const kind = this.view.getUint32(this.offsetNodes + index * NODE_LEN + NODE_OFFSET_KIND, true);
+            child = kind === KIND_NODE_LIST
+                ? new RemoteNodeList(this.view, this.decoder, index, this)
+                : new RemoteNode(this.view, this.decoder, index, this);
+            this._children.set(pos, child);
+        }
+        return child;
+    }
+
+    private hasChildren(): boolean {
+        if (this._children) {
+            return true;
+        }
+        if (this.byteIndex >= this.view.byteLength - NODE_LEN) {
+            return false;
+        }
+        const nextNodeParent = this.view.getUint32(this.offsetNodes + (this.index + 1) * NODE_LEN + NODE_OFFSET_PARENT, true);
+        return nextNodeParent === this.index;
+    }
+
+    private getNamedChild(propertyName: string): RemoteNode | RemoteNodeList | undefined {
+        const propertyNames = childProperties[this.kind];
+        if (!propertyNames) {
+            // `childProperties` is only defined for nodes with more than one child property.
+            // Get the only child if it exists.
+            const child = this.getOrCreateChildAtNodeIndex(this.index + 1);
+            if (child.next !== 0) {
+                throw new Error("Expected only one child");
+            }
+            return child;
+        }
+
+        let order = propertyNames.indexOf(propertyName);
+        if (order === -1) {
+            // JSDocPropertyTag and JSDocParameterTag need special handling
+            // because they have a conditional property order
+            const kind = this.kind;
+            if (kind === SyntaxKind.JSDocPropertyTag) {
+                switch (propertyName) {
+                    case "name":
+                        order = this.isNameFirst ? 0 : 1;
+                        break;
+                    case "typeExpression":
+                        order = this.isNameFirst ? 1 : 0;
+                        break;
+                }
+            }
+            else if (kind === SyntaxKind.JSDocParameterTag) {
+                switch (propertyName) {
+                    case "name":
+                        order = this.isNameFirst ? 1 : 2;
+                    case "typeExpression":
+                        order = this.isNameFirst ? 2 : 1;
+                }
+            }
+            // Node kind does not have this property
+            return undefined;
+        }
+        const mask = this.childMask;
+        if (!(mask & (1 << order))) {
+            // Property is not present
+            return undefined;
+        }
+
+        // The property index is `order`, minus the number of zeros in the mask that are in bit positions less
+        // than the `order`th bit. Example:
+        //
+        // This is a MethodDeclaration with mask 0b01110101. The possible properties are
+        // ["modifiers", "asteriskToken", "name", "postfixToken", "typeParameters", "parameters", "type", "body"]
+        // (it has modifiers, name, typeParameters, parameters, and type).
+        //
+        // | Bit   | 7    | 6    | 5          | 4              | 3            | 2    | 1             | 0         |
+        // | ----- | ---- | ---- | ---------- | -------------- | ------------ | ---- | ------------- | --------- |
+        // | Value | 0    | 1    | 1          | 1              | 0            | 1    | 0             | 1         |
+        // | Name  | body | type | parameters | typeParameters | postfixToken | name | asteriskToken | modifiers |
+        //
+        // We are trying to get the index of "parameters" (bit = 5).
+        // First, set all the more significant bits to 1:
+        //
+        // | Bit   | 7    | 6    | 5          | 4              | 3            | 2    | 1             | 0         |
+        // | ----- | ---- | ---- | ---------- | -------------- | ------------ | ---- | ------------- | --------- |
+        // | Value | 1    | 1    | 1          | 1              | 0            | 1    | 0             | 1         |
+        //
+        // Then, flip the bits:
+        //
+        // | Bit   | 7    | 6    | 5          | 4              | 3            | 2    | 1             | 0         |
+        // | ----- | ---- | ---- | ---------- | -------------- | ------------ | ---- | ------------- | --------- |
+        // | Value | 0    | 0    | 0          | 0              | 1            | 0    | 1             | 0         |
+        //
+        // Counting the 1s gives us the number of *missing properties* before the `order`th property. If every property
+        // were present, we would have `parameters = children[5]`, but since `postfixToken` and `astersiskToken` are
+        // missing, we have `parameters = children[5 - 2]`.
+        const propertyIndex = order - popcount8[~(mask | ((0xff << order) & 0xff)) & 0xff];
+        return this.getOrCreateChildAtNodeIndex(this.index + 1 + propertyIndex);
+    }
+
+    __print(): string {
+        const result = [];
+        result.push(`index: ${this.index}`);
+        result.push(`byteIndex: ${this.byteIndex}`);
+        result.push(`kind: ${SyntaxKind[this.kind]}`);
+        result.push(`pos: ${this.pos}`);
+        result.push(`end: ${this.end}`);
+        result.push(`next: ${this.next}`);
+        result.push(`parent: ${this.parentIndex}`);
+        result.push(`data: ${this.data.toString(2).padStart(32, "0")}`);
+        const dataType = this.dataType === NODE_DATA_TYPE_CHILDREN ? "children" :
+            this.dataType === NODE_DATA_TYPE_STRING ? "string" :
+            "extended";
+        result.push(`dataType: ${dataType}`);
+        if (this.dataType === NODE_DATA_TYPE_CHILDREN) {
+            result.push(`childMask: ${this.childMask.toString(2).padStart(8, "0")}`);
+            result.push(`childProperties: ${childProperties[this.kind]?.join(", ")}`);
+        }
+        return result.join("\n");
+    }
+
+    __printChildren(): string {
+        const result = [];
+        let next = this.index + 1;
+        while (next) {
+            const child = this.getOrCreateChildAtNodeIndex(next);
+            next = child.next;
+            result.push(child.__print());
+        }
+        return result.join("\n\n");
+    }
+
+    __printSubtree(): string {
+        const result = [this.__print()];
+        this.forEachChild(function visitNode(node) {
+            result.push((node as RemoteNode).__print());
+            node.forEachChild(visitNode);
+        }, visitList => {
+            result.push((visitList as RemoteNodeList).__print());
+        });
+        return result.join("\n\n");
+    }
+
+    // Boolean properties
+    get isArrayType(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.JSDocTypeLiteral:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get isTypeOnly(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.ImportSpecifier:
+            case SyntaxKind.ImportClause:
+            case SyntaxKind.ExportSpecifier:
+            case SyntaxKind.ImportEqualsDeclaration:
+            case SyntaxKind.ExportDeclaration:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get isTypeOf(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.ImportType:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get multiline(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.Block:
+            case SyntaxKind.ArrayLiteralExpression:
+            case SyntaxKind.ObjectLiteralExpression:
+            case SyntaxKind.ImportAttributes:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get isExportEquals(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.ExportAssignment:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get isBracketed(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.JSDocPropertyTag:
+            case SyntaxKind.JSDocParameterTag:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get containsOnlyTriviaWhiteSpaces(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.JsxText:
+                return (this.data & 1 << 24) !== 0;
+        }
+    }
+
+    get isNameFirst(): boolean | undefined {
+        switch (this.kind) {
+            case SyntaxKind.JSDocPropertyTag:
+            case SyntaxKind.JSDocParameterTag:
+                return (this.data & 1 << 25) !== 0;
+        }
+    }
+
+    // Children properties
+    get argument(): RemoteNode | undefined {
+        return this.getNamedChild("argument") as RemoteNode;
+    }
+    get argumentExpression(): RemoteNode | undefined {
+        return this.getNamedChild("argumentExpression") as RemoteNode;
+    }
+    get arguments(): RemoteNodeList | undefined {
+        return this.getNamedChild("arguments") as RemoteNodeList;
+    }
+    get assertsModifier(): RemoteNode | undefined {
+        return this.getNamedChild("assertsModifier") as RemoteNode;
+    }
+    get asteriskToken(): RemoteNode | undefined {
+        return this.getNamedChild("asteriskToken") as RemoteNode;
+    }
+    get attributes(): RemoteNode | undefined {
+        return this.getNamedChild("attributes") as RemoteNode;
+    }
+    get awaitModifier(): RemoteNode | undefined {
+        return this.getNamedChild("awaitModifier") as RemoteNode;
+    }
+    get block(): RemoteNode | undefined {
+        return this.getNamedChild("block") as RemoteNode;
+    }
+    get body(): RemoteNode | undefined {
+        return this.getNamedChild("body") as RemoteNode;
+    }
+    get caseBlock(): RemoteNode | undefined {
+        return this.getNamedChild("caseBlock") as RemoteNode;
+    }
+    get catchClause(): RemoteNode | undefined {
+        return this.getNamedChild("catchClause") as RemoteNode;
+    }
+    get checkType(): RemoteNode | undefined {
+        return this.getNamedChild("checkType") as RemoteNode;
+    }
+    get children(): RemoteNodeList | undefined {
+        return this.getNamedChild("children") as RemoteNodeList;
+    }
+    get className(): RemoteNode | undefined {
+        return this.getNamedChild("className") as RemoteNode;
+    }
+    get closingElement(): RemoteNode | undefined {
+        return this.getNamedChild("closingElement") as RemoteNode;
+    }
+    get closingFragment(): RemoteNode | undefined {
+        return this.getNamedChild("closingFragment") as RemoteNode;
+    }
+    get colonToken(): RemoteNode | undefined {
+        return this.getNamedChild("colonToken") as RemoteNode;
+    }
+    get comment(): RemoteNode | undefined {
+        return this.getNamedChild("comment") as RemoteNode;
+    }
+    get condition(): RemoteNode | undefined {
+        return this.getNamedChild("condition") as RemoteNode;
+    }
+    get constraint(): RemoteNode | undefined {
+        return this.getNamedChild("constraint") as RemoteNode;
+    }
+    get declarationList(): RemoteNode | undefined {
+        return this.getNamedChild("declarationList") as RemoteNode;
+    }
+    get defaultType(): RemoteNode | undefined {
+        return this.getNamedChild("defaultType") as RemoteNode;
+    }
+    get dotDotDotToken(): RemoteNode | undefined {
+        return this.getNamedChild("dotDotDotToken") as RemoteNode;
+    }
+    get elements(): RemoteNodeList | undefined {
+        return this.getNamedChild("elements") as RemoteNodeList;
+    }
+    get elseStatement(): RemoteNode | undefined {
+        return this.getNamedChild("elseStatement") as RemoteNode;
+    }
+    get equalsGreaterThanToken(): RemoteNode | undefined {
+        return this.getNamedChild("equalsGreaterThanToken") as RemoteNode;
+    }
+    get equalsToken(): RemoteNode | undefined {
+        return this.getNamedChild("equalsToken") as RemoteNode;
+    }
+    get exclamationToken(): RemoteNode | undefined {
+        return this.getNamedChild("exclamationToken") as RemoteNode;
+    }
+    get exportClause(): RemoteNode | undefined {
+        return this.getNamedChild("exportClause") as RemoteNode;
+    }
+    get expression(): RemoteNode | undefined {
+        return this.getNamedChild("expression") as RemoteNode;
+    }
+    get exprName(): RemoteNode | undefined {
+        return this.getNamedChild("exprName") as RemoteNode;
+    }
+    get extendsType(): RemoteNode | undefined {
+        return this.getNamedChild("extendsType") as RemoteNode;
+    }
+    get falseType(): RemoteNode | undefined {
+        return this.getNamedChild("falseType") as RemoteNode;
+    }
+    get finallyBlock(): RemoteNode | undefined {
+        return this.getNamedChild("finallyBlock") as RemoteNode;
+    }
+    get fullName(): RemoteNode | undefined {
+        return this.getNamedChild("fullName") as RemoteNode;
+    }
+    get head(): RemoteNode | undefined {
+        return this.getNamedChild("head") as RemoteNode;
+    }
+    get heritageClauses(): RemoteNodeList | undefined {
+        return this.getNamedChild("heritageClauses") as RemoteNodeList;
+    }
+    get importClause(): RemoteNode | undefined {
+        return this.getNamedChild("importClause") as RemoteNode;
+    }
+    get incrementor(): RemoteNode | undefined {
+        return this.getNamedChild("incrementor") as RemoteNode;
+    }
+    get indexType(): RemoteNode | undefined {
+        return this.getNamedChild("indexType") as RemoteNode;
+    }
+    get initializer(): RemoteNode | undefined {
+        return this.getNamedChild("initializer") as RemoteNode;
+    }
+    get label(): RemoteNode | undefined {
+        return this.getNamedChild("label") as RemoteNode;
+    }
+    get left(): RemoteNode | undefined {
+        return this.getNamedChild("left") as RemoteNode;
+    }
+    get literal(): RemoteNode | undefined {
+        return this.getNamedChild("literal") as RemoteNode;
+    }
+    get members(): RemoteNodeList | undefined {
+        return this.getNamedChild("members") as RemoteNodeList;
+    }
+    get modifiers(): RemoteNodeList | undefined {
+        return this.getNamedChild("modifiers") as RemoteNodeList;
+    }
+    get moduleReference(): RemoteNode | undefined {
+        return this.getNamedChild("moduleReference") as RemoteNode;
+    }
+    get moduleSpecifier(): RemoteNode | undefined {
+        return this.getNamedChild("moduleSpecifier") as RemoteNode;
+    }
+    get name(): RemoteNode | undefined {
+        return this.getNamedChild("name") as RemoteNode;
+    }
+    get namedBindings(): RemoteNode | undefined {
+        return this.getNamedChild("namedBindings") as RemoteNode;
+    }
+    get nameExpression(): RemoteNode | undefined {
+        return this.getNamedChild("nameExpression") as RemoteNode;
+    }
+    get namespace(): RemoteNode | undefined {
+        return this.getNamedChild("namespace") as RemoteNode;
+    }
+    get nameType(): RemoteNode | undefined {
+        return this.getNamedChild("nameType") as RemoteNode;
+    }
+    get objectAssignmentInitializer(): RemoteNode | undefined {
+        return this.getNamedChild("objectAssignmentInitializer") as RemoteNode;
+    }
+    get objectType(): RemoteNode | undefined {
+        return this.getNamedChild("objectType") as RemoteNode;
+    }
+    get openingElement(): RemoteNode | undefined {
+        return this.getNamedChild("openingElement") as RemoteNode;
+    }
+    get openingFragment(): RemoteNode | undefined {
+        return this.getNamedChild("openingFragment") as RemoteNode;
+    }
+    get operatorToken(): RemoteNode | undefined {
+        return this.getNamedChild("operatorToken") as RemoteNode;
+    }
+    get parameterName(): RemoteNode | undefined {
+        return this.getNamedChild("parameterName") as RemoteNode;
+    }
+    get parameters(): RemoteNodeList | undefined {
+        return this.getNamedChild("parameters") as RemoteNodeList;
+    }
+    get postfixToken(): RemoteNode | undefined {
+        return this.getNamedChild("postfixToken") as RemoteNode;
+    }
+    get propertyName(): RemoteNode | undefined {
+        return this.getNamedChild("propertyName") as RemoteNode;
+    }
+    get qualifier(): RemoteNode | undefined {
+        return this.getNamedChild("qualifier") as RemoteNode;
+    }
+    get questionDotToken(): RemoteNode | undefined {
+        return this.getNamedChild("questionDotToken") as RemoteNode;
+    }
+    get questionToken(): RemoteNode | undefined {
+        return this.getNamedChild("questionToken") as RemoteNode;
+    }
+    get readonlyToken(): RemoteNode | undefined {
+        return this.getNamedChild("readonlyToken") as RemoteNode;
+    }
+    get right(): RemoteNode | undefined {
+        return this.getNamedChild("right") as RemoteNode;
+    }
+    get statement(): RemoteNode | undefined {
+        return this.getNamedChild("statement") as RemoteNode;
+    }
+    get statements(): RemoteNodeList | undefined {
+        return this.getNamedChild("statements") as RemoteNodeList;
+    }
+    get tag(): RemoteNode | undefined {
+        return this.getNamedChild("tag") as RemoteNode;
+    }
+    get tagName(): RemoteNode | undefined {
+        return this.getNamedChild("tagName") as RemoteNode;
+    }
+    get tags(): RemoteNodeList | undefined {
+        return this.getNamedChild("tags") as RemoteNodeList;
+    }
+    get template(): RemoteNode | undefined {
+        return this.getNamedChild("template") as RemoteNode;
+    }
+    get templateSpans(): RemoteNodeList | undefined {
+        return this.getNamedChild("templateSpans") as RemoteNodeList;
+    }
+    get thenStatement(): RemoteNode | undefined {
+        return this.getNamedChild("thenStatement") as RemoteNode;
+    }
+    get trueType(): RemoteNode | undefined {
+        return this.getNamedChild("trueType") as RemoteNode;
+    }
+    get tryBlock(): RemoteNode | undefined {
+        return this.getNamedChild("tryBlock") as RemoteNode;
+    }
+    get type(): RemoteNode | undefined {
+        return this.getNamedChild("type") as RemoteNode;
+    }
+    get typeArguments(): RemoteNode | undefined {
+        return this.getNamedChild("typeArguments") as RemoteNode;
+    }
+    get typeExpression(): RemoteNode | undefined {
+        return this.getNamedChild("typeExpression") as RemoteNode;
+    }
+    get typeName(): RemoteNode | undefined {
+        return this.getNamedChild("typeName") as RemoteNode;
+    }
+    get typeParameter(): RemoteNode | undefined {
+        return this.getNamedChild("typeParameter") as RemoteNode;
+    }
+    get typeParameters(): RemoteNodeList | undefined {
+        return this.getNamedChild("typeParameters") as RemoteNodeList;
+    }
+    get value(): RemoteNode | undefined {
+        return this.getNamedChild("value") as RemoteNode;
+    }
+    get variableDeclaration(): RemoteNode | undefined {
+        return this.getNamedChild("variableDeclaration") as RemoteNode;
+    }
+    get whenFalse(): RemoteNode | undefined {
+        return this.getNamedChild("whenFalse") as RemoteNode;
+    }
+    get whenTrue(): RemoteNode | undefined {
+        return this.getNamedChild("whenTrue") as RemoteNode;
+    }
+
+    // String properties
+    get text(): string | undefined {
+        switch (this.kind) {
+            case SyntaxKind.JsxText:
+            case SyntaxKind.Identifier:
+            case SyntaxKind.PrivateIdentifier:
+            case SyntaxKind.StringLiteral:
+            case SyntaxKind.NumericLiteral:
+            case SyntaxKind.BigIntLiteral:
+            case SyntaxKind.RegularExpressionLiteral:
+            case SyntaxKind.NoSubstitutionTemplateLiteral:
+            case SyntaxKind.JSDocText: {
+                const stringIndex = this.data & NODE_STRING_INDEX_MASK;
+                return this.getString(stringIndex);
+            }
+            case SyntaxKind.SourceFile:
+            case SyntaxKind.TemplateHead:
+            case SyntaxKind.TemplateMiddle:
+            case SyntaxKind.TemplateTail: {
+                const extendedDataOffset = this.offsetExtendedData + (this.data & NODE_EXTENDED_DATA_MASK);
+                const stringIndex = this.view.getUint32(extendedDataOffset, true);
+                return this.getString(stringIndex);
+            }
+        }
+    }
+
+    get rawText(): string | undefined {
+        switch (this.kind) {
+            case SyntaxKind.TemplateHead:
+            case SyntaxKind.TemplateMiddle:
+            case SyntaxKind.TemplateTail:
+                const extendedDataOffset = this.offsetExtendedData + (this.data & NODE_EXTENDED_DATA_MASK);
+                const stringIndex = this.view.getUint32(extendedDataOffset + 4, true);
+                return this.getString(stringIndex);
+        }
+    }
+
+    get fileName(): string | undefined {
+        switch (this.kind) {
+            case SyntaxKind.SourceFile:
+                const extendedDataOffset = this.offsetExtendedData + (this.data & NODE_EXTENDED_DATA_MASK);
+                const stringIndex = this.view.getUint32(extendedDataOffset + 4, true);
+                return this.getString(stringIndex);
+        }
+    }
+
+    // Other properties
+    get flags(): number {
+        switch (this.kind) {
+            case SyntaxKind.VariableDeclarationList:
+                return this.data & (1 << 24 | 1 << 25) >> 24;
+            default:
+                return 0;
+        }
+    }
+
+    get token(): SyntaxKind | undefined {
+        switch (this.kind) {
+            case SyntaxKind.ImportAttributes:
+                if ((this.data & 1 << 25) !== 0) {
+                    return SyntaxKind.AssertKeyword;
+                }
+                return SyntaxKind.WithKeyword;
+        }
+    }
+
+    get templateFlags(): number | undefined {
+        switch (this.kind) {
+            case SyntaxKind.TemplateHead:
+            case SyntaxKind.TemplateMiddle:
+            case SyntaxKind.TemplateTail:
+                const extendedDataOffset = this.offsetExtendedData + (this.data & NODE_EXTENDED_DATA_MASK);
+                return this.view.getUint32(extendedDataOffset + 8, true);
+        }
+    }
+}
+
+export class RemoteSourceFile extends RemoteNode {
+    constructor(data: Uint8Array, decoder: TextDecoder) {
+        const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
+        super(view, decoder, 1, undefined!);
+        this.id = this.getString(this.view.getUint32(this.offsetExtendedData + 8, true));
+    }
+}
diff --git a/_packages/api/src/objectRegistry.ts b/_packages/api/src/objectRegistry.ts
new file mode 100644
index 0000000000..7a36ecb9c6
--- /dev/null
+++ b/_packages/api/src/objectRegistry.ts
@@ -0,0 +1,85 @@
+import {
+    Project,
+    Symbol,
+    Type,
+} from "./api.ts";
+import type { Client } from "./client.ts";
+import type {
+    ProjectResponse,
+    SymbolResponse,
+    TypeResponse,
+} from "./proto.ts";
+
+export class ObjectRegistry {
+    private client: Client;
+    private projects: Map<string, Project> = new Map();
+    private symbols: Map<string, Symbol> = new Map();
+    private types: Map<string, Type> = new Map();
+
+    constructor(client: Client) {
+        this.client = client;
+    }
+
+    getProject(data: ProjectResponse): Project {
+        let project = this.projects.get(data.id);
+        if (project) {
+            return project;
+        }
+
+        project = new Project(this.client, this, data);
+        this.projects.set(data.id, project);
+        return project;
+    }
+
+    getSymbol(data: SymbolResponse): Symbol {
+        let symbol = this.symbols.get(data.id);
+        if (symbol) {
+            return symbol;
+        }
+
+        symbol = new Symbol(this.client, this, data);
+        this.symbols.set(data.id, symbol);
+        return symbol;
+    }
+
+    getType(data: TypeResponse): Type {
+        let type = this.types.get(data.id);
+        if (type) {
+            return type;
+        }
+
+        type = new Type(this.client, this, data);
+        this.types.set(data.id, type);
+        return type;
+    }
+
+    release(object: object): void {
+        if (object instanceof Project) {
+            this.releaseProject(object);
+        }
+        else if (object instanceof Symbol) {
+            this.releaseSymbol(object);
+        }
+        else if (object instanceof Type) {
+            this.releaseType(object);
+        }
+        else {
+            throw new Error("Unknown object type");
+        }
+    }
+
+    releaseProject(project: Project): void {
+        this.projects.delete(project.id);
+        this.client.request("release", project.id);
+    }
+
+    releaseSymbol(symbol: Symbol): void {
+        this.symbols.delete(symbol.id);
+        this.client.request("release", symbol.id);
+    }
+
+    releaseType(type: Type): void {
+        this.types.delete(type.id);
+        this.client.request("release", type.id);
+    }
+}
diff --git a/_packages/api/src/path.ts b/_packages/api/src/path.ts
new file mode 100644
index 0000000000..487432f057
--- /dev/null
+++ b/_packages/api/src/path.ts
@@ -0,0 +1,136 @@
+const CharacterCodesSlash = "/".charCodeAt(0);
+const CharacterCodesBackslash = "\\".charCodeAt(0);
+const CharacterCodesColon = ":".charCodeAt(0);
+const CharacterCodesPercent = "%".charCodeAt(0);
+const CharacterCodes3 = "3".charCodeAt(0);
+const CharacterCodesa = "a".charCodeAt(0);
+const CharacterCodesz = "z".charCodeAt(0);
+const CharacterCodesA = "A".charCodeAt(0);
+const CharacterCodesZ = "Z".charCodeAt(0);
+const directorySeparator = "/";
+const altDirectorySeparator = "\\";
+const urlSchemeSeparator = "://";
+
+function isVolumeCharacter(charCode: number) {
+    return (charCode >= CharacterCodesa && charCode <= CharacterCodesz) ||
+        (charCode >= CharacterCodesA && charCode <= CharacterCodesZ);
+}
+
+function getFileUrlVolumeSeparatorEnd(url: string, start: number) {
+    const ch0 = url.charCodeAt(start);
+    if (ch0 === CharacterCodesColon) return start + 1;
+    if (ch0 === CharacterCodesPercent && url.charCodeAt(start + 1) === CharacterCodes3) {
+        const ch2 = url.charCodeAt(start + 2);
+        if (ch2 === CharacterCodesa || ch2 === CharacterCodesA) return start + 3;
+    }
+    return -1;
+}
+
+/**
+ * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
+ *
+ * For example:
+ * ```ts
+ * getRootLength("a") === 0                   // ""
+ * getRootLength("/") === 1                   // "/"
+ * getRootLength("c:") === 2                  // "c:"
+ * getRootLength("c:d") === 0                 // ""
+ * getRootLength("c:/") === 3                 // "c:/"
+ * getRootLength("c:\\") === 3                // "c:\\"
+ * getRootLength("//server") === 7            // "//server"
+ * getRootLength("//server/share") === 8      // "//server/"
+ * getRootLength("\\\\server") === 7          // "\\\\server"
+ * getRootLength("\\\\server\\share") === 8   // "\\\\server\\"
+ * getRootLength("file:///path") === 8        // "file:///"
+ * getRootLength("file:///c:") === 10         // "file:///c:"
+ * getRootLength("file:///c:d") === 8         // "file:///"
+ * getRootLength("file:///c:/path") === 11    // "file:///c:/"
+ * getRootLength("file://server") === 13      // "file://server"
+ * getRootLength("file://server/path") === 14 // "file://server/"
+ * getRootLength("http://server") === 13      // "http://server"
+ * getRootLength("http://server/path") === 14 // "http://server/"
+ * ```
+ *
+ * @internal
+ */
+export function getRootLength(path: string): number {
+    const rootLength = getEncodedRootLength(path);
+    return rootLength < 0 ? ~rootLength : rootLength;
+}
+
+/**
+ * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
+ * If the root is part of a URL, the twos-complement of the root length is returned.
+ */
+function getEncodedRootLength(path: string): number {
+    if (!path) return 0;
+    const ch0 = path.charCodeAt(0);
+
+    // POSIX or UNC
+    if (ch0 === CharacterCodesSlash || ch0 === CharacterCodesBackslash) {
+        if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\")
+
+        const p1 = path.indexOf(ch0 === CharacterCodesSlash ? directorySeparator : altDirectorySeparator, 2);
+        if (p1 < 0) return path.length; // UNC: "//server" or "\\server"
+
+        return p1 + 1; // UNC: "//server/" or "\\server\"
+    }
+
+    // DOS
+    if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodesColon) {
+        const ch2 = path.charCodeAt(2);
+        if (ch2 === CharacterCodesSlash || ch2 === CharacterCodesBackslash) return 3; // DOS: "c:/" or "c:\"
+        if (path.length === 2) return 2; // DOS: "c:" (but not "c:d")
+    }
+
+    // URL
+    const schemeEnd = path.indexOf(urlSchemeSeparator);
+    if (schemeEnd !== -1) {
+        const authorityStart = schemeEnd + urlSchemeSeparator.length;
+        const authorityEnd = path.indexOf(directorySeparator, authorityStart);
+        if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
+            // For local "file" URLs, include the leading DOS volume (if present).
+            // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
+            // special case interpreted as "the machine from which the URL is being interpreted".
+            const scheme = path.slice(0, schemeEnd);
+            const authority = path.slice(authorityStart, authorityEnd);
+            if (
+                scheme === "file" && (authority === "" || authority === "localhost") &&
+                isVolumeCharacter(path.charCodeAt(authorityEnd + 1))
+            ) {
+                const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
+                if (volumeSeparatorEnd !== -1) {
+                    if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodesSlash) {
+                        // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
+                        return ~(volumeSeparatorEnd + 1);
+                    }
+                    if (volumeSeparatorEnd === path.length) {
+                        // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
+                        // but not "file:///c:d" or "file:///c%3ad"
+                        return ~volumeSeparatorEnd;
+                    }
+                }
+            }
+            return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
+        }
+        return ~path.length; // URL: "file://server", "http://server"
+    }
+
+    // relative
+    return 0;
+}
+
+export function getPathComponents(path: string): string[] {
+    return pathComponents(path, getRootLength(path));
+}
+
+function pathComponents(path: string, rootLength: number) {
+    const root = path.substring(0, rootLength);
+    const rest = path.substring(rootLength).split("/");
+    if (rest.length && !lastOrUndefined(rest)) rest.pop();
+    return [root, ...rest];
+}
+
+function lastOrUndefined<T>(array: T[]): T | undefined {
+    return array.length ? array[array.length - 1] : undefined;
+}
diff --git a/_packages/api/src/proto.ts b/_packages/api/src/proto.ts
new file mode 100644
index 0000000000..d47eceab7e
--- /dev/null
+++ b/_packages/api/src/proto.ts
@@ -0,0 +1,23 @@
+export interface ConfigResponse {
+    options: Record<string, unknown>;
+    fileNames: string[];
+}
+
+export interface ProjectResponse {
+    id: string;
+    configFileName: string;
+    compilerOptions: Record<string, unknown>;
+    rootFiles: string[];
+}
+
+export interface SymbolResponse {
+    id: string;
+    name: string;
+    flags: number;
+    checkFlags: number;
+}
+
+export interface TypeResponse {
+    id: string;
+    flags: number;
+}
diff --git a/_packages/api/src/symbolFlags.enum.ts b/_packages/api/src/symbolFlags.enum.ts
new file mode 100644
index 0000000000..8032b9fa45
--- /dev/null
+++ b/_packages/api/src/symbolFlags.enum.ts
@@ -0,0 +1,75 @@
+export enum SymbolFlags {
+    None = 0,
+    FunctionScopedVariable = 1 << 0,
+    BlockScopedVariable = 1 << 1,
+    Property = 1 << 2,
+    EnumMember = 1 << 3,
+    Function = 1 << 4,
+    Class = 1 << 5,
+    Interface = 1 << 6,
+    ConstEnum = 1 << 7,
+    RegularEnum = 1 << 8,
+    ValueModule = 1 << 9,
+    NamespaceModule = 1 << 10,
+    TypeLiteral = 1 << 11,
+    ObjectLiteral = 1 << 12,
+    Method = 1 << 13,
+    Constructor = 1 << 14,
+    GetAccessor = 1 << 15,
+    SetAccessor = 1 << 16,
+    Signature = 1 << 17,
+    TypeParameter = 1 << 18,
+    TypeAlias = 1 << 19,
+    ExportValue = 1 << 20,
+    Alias = 1 << 21,
+    Prototype = 1 << 22,
+    ExportStar = 1 << 23,
+    Optional = 1 << 24,
+    Transient = 1 << 25,
+    Assignment = 1 << 26,
+    ModuleExports = 1 << 27,
+    ConstEnumOnlyModule = 1 << 28,
+    ReplaceableByMethod = 1 << 29,
+    GlobalLookup = 1 << 30,
+    All = 1 << 30 - 1,
+
+    Enum = RegularEnum | ConstEnum,
+    Variable = FunctionScopedVariable | BlockScopedVariable,
+    Value = Variable | Property | EnumMember | ObjectLiteral | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor,
+    Type = Class | Interface | Enum | EnumMember | TypeLiteral | TypeParameter | TypeAlias,
+    Namespace = ValueModule | NamespaceModule | Enum,
+    Module = ValueModule | NamespaceModule,
+    Accessor = GetAccessor | SetAccessor,
+
+    FunctionScopedVariableExcludes = Value & ~FunctionScopedVariable,
+
+    BlockScopedVariableExcludes = Value,
+
+    ParameterExcludes = Value,
+    PropertyExcludes = Value & ~Property,
+    EnumMemberExcludes = Value | Type,
+    FunctionExcludes = Value & ~(Function | ValueModule | Class),
+    ClassExcludes = (Value | Type) & ~(ValueModule | Interface | Function),
+    InterfaceExcludes = Type & ~(Interface | Class),
+    RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule),
+    ConstEnumExcludes = (Value | Type) & ~ConstEnum,
+    ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule),
+    NamespaceModuleExcludes = None,
+    MethodExcludes = Value & ~Method,
+    GetAccessorExcludes = Value & ~SetAccessor,
+    SetAccessorExcludes = Value & ~GetAccessor,
+    AccessorExcludes = Value & ~Accessor,
+    TypeParameterExcludes = Type & ~TypeParameter,
+    TypeAliasExcludes = Type,
+    AliasExcludes = Alias,
+    ModuleMember = Variable | Function | Class | Interface | Enum | Module | TypeAlias | Alias,
+    ExportHasLocal = Function | Class | Enum | ValueModule,
+    BlockScoped = BlockScopedVariable | Class | Enum,
+    PropertyOrAccessor = Property | Accessor,
+    ClassMember = Method | Accessor | Property,
+    ExportSupportsDefaultModifier = Class | Function | Interface,
+    ExportDoesNotSupportDefaultModifier = ~ExportSupportsDefaultModifier,
+
+    Classifiable = Class | Enum | TypeAlias | Interface | TypeParameter | Module | Alias,
+    LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral | Function,
+}
diff --git a/_packages/api/src/symbolFlags.ts b/_packages/api/src/symbolFlags.ts
new file mode 100644
index 0000000000..896335fb8f
--- /dev/null
+++ b/_packages/api/src/symbolFlags.ts
@@ -0,0 +1,71 @@
+export var SymbolFlags: any;
+(function (SymbolFlags) {
+    SymbolFlags[SymbolFlags["None"] = 0] = "None";
+    SymbolFlags[SymbolFlags["FunctionScopedVariable"] = 1] = "FunctionScopedVariable";
+    SymbolFlags[SymbolFlags["BlockScopedVariable"] = 2] = "BlockScopedVariable";
+    SymbolFlags[SymbolFlags["Property"] = 4] = "Property";
+    SymbolFlags[SymbolFlags["EnumMember"] = 8] = "EnumMember";
+    SymbolFlags[SymbolFlags["Function"] = 16] = "Function";
+    SymbolFlags[SymbolFlags["Class"] = 32] = "Class";
+    SymbolFlags[SymbolFlags["Interface"] = 64] = "Interface";
+    SymbolFlags[SymbolFlags["ConstEnum"] = 128] = "ConstEnum";
+    SymbolFlags[SymbolFlags["RegularEnum"] = 256] = "RegularEnum";
+    SymbolFlags[SymbolFlags["ValueModule"] = 512] = "ValueModule";
+    SymbolFlags[SymbolFlags["NamespaceModule"] = 1024] = "NamespaceModule";
+    SymbolFlags[SymbolFlags["TypeLiteral"] = 2048] = "TypeLiteral";
+    SymbolFlags[SymbolFlags["ObjectLiteral"] = 4096] = "ObjectLiteral";
+    SymbolFlags[SymbolFlags["Method"] = 8192] = "Method";
+    SymbolFlags[SymbolFlags["Constructor"] = 16384] = "Constructor";
+    SymbolFlags[SymbolFlags["GetAccessor"] = 32768] = "GetAccessor";
+    SymbolFlags[SymbolFlags["SetAccessor"] = 65536] = "SetAccessor";
+    SymbolFlags[SymbolFlags["Signature"] = 131072] = "Signature";
+    SymbolFlags[SymbolFlags["TypeParameter"] = 262144] = "TypeParameter";
+    SymbolFlags[SymbolFlags["TypeAlias"] = 524288] = "TypeAlias";
+    SymbolFlags[SymbolFlags["ExportValue"] = 1048576] = "ExportValue";
+    SymbolFlags[SymbolFlags["Alias"] = 2097152] = "Alias";
+    SymbolFlags[SymbolFlags["Prototype"] = 4194304] = "Prototype";
+    SymbolFlags[SymbolFlags["ExportStar"] = 8388608] = "ExportStar";
+    SymbolFlags[SymbolFlags["Optional"] = 16777216] = "Optional";
+    SymbolFlags[SymbolFlags["Transient"] = 33554432] = "Transient";
+    SymbolFlags[SymbolFlags["Assignment"] = 67108864] = "Assignment";
+    SymbolFlags[SymbolFlags["ModuleExports"] = 134217728] = "ModuleExports";
+    SymbolFlags[SymbolFlags["ConstEnumOnlyModule"] = 268435456] = "ConstEnumOnlyModule";
+    SymbolFlags[SymbolFlags["ReplaceableByMethod"] = 536870912] = "ReplaceableByMethod";
+    SymbolFlags[SymbolFlags["GlobalLookup"] = 1073741824] = "GlobalLookup";
+    SymbolFlags[SymbolFlags["All"] = 536870912] = "All";
+    SymbolFlags[SymbolFlags["Enum"] = 384] = "Enum";
+    SymbolFlags[SymbolFlags["Variable"] = 3] = "Variable";
+    SymbolFlags[SymbolFlags["Value"] = 111551] = "Value";
+    SymbolFlags[SymbolFlags["Type"] = 788968] = "Type";
+    SymbolFlags[SymbolFlags["Namespace"] = 1920] = "Namespace";
+    SymbolFlags[SymbolFlags["Module"] = 1536] = "Module";
+    SymbolFlags[SymbolFlags["Accessor"] = 98304] = "Accessor";
+    SymbolFlags[SymbolFlags["FunctionScopedVariableExcludes"] = 111550] = "FunctionScopedVariableExcludes";
+    SymbolFlags[SymbolFlags["BlockScopedVariableExcludes"] = 111551] = "BlockScopedVariableExcludes";
+    SymbolFlags[SymbolFlags["ParameterExcludes"] = 111551] = "ParameterExcludes";
+    SymbolFlags[SymbolFlags["PropertyExcludes"] = 111547] = "PropertyExcludes";
+    SymbolFlags[SymbolFlags["EnumMemberExcludes"] = 900095] = "EnumMemberExcludes";
+    SymbolFlags[SymbolFlags["FunctionExcludes"] = 110991] = "FunctionExcludes";
+    SymbolFlags[SymbolFlags["ClassExcludes"] = 899503] = "ClassExcludes";
+    SymbolFlags[SymbolFlags["InterfaceExcludes"] = 788872] = "InterfaceExcludes";
+    SymbolFlags[SymbolFlags["RegularEnumExcludes"] = 899327] = "RegularEnumExcludes";
+    SymbolFlags[SymbolFlags["ConstEnumExcludes"] = 899967] = "ConstEnumExcludes";
+    SymbolFlags[SymbolFlags["ValueModuleExcludes"] = 110735] = "ValueModuleExcludes";
+    SymbolFlags[SymbolFlags["NamespaceModuleExcludes"] = 0] = "NamespaceModuleExcludes";
+    SymbolFlags[SymbolFlags["MethodExcludes"] = 103359] = "MethodExcludes";
+    SymbolFlags[SymbolFlags["GetAccessorExcludes"] = 46015] = "GetAccessorExcludes";
+    SymbolFlags[SymbolFlags["SetAccessorExcludes"] = 78783] = "SetAccessorExcludes";
+    SymbolFlags[SymbolFlags["AccessorExcludes"] = 13247] = "AccessorExcludes";
+    SymbolFlags[SymbolFlags["TypeParameterExcludes"] = 526824] = "TypeParameterExcludes";
+    SymbolFlags[SymbolFlags["TypeAliasExcludes"] = 788968] = "TypeAliasExcludes";
+    SymbolFlags[SymbolFlags["AliasExcludes"] = 2097152] = "AliasExcludes";
+    SymbolFlags[SymbolFlags["ModuleMember"] = 2623475] = "ModuleMember";
+    SymbolFlags[SymbolFlags["ExportHasLocal"] = 944] = "ExportHasLocal";
+    SymbolFlags[SymbolFlags["BlockScoped"] = 418] = "BlockScoped";
+    SymbolFlags[SymbolFlags["PropertyOrAccessor"] = 98308] = "PropertyOrAccessor";
+    SymbolFlags[SymbolFlags["ClassMember"] = 106500] = "ClassMember";
+    SymbolFlags[SymbolFlags["ExportSupportsDefaultModifier"] = 112] = "ExportSupportsDefaultModifier";
+    SymbolFlags[SymbolFlags["ExportDoesNotSupportDefaultModifier"] = -113] = "ExportDoesNotSupportDefaultModifier";
+    SymbolFlags[SymbolFlags["Classifiable"] = 2885600] = "Classifiable";
+    SymbolFlags[SymbolFlags["LateBindingContainer"] = 6256] = "LateBindingContainer";
+})(SymbolFlags || (SymbolFlags = {}));
diff --git a/_packages/api/src/typeFlags.enum.ts b/_packages/api/src/typeFlags.enum.ts
new file mode 100644
index 0000000000..1c57bd0c31
--- /dev/null
+++ b/_packages/api/src/typeFlags.enum.ts
@@ -0,0 +1,32 @@
+export enum TypeFlags {
+    None = 0,
+    Any = 1 << 0,
+    Unknown = 1 << 1,
+    Undefined = 1 << 2,
+    Null = 1 << 3,
+    Void = 1 << 4,
+    String = 1 << 5,
+    Number = 1 << 6,
+    BigInt = 1 << 7,
+    Boolean = 1 << 8,
+    ESSymbol = 1 << 9,
+    StringLiteral = 1 << 10,
+    NumberLiteral = 1 << 11,
+    BigIntLiteral = 1 << 12,
+    BooleanLiteral = 1 << 13,
+    UniqueESSymbol = 1 << 14,
+    EnumLiteral = 1 << 15,
+    Enum = 1 << 16,
+    Never = 1 << 17,
+    TypeParameter = 1 << 18,
+    Object = 1 << 19,
+    Union = 1 << 20,
+    Intersection = 1 << 21,
+    Index = 1 << 22,
+    IndexedAccess = 1 << 23,
+    Conditional = 1 << 24,
+    Substitution = 1 << 25,
+    NonPrimitive = 1 << 26,
+    TemplateLiteral = 1 << 27,
+    StringMapping = 1 << 28,
+}
diff --git a/_packages/api/src/typeFlags.ts b/_packages/api/src/typeFlags.ts
new file mode 100644
index 0000000000..a581948cb0
--- /dev/null
+++ b/_packages/api/src/typeFlags.ts
@@ -0,0 +1,33 @@
+export var TypeFlags: any;
+(function (TypeFlags) {
+    TypeFlags[TypeFlags["None"] = 0] = "None";
+    TypeFlags[TypeFlags["Any"] = 1] = "Any";
+    TypeFlags[TypeFlags["Unknown"] = 2] = "Unknown";
+    TypeFlags[TypeFlags["Undefined"] = 4] = "Undefined";
+    TypeFlags[TypeFlags["Null"] = 8] = "Null";
+    TypeFlags[TypeFlags["Void"] = 16] = "Void";
+    TypeFlags[TypeFlags["String"] = 32] = "String";
+    TypeFlags[TypeFlags["Number"] = 64] = "Number";
+    TypeFlags[TypeFlags["BigInt"] = 128] = "BigInt";
+    TypeFlags[TypeFlags["Boolean"] = 256] = "Boolean";
+    TypeFlags[TypeFlags["ESSymbol"] = 512] = "ESSymbol";
+    TypeFlags[TypeFlags["StringLiteral"] = 1024] = "StringLiteral";
+    TypeFlags[TypeFlags["NumberLiteral"] = 2048] = "NumberLiteral";
+    TypeFlags[TypeFlags["BigIntLiteral"] = 4096] = "BigIntLiteral";
+    TypeFlags[TypeFlags["BooleanLiteral"] = 8192] = "BooleanLiteral";
+    TypeFlags[TypeFlags["UniqueESSymbol"] = 16384] = "UniqueESSymbol";
+    TypeFlags[TypeFlags["EnumLiteral"] = 32768] = "EnumLiteral";
+    TypeFlags[TypeFlags["Enum"] = 65536] = "Enum";
+    TypeFlags[TypeFlags["Never"] = 131072] = "Never";
+    TypeFlags[TypeFlags["TypeParameter"] = 262144] = "TypeParameter";
+    TypeFlags[TypeFlags["Object"] = 524288] = "Object";
+    TypeFlags[TypeFlags["Union"] = 1048576] = "Union";
+    TypeFlags[TypeFlags["Intersection"] = 2097152] = "Intersection";
+    TypeFlags[TypeFlags["Index"] = 4194304] = "Index";
+    TypeFlags[TypeFlags["IndexedAccess"] = 8388608] = "IndexedAccess";
+    TypeFlags[TypeFlags["Conditional"] = 16777216] = "Conditional";
+    TypeFlags[TypeFlags["Substitution"] = 33554432] = "Substitution";
+    TypeFlags[TypeFlags["NonPrimitive"] = 67108864] = "NonPrimitive";
+    TypeFlags[TypeFlags["TemplateLiteral"] = 134217728] = "TemplateLiteral";
+    TypeFlags[TypeFlags["StringMapping"] = 268435456] = "StringMapping";
+})(TypeFlags || (TypeFlags = {}));
diff --git a/_packages/api/test/api.bench.ts b/_packages/api/test/api.bench.ts
new file mode 100644
index 0000000000..11da280dee
--- /dev/null
+++ b/_packages/api/test/api.bench.ts
@@ -0,0 +1,303 @@
+import {
+    API,
+    type Project,
+} from "@typescript/api";
+import {
+    type FileSystem,
+    type FileSystemEntries,
+} from "@typescript/api/fs";
+import {
+    type Node,
+    type SourceFile,
+    SyntaxKind,
+} from "@typescript/ast";
+import fs, { existsSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { Bench } from "tinybench";
+import ts from "typescript";
+
+const isMain = process.argv[1] === fileURLToPath(import.meta.url);
+if (isMain) {
+    await runBenchmarks();
+}
+
+export async function runBenchmarks(singleIteration?: boolean) {
+    const repoRoot = fileURLToPath(new URL("../../../", import.meta.url).toString());
+    if (!existsSync(path.join(repoRoot, "_submodules/TypeScript/src/compiler"))) {
+        console.warn("Warning: TypeScript submodule is not cloned; skipping benchmarks.");
+        return;
+    }
+
+    const bench = new Bench({
+        name: "Sync API",
+        teardown,
+        ...singleIteration ? {
+            iterations: 1,
+            warmup: false,
+            time: 0,
+        } : undefined,
+    });
+
+    let api: API;
+    let project: Project;
+    let tsProgram: ts.Program;
+    let file: SourceFile;
+    let tsFile: ts.SourceFile;
+
+    const programIdentifierCount = (() => {
+        spawnAPI();
+        loadProject();
+        getProgramTS();
+        let count = 0;
+        file!.forEachChild(function visit(node) {
+            if (node.kind === SyntaxKind.Identifier) {
+                count++;
+            }
+            node.forEachChild(visit);
+        });
+        teardown();
+        return count;
+    })();
+
+    const SMALL_STRING = "ping";
+    const LARGE_STRING = "a".repeat(1_000_000);
+    const SMALL_UINT8_ARRAY = new Uint8Array([1, 2, 3, 4]);
+    const LARGE_UINT8_ARRAY = new Uint8Array(1_000_000);
+
+    bench
+        .add("spawn API", () => {
+            spawnAPI();
+        })
+        .add("echo (small string)", () => {
+            api.echo(SMALL_STRING);
+        }, { beforeAll: spawnAPI })
+        .add("echo (large string)", () => {
+            api.echo(LARGE_STRING);
+        }, { beforeAll: spawnAPI })
+        .add("echo (small Uint8Array)", () => {
+            api.echoBinary(SMALL_UINT8_ARRAY);
+        }, { beforeAll: spawnAPI })
+        .add("echo (large Uint8Array)", () => {
+            api.echoBinary(LARGE_UINT8_ARRAY);
+        }, { beforeAll: spawnAPI })
+        .add("load project", () => {
+            loadProject();
+        }, { beforeAll: spawnAPI })
+        .add("load project (client FS)", () => {
+            loadProject();
+        }, { beforeAll: spawnAPIHosted })
+        .add("TS - load project", () => {
+            tsCreateProgram();
+        })
+        .add("transfer debug.ts", () => {
+            getDebugTS();
+        }, { beforeAll: all(spawnAPI, loadProject) })
+        .add("transfer program.ts", () => {
+            getProgramTS();
+        }, { beforeAll: all(spawnAPI, loadProject) })
+        .add("transfer checker.ts", () => {
+            getCheckerTS();
+        }, { beforeAll: all(spawnAPI, loadProject) })
+        .add("materialize program.ts", () => {
+            file.forEachChild(function visit(node) {
+                node.forEachChild(visit);
+            });
+        }, { beforeAll: all(spawnAPI, loadProject, getProgramTS) })
+        .add("materialize checker.ts", () => {
+            file.forEachChild(function visit(node) {
+                node.forEachChild(visit);
+            });
+        }, { beforeAll: all(spawnAPI, loadProject, getCheckerTS) })
+        .add("getSymbolAtPosition - one location", () => {
+            project.getSymbolAtPosition("program.ts", 8895);
+        }, { beforeAll: all(spawnAPI, loadProject, createChecker) })
+        .add("TS - getSymbolAtPosition - one location", () => {
+            tsProgram.getTypeChecker().getSymbolAtLocation(
+                // @ts-ignore internal API
+                ts.getTokenAtPosition(tsFile, 8895),
+            );
+        }, { beforeAll: all(tsCreateProgram, tsCreateChecker, tsGetProgramTS) })
+        .add(`getSymbolAtPosition - ${programIdentifierCount} identifiers`, () => {
+            file.forEachChild(function visit(node) {
+                if (node.kind === SyntaxKind.Identifier) {
+                    project.getSymbolAtPosition("program.ts", node.pos);
+                }
+                node.forEachChild(visit);
+            });
+        }, { beforeAll: all(spawnAPI, loadProject, createChecker, getProgramTS) })
+        .add(`getSymbolAtPosition - ${programIdentifierCount} identifiers (batched)`, () => {
+            const positions: number[] = [];
+            file.forEachChild(function visit(node) {
+                if (node.kind === SyntaxKind.Identifier) {
+                    positions.push(node.pos);
+                }
+                node.forEachChild(visit);
+            });
+            project.getSymbolAtPosition("program.ts", positions);
+        }, { beforeAll: all(spawnAPI, loadProject, createChecker, getProgramTS) })
+        .add(`getSymbolAtLocation - ${programIdentifierCount} identifiers`, () => {
+            file.forEachChild(function visit(node) {
+                if (node.kind === SyntaxKind.Identifier) {
+                    project.getSymbolAtLocation(node);
+                }
+                node.forEachChild(visit);
+            });
+        }, { beforeAll: all(spawnAPI, loadProject, createChecker, getProgramTS) })
+        .add(`getSymbolAtLocation - ${programIdentifierCount} identifiers (batched)`, () => {
+            const nodes: Node[] = [];
+            file.forEachChild(function visit(node) {
+                if (node.kind === SyntaxKind.Identifier) {
+                    nodes.push(node);
+                }
+                node.forEachChild(visit);
+            });
+            project.getSymbolAtLocation(nodes);
+        }, { beforeAll: all(spawnAPI, loadProject, createChecker, getProgramTS) })
+        .add(`TS - getSymbolAtLocation - ${programIdentifierCount} identifiers`, () => {
+            const checker = tsProgram.getTypeChecker();
+            tsFile.forEachChild(function visit(node) {
+                if (node.kind === ts.SyntaxKind.Identifier) {
+                    checker.getSymbolAtLocation(node);
+                }
+                node.forEachChild(visit);
+            });
+        }, { beforeAll: all(tsCreateProgram, tsCreateChecker, tsGetProgramTS) });
+
+    await bench.run();
+    console.table(bench.table());
+
+    function spawnAPI() {
+        api = new API({
+            cwd: repoRoot,
+            tsserverPath: fileURLToPath(new URL(`../../../built/local/tsgo${process.platform === "win32" ? ".exe" : ""}`, import.meta.url).toString()),
+        });
+    }
+
+    function spawnAPIHosted() {
+        api = new API({
+            cwd: repoRoot,
+            tsserverPath: fileURLToPath(new URL(`../../../built/local/tsgo${process.platform === "win32" ? ".exe" : ""}`, import.meta.url).toString()),
+            fs: createNodeFileSystem(),
+        });
+    }
+
+    function loadProject() {
+        project = api.loadProject("_submodules/TypeScript/src/compiler/tsconfig.json");
+    }
+
+    function tsCreateProgram() {
+        const configFileName = fileURLToPath(new URL("../../../_submodules/TypeScript/src/compiler/tsconfig.json", import.meta.url).toString());
+        const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
+        const parsedCommandLine = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configFileName));
+        const host = ts.createCompilerHost(parsedCommandLine.options);
+        tsProgram = ts.createProgram({
+            rootNames: parsedCommandLine.fileNames,
+            options: parsedCommandLine.options,
+            host,
+        });
+    }
+
+    function createChecker() {
+        // checker is created lazily, for measuring symbol time in a loop
+        // we need to create it first.
+        project.getSymbolAtPosition("core.ts", 0);
+    }
+
+    function tsCreateChecker() {
+        tsProgram.getTypeChecker();
+    }
+
+    function getDebugTS() {
+        file = project.getSourceFile("debug.ts")!;
+    }
+
+    function getProgramTS() {
+        file = project.getSourceFile("program.ts")!;
+    }
+
+    function tsGetProgramTS() {
+        tsFile = tsProgram.getSourceFile(fileURLToPath(new URL("../../../_submodules/TypeScript/src/compiler/program.ts", import.meta.url).toString()))!;
+    }
+
+    function getCheckerTS() {
+        file = project.getSourceFile("checker.ts")!;
+    }
+
+    function teardown() {
+        api?.close();
+        api = undefined!;
+        project = undefined!;
+        file = undefined!;
+        tsProgram = undefined!;
+        tsFile = undefined!;
+    }
+
+    function all(...fns: (() => void)[]) {
+        return () => {
+            for (const fn of fns) {
+                fn();
+            }
+        };
+    }
+
+    function createNodeFileSystem(): FileSystem {
+        return {
+            directoryExists: directoryName => {
+                try {
+                    return fs.statSync(directoryName).isDirectory();
+                }
+                catch {
+                    return false;
+                }
+            },
+            fileExists: fileName => {
+                try {
+                    return fs.statSync(fileName).isFile();
+                }
+                catch {
+                    return false;
+                }
+            },
+            readFile: fileName => {
+                try {
+                    return fs.readFileSync(fileName, "utf8");
+                }
+                catch {
+                    return undefined;
+                }
+            },
+            getAccessibleEntries: dirName => {
+                const entries: FileSystemEntries = {
+                    files: [],
+                    directories: [],
+                };
+                for (const entry of fs.readdirSync(dirName, { withFileTypes: true })) {
+                    if (entry.isFile()) {
+                        entries.files.push(entry.name);
+                    }
+                    else if (entry.isDirectory()) {
+                        entries.directories.push(entry.name);
+                    }
+                    else if (entry.isSymbolicLink()) {
+                        const fullName = path.join(dirName, entry.name);
+                        try {
+                            const stat = fs.statSync(fullName);
+                            if (stat.isFile()) {
+                                entries.files.push(entry.name);
+                            }
+                            else if (stat.isDirectory()) {
+                                entries.directories.push(entry.name);
+                            }
+                        }
+                        catch {
+                            // Ignore errors
+                        }
+                    }
+                }
+                return entries;
+            },
+            realpath: fs.realpathSync,
+        };
+    }
+}
diff --git a/_packages/api/test/api.test.ts b/_packages/api/test/api.test.ts
new file mode 100644
index 0000000000..846b44d604
--- /dev/null
+++ b/_packages/api/test/api.test.ts
@@ -0,0 +1,164 @@
+import {
+    API,
+    SymbolFlags,
+    TypeFlags,
+} from "@typescript/api";
+import { createVirtualFileSystem } from "@typescript/api/fs";
+import {
+    cast,
+    isImportDeclaration,
+    isNamedImports,
+    isTemplateHead,
+    isTemplateMiddle,
+    isTemplateTail,
+} from "@typescript/ast";
+import assert from "node:assert";
+import {
+    describe,
+    test,
+} from "node:test";
+import { fileURLToPath } from "node:url";
+import { runBenchmarks } from "./api.bench.ts";
+
+const defaultFiles = {
+    "/tsconfig.json": "{}",
+    "/src/index.ts": `import { foo } from './foo';`,
+    "/src/foo.ts": `export const foo = 42;`,
+};
+
+describe("API", () => {
+    test("parseConfigFile", () => {
+        const api = spawnAPI();
+        const config = api.parseConfigFile("/tsconfig.json");
+        assert.deepEqual(config.fileNames, ["/src/index.ts", "/src/foo.ts"]);
+        assert.deepEqual(config.options, { configFilePath: "/tsconfig.json" });
+    });
+});
+
+describe("Project", () => {
+    test("getSymbolAtPosition", () => {
+        const api = spawnAPI();
+        const project = api.loadProject("/tsconfig.json");
+        const symbol = project.getSymbolAtPosition("/src/index.ts", 9);
+        assert.ok(symbol);
+        assert.equal(symbol.name, "foo");
+        assert.ok(symbol.flags & SymbolFlags.Alias);
+    });
+
+    test("getSymbolAtLocation", () => {
+        const api = spawnAPI();
+        const project = api.loadProject("/tsconfig.json");
+        const sourceFile = project.getSourceFile("/src/index.ts");
+        assert.ok(sourceFile);
+        const node = cast(
+            cast(sourceFile.statements[0], isImportDeclaration).importClause?.namedBindings,
+            isNamedImports,
+        ).elements[0].name;
+        assert.ok(node);
+        const symbol = project.getSymbolAtLocation(node);
+        assert.ok(symbol);
+        assert.equal(symbol.name, "foo");
+        assert.ok(symbol.flags & SymbolFlags.Alias);
+    });
+
+    test("getTypeOfSymbol", () => {
+        const api = spawnAPI();
+        const project = api.loadProject("/tsconfig.json");
+        const symbol = project.getSymbolAtPosition("/src/index.ts", 9);
+        assert.ok(symbol);
+        const type = project.getTypeOfSymbol(symbol);
+        assert.ok(type);
+        assert.ok(type.flags & TypeFlags.NumberLiteral);
+    });
+});
+
+describe("SourceFile", () => {
+    test("file properties", () => {
+        const api = spawnAPI();
+        const project = api.loadProject("/tsconfig.json");
+        const sourceFile = project.getSourceFile("/src/index.ts");
+
+        assert.ok(sourceFile);
+        assert.equal(sourceFile.text, defaultFiles["/src/index.ts"]);
+        assert.equal(sourceFile.fileName, "/src/index.ts");
+    });
+
+    test("extended data", () => {
+        const api = spawnAPI();
+        const project = api.loadProject("/tsconfig.json");
+        const sourceFile = project.getSourceFile("/src/index.ts");
+
+        assert.ok(sourceFile);
+        let nodeCount = 1;
+        sourceFile.forEachChild(function visit(node) {
+            if (isTemplateHead(node)) {
+                assert.equal(node.text, "head ");
+                assert.equal(node.rawText, "head ");
+                assert.equal(node.templateFlags, 0);
+            }
+            else if (isTemplateMiddle(node)) {
+                assert.equal(node.text, "middle");
+                assert.equal(node.rawText, "middle");
+                assert.equal(node.templateFlags, 0);
+            }
+            else if (isTemplateTail(node)) {
+                assert.equal(node.text, " tail");
+                assert.equal(node.rawText, " tail");
+                assert.equal(node.templateFlags, 0);
+            }
+            nodeCount++;
+            node.forEachChild(visit);
+        });
+        assert.equal(nodeCount, 7);
+    });
+});
+
+test("Object equality", () => {
+    const api = spawnAPI();
+    const project = api.loadProject("/tsconfig.json");
+    assert.strictEqual(project, api.loadProject("/tsconfig.json"));
+    assert.strictEqual(
+        project.getSymbolAtPosition("/src/index.ts", 9),
+        project.getSymbolAtPosition("/src/index.ts", 10),
+    );
+});
+
+test("Dispose", () => {
+    const api = spawnAPI();
+    const project = api.loadProject("/tsconfig.json");
+    const symbol = project.getSymbolAtPosition("/src/index.ts", 9);
+    assert.ok(symbol);
+    assert.ok(symbol.isDisposed() === false);
+    symbol.dispose();
+    assert.ok(symbol.isDisposed() === true);
+    assert.throws(() => {
+        project.getTypeOfSymbol(symbol);
+    }, {
+        name: "Error",
+        message: "Symbol is disposed",
+    });
+
+    const symbol2 = project.getSymbolAtPosition("/src/index.ts", 9);
+    assert.ok(symbol2);
+    assert.notStrictEqual(symbol, symbol2);
+    // @ts-ignore private API
+    api.client.request("release", symbol2.id);
+    assert.throws(() => {
+        project.getTypeOfSymbol(symbol2);
+    }, {
+        name: "Error",
+        message: `symbol "${symbol.id}" not found`,
+    });
+});
+
+test("Benchmarks", async () => {
+    await runBenchmarks(/*singleIteration*/ true);
+});
+
+function spawnAPI(files: Record<string, string> = defaultFiles) {
+    return new API({
+        cwd: fileURLToPath(new URL("../../../", import.meta.url).toString()),
+        tsserverPath: fileURLToPath(new URL(`../../../built/local/tsgo${process.platform === "win32" ? ".exe" : ""}`, import.meta.url).toString()),
+        fs: createVirtualFileSystem(files),
+    });
+}
diff --git a/_packages/api/test/tsconfig.json b/_packages/api/test/tsconfig.json
new file mode 100644
index 0000000000..b1a688b3b8
--- /dev/null
+++ b/_packages/api/test/tsconfig.json
@@ -0,0 +1,6 @@
+{
+    "extends": "../tsconfig.dev.json",
+    "references": [
+        { "path": ".." }
+    ]
+}
diff --git a/_packages/api/tsconfig.base.json b/_packages/api/tsconfig.base.json
new file mode 100644
index 0000000000..c99f883b56
--- /dev/null
+++ b/_packages/api/tsconfig.base.json
@@ -0,0 +1,9 @@
+{
+    "compilerOptions": {
+        "strict": true,
+        "composite": true,
+        "forceConsistentCasingInFileNames": true,
+        "rewriteRelativeImportExtensions": true,
+        "verbatimModuleSyntax": true
+    }
+}
diff --git a/_packages/api/tsconfig.dev.json b/_packages/api/tsconfig.dev.json
new file mode 100644
index 0000000000..d3b396340e
--- /dev/null
+++ b/_packages/api/tsconfig.dev.json
@@ -0,0 +1,10 @@
+{
+    "extends": "./tsconfig.base.json",
+    "compilerOptions": {
+        "module": "nodenext",
+        "strict": true,
+        "noEmit": true,
+        "verbatimModuleSyntax": true,
+        "allowImportingTsExtensions": true
+    }
+}
diff --git a/_packages/api/tsconfig.json b/_packages/api/tsconfig.json
new file mode 100644
index 0000000000..b3b68a0719
--- /dev/null
+++ b/_packages/api/tsconfig.json
@@ -0,0 +1,16 @@
+{
+    "extends": "./tsconfig.base.json",
+    "compilerOptions": {
+        "module": "node16",
+        "isolatedDeclarations": true,
+        "sourceMap": true,
+        "declaration": true,
+        "declarationMap": true,
+        "rootDir": "src",
+        "outDir": "dist"
+    },
+    "include": ["src"],
+    "references": [
+        { "path": "../ast" }
+    ]
+}
diff --git a/_packages/ast/package.json b/_packages/ast/package.json
new file mode 100644
index 0000000000..0bddc1cdba
--- /dev/null
+++ b/_packages/ast/package.json
@@ -0,0 +1,39 @@
+{
+    "private": true,
+    "name": "@typescript/ast",
+    "version": "1.0.0",
+    "description": "TypeScript AST definitions",
+    "type": "module",
+    "imports": {
+        "#syntaxKind": {
+            "@typescript/source": {
+                "types": "./src/syntaxKind.enum.ts",
+                "default": "./src/syntaxKind.ts"
+            },
+            "types": "./dist/syntaxKind.enum.d.ts",
+            "default": "./dist/syntaxKind.js"
+        },
+        "#tokenFlags": {
+            "@typescript/source": {
+                "types": "./src/tokenFlags.enum.ts",
+                "default": "./src/tokenFlags.ts"
+            },
+            "types": "./dist/tokenFlags.enum.d.ts",
+            "default": "./dist/tokenFlags.js"
+        }
+    },
+    "exports": {
+        ".": {
+            "@typescript/source": "./src/index.ts",
+            "default": "./dist/index.js"
+        },
+        "./is": {
+            "@typescript/source": "./src/is.ts",
+            "default": "./dist/is.js"
+        },
+        "./utils": {
+            "@typescript/source": "./src/utils.ts",
+            "default": "./dist/utils.js"
+        }
+    }
+}
diff --git a/_packages/ast/src/index.ts b/_packages/ast/src/index.ts
new file mode 100644
index 0000000000..74af64f5dd
--- /dev/null
+++ b/_packages/ast/src/index.ts
@@ -0,0 +1,4 @@
+export { SyntaxKind } from "#syntaxKind";
+export * from "./is.ts";
+export * from "./nodes.ts";
+export * from "./utils.ts";
diff --git a/_packages/ast/src/is.ts b/_packages/ast/src/is.ts
new file mode 100644
index 0000000000..d9563838f5
--- /dev/null
+++ b/_packages/ast/src/is.ts
@@ -0,0 +1,1115 @@
+import { SyntaxKind } from "#syntaxKind";
+import type {
+    AbstractKeyword,
+    AccessorKeyword,
+    ArrayBindingPattern,
+    ArrayLiteralExpression,
+    ArrayTypeNode,
+    ArrowFunction,
+    AsExpression,
+    AssertsKeyword,
+    AsteriskToken,
+    AsyncKeyword,
+    AwaitExpression,
+    AwaitKeyword,
+    BigIntLiteral,
+    BinaryExpression,
+    BindingElement,
+    Block,
+    BreakStatement,
+    CallExpression,
+    CallSignatureDeclaration,
+    CaseBlock,
+    CaseClause,
+    CaseKeyword,
+    CatchClause,
+    ClassDeclaration,
+    ClassExpression,
+    ClassStaticBlockDeclaration,
+    ColonToken,
+    CommaListExpression,
+    ComputedPropertyName,
+    ConditionalExpression,
+    ConditionalTypeNode,
+    ConstructorDeclaration,
+    ConstructorTypeNode,
+    ConstructSignatureDeclaration,
+    ContinueStatement,
+    DebuggerStatement,
+    Decorator,
+    DefaultClause,
+    DefaultKeyword,
+    DeleteExpression,
+    DoStatement,
+    DotDotDotToken,
+    ElementAccessExpression,
+    EmptyStatement,
+    EnumDeclaration,
+    EnumMember,
+    EqualsGreaterThanToken,
+    ExclamationToken,
+    ExportAssignment,
+    ExportDeclaration,
+    ExportKeyword,
+    ExportSpecifier,
+    ExpressionStatement,
+    ExpressionWithTypeArguments,
+    ExternalModuleReference,
+    ForInStatement,
+    ForOfStatement,
+    ForStatement,
+    FunctionDeclaration,
+    FunctionExpression,
+    FunctionTypeNode,
+    GetAccessorDeclaration,
+    HeritageClause,
+    Identifier,
+    IfStatement,
+    ImportAttribute,
+    ImportAttributes,
+    ImportClause,
+    ImportDeclaration,
+    ImportEqualsDeclaration,
+    ImportExpression,
+    ImportSpecifier,
+    ImportTypeNode,
+    IndexedAccessTypeNode,
+    IndexSignatureDeclaration,
+    InferTypeNode,
+    InterfaceDeclaration,
+    IntersectionTypeNode,
+    JSDoc,
+    JSDocAllType,
+    JSDocAugmentsTag,
+    JSDocCallbackTag,
+    JSDocDeprecatedTag,
+    JSDocImplementsTag,
+    JSDocImportTag,
+    JSDocLink,
+    JSDocLinkCode,
+    JSDocLinkPlain,
+    JSDocMemberName,
+    JSDocNameReference,
+    JSDocNonNullableType,
+    JSDocNullableType,
+    JSDocOptionalType,
+    JSDocOverloadTag,
+    JSDocOverrideTag,
+    JSDocParameterTag,
+    JSDocPrivateTag,
+    JSDocPropertyTag,
+    JSDocProtectedTag,
+    JSDocPublicTag,
+    JSDocReadonlyTag,
+    JSDocReturnTag,
+    JSDocSatisfiesTag,
+    JSDocSeeTag,
+    JSDocSignature,
+    JSDocTemplateTag,
+    JSDocThisTag,
+    JSDocTypedefTag,
+    JSDocTypeExpression,
+    JSDocTypeLiteral,
+    JSDocTypeTag,
+    JSDocUnknownTag,
+    JSDocVariadicType,
+    JsxAttribute,
+    JsxAttributes,
+    JsxClosingElement,
+    JsxClosingFragment,
+    JsxElement,
+    JsxExpression,
+    JsxFragment,
+    JsxNamespacedName,
+    JsxOpeningElement,
+    JsxOpeningFragment,
+    JsxSelfClosingElement,
+    JsxSpreadAttribute,
+    JsxText,
+    LabeledStatement,
+    LiteralTypeNode,
+    MappedTypeNode,
+    MetaProperty,
+    MethodDeclaration,
+    MethodSignature,
+    MinusToken,
+    ModuleBlock,
+    ModuleDeclaration,
+    ModuleExportName,
+    NamedExports,
+    NamedImports,
+    NamedTupleMember,
+    NamespaceExport,
+    NamespaceExportDeclaration,
+    NamespaceImport,
+    NewExpression,
+    Node,
+    NonNullExpression,
+    NoSubstitutionTemplateLiteral,
+    NumericLiteral,
+    ObjectBindingPattern,
+    ObjectLiteralExpression,
+    OmittedExpression,
+    OptionalTypeNode,
+    OverrideKeyword,
+    ParameterDeclaration,
+    ParenthesizedExpression,
+    ParenthesizedTypeNode,
+    PartiallyEmittedExpression,
+    PlusToken,
+    PostfixUnaryExpression,
+    PrefixUnaryExpression,
+    PrivateIdentifier,
+    PropertyAccessExpression,
+    PropertyAssignment,
+    PropertyDeclaration,
+    PropertySignature,
+    QualifiedName,
+    QuestionDotToken,
+    QuestionToken,
+    ReadonlyKeyword,
+    RegularExpressionLiteral,
+    RestTypeNode,
+    ReturnStatement,
+    SatisfiesExpression,
+    SemicolonClassElement,
+    SetAccessorDeclaration,
+    ShorthandPropertyAssignment,
+    SourceFile,
+    SpreadAssignment,
+    SpreadElement,
+    StaticKeyword,
+    StringLiteral,
+    SuperExpression,
+    SwitchStatement,
+    TaggedTemplateExpression,
+    TemplateExpression,
+    TemplateHead,
+    TemplateLiteralTypeNode,
+    TemplateLiteralTypeSpan,
+    TemplateMiddle,
+    TemplateSpan,
+    TemplateTail,
+    ThisTypeNode,
+    ThrowStatement,
+    Token,
+    TryStatement,
+    TupleTypeNode,
+    TypeAliasDeclaration,
+    TypeAssertion,
+    TypeLiteralNode,
+    TypeOfExpression,
+    TypeOperatorNode,
+    TypeParameterDeclaration,
+    TypePredicateNode,
+    TypeQueryNode,
+    TypeReferenceNode,
+    UnionTypeNode,
+    VariableDeclaration,
+    VariableDeclarationList,
+    VariableStatement,
+    VoidExpression,
+    WhileStatement,
+    WithStatement,
+    YieldExpression,
+} from "./nodes.ts";
+
+// Literals
+
+export function isNumericLiteral(node: Node): node is NumericLiteral {
+    return node.kind === SyntaxKind.NumericLiteral;
+}
+
+export function isBigIntLiteral(node: Node): node is BigIntLiteral {
+    return node.kind === SyntaxKind.BigIntLiteral;
+}
+
+export function isStringLiteral(node: Node): node is StringLiteral {
+    return node.kind === SyntaxKind.StringLiteral;
+}
+
+export function isJsxText(node: Node): node is JsxText {
+    return node.kind === SyntaxKind.JsxText;
+}
+
+export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral {
+    return node.kind === SyntaxKind.RegularExpressionLiteral;
+}
+
+export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral {
+    return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
+}
+
+// Pseudo-literals
+
+export function isTemplateHead(node: Node): node is TemplateHead {
+    return node.kind === SyntaxKind.TemplateHead;
+}
+
+export function isTemplateMiddle(node: Node): node is TemplateMiddle {
+    return node.kind === SyntaxKind.TemplateMiddle;
+}
+
+export function isTemplateTail(node: Node): node is TemplateTail {
+    return node.kind === SyntaxKind.TemplateTail;
+}
+
+// Punctuation
+
+export function isDotDotDotToken(node: Node): node is DotDotDotToken {
+    return node.kind === SyntaxKind.DotDotDotToken;
+}
+
+/** @internal */
+export function isCommaToken(node: Node): node is Token<SyntaxKind.CommaToken> {
+    return node.kind === SyntaxKind.CommaToken;
+}
+
+export function isPlusToken(node: Node): node is PlusToken {
+    return node.kind === SyntaxKind.PlusToken;
+}
+
+export function isMinusToken(node: Node): node is MinusToken {
+    return node.kind === SyntaxKind.MinusToken;
+}
+
+export function isAsteriskToken(node: Node): node is AsteriskToken {
+    return node.kind === SyntaxKind.AsteriskToken;
+}
+
+export function isExclamationToken(node: Node): node is ExclamationToken {
+    return node.kind === SyntaxKind.ExclamationToken;
+}
+
+export function isQuestionToken(node: Node): node is QuestionToken {
+    return node.kind === SyntaxKind.QuestionToken;
+}
+
+export function isColonToken(node: Node): node is ColonToken {
+    return node.kind === SyntaxKind.ColonToken;
+}
+
+export function isQuestionDotToken(node: Node): node is QuestionDotToken {
+    return node.kind === SyntaxKind.QuestionDotToken;
+}
+
+export function isEqualsGreaterThanToken(node: Node): node is EqualsGreaterThanToken {
+    return node.kind === SyntaxKind.EqualsGreaterThanToken;
+}
+
+// Identifiers
+
+export function isIdentifier(node: Node): node is Identifier {
+    return node.kind === SyntaxKind.Identifier;
+}
+
+export function isPrivateIdentifier(node: Node): node is PrivateIdentifier {
+    return node.kind === SyntaxKind.PrivateIdentifier;
+}
+
+// Reserved Words
+
+/** @internal */
+export function isExportModifier(node: Node): node is ExportKeyword {
+    return node.kind === SyntaxKind.ExportKeyword;
+}
+
+/** @internal */
+export function isDefaultModifier(node: Node): node is DefaultKeyword {
+    return node.kind === SyntaxKind.DefaultKeyword;
+}
+
+/** @internal */
+export function isAsyncModifier(node: Node): node is AsyncKeyword {
+    return node.kind === SyntaxKind.AsyncKeyword;
+}
+
+export function isAssertsKeyword(node: Node): node is AssertsKeyword {
+    return node.kind === SyntaxKind.AssertsKeyword;
+}
+
+export function isAwaitKeyword(node: Node): node is AwaitKeyword {
+    return node.kind === SyntaxKind.AwaitKeyword;
+}
+
+/** @internal */
+export function isReadonlyKeyword(node: Node): node is ReadonlyKeyword {
+    return node.kind === SyntaxKind.ReadonlyKeyword;
+}
+
+/** @internal */
+export function isStaticModifier(node: Node): node is StaticKeyword {
+    return node.kind === SyntaxKind.StaticKeyword;
+}
+
+/** @internal */
+export function isAbstractModifier(node: Node): node is AbstractKeyword {
+    return node.kind === SyntaxKind.AbstractKeyword;
+}
+
+/** @internal */
+export function isOverrideModifier(node: Node): node is OverrideKeyword {
+    return node.kind === SyntaxKind.OverrideKeyword;
+}
+
+/** @internal */
+export function isAccessorModifier(node: Node): node is AccessorKeyword {
+    return node.kind === SyntaxKind.AccessorKeyword;
+}
+
+/** @internal */
+export function isSuperKeyword(node: Node): node is SuperExpression {
+    return node.kind === SyntaxKind.SuperKeyword;
+}
+
+/** @internal */
+export function isImportKeyword(node: Node): node is ImportExpression {
+    return node.kind === SyntaxKind.ImportKeyword;
+}
+
+/** @internal */
+export function isCaseKeyword(node: Node): node is CaseKeyword {
+    return node.kind === SyntaxKind.CaseKeyword;
+}
+
+// Names
+
+export function isQualifiedName(node: Node): node is QualifiedName {
+    return node.kind === SyntaxKind.QualifiedName;
+}
+
+export function isComputedPropertyName(node: Node): node is ComputedPropertyName {
+    return node.kind === SyntaxKind.ComputedPropertyName;
+}
+
+// Signature elements
+
+export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration {
+    return node.kind === SyntaxKind.TypeParameter;
+}
+
+// TODO(rbuckton): Rename to 'isParameterDeclaration'
+export function isParameter(node: Node): node is ParameterDeclaration {
+    return node.kind === SyntaxKind.Parameter;
+}
+
+export function isDecorator(node: Node): node is Decorator {
+    return node.kind === SyntaxKind.Decorator;
+}
+
+// TypeMember
+
+export function isPropertySignature(node: Node): node is PropertySignature {
+    return node.kind === SyntaxKind.PropertySignature;
+}
+
+export function isPropertyDeclaration(node: Node): node is PropertyDeclaration {
+    return node.kind === SyntaxKind.PropertyDeclaration;
+}
+
+export function isMethodSignature(node: Node): node is MethodSignature {
+    return node.kind === SyntaxKind.MethodSignature;
+}
+
+export function isMethodDeclaration(node: Node): node is MethodDeclaration {
+    return node.kind === SyntaxKind.MethodDeclaration;
+}
+
+export function isClassStaticBlockDeclaration(node: Node): node is ClassStaticBlockDeclaration {
+    return node.kind === SyntaxKind.ClassStaticBlockDeclaration;
+}
+
+export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration {
+    return node.kind === SyntaxKind.Constructor;
+}
+
+export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration {
+    return node.kind === SyntaxKind.GetAccessor;
+}
+
+export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration {
+    return node.kind === SyntaxKind.SetAccessor;
+}
+
+export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration {
+    return node.kind === SyntaxKind.CallSignature;
+}
+
+export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration {
+    return node.kind === SyntaxKind.ConstructSignature;
+}
+
+export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration {
+    return node.kind === SyntaxKind.IndexSignature;
+}
+
+// Type
+
+export function isTypePredicateNode(node: Node): node is TypePredicateNode {
+    return node.kind === SyntaxKind.TypePredicate;
+}
+
+export function isTypeReferenceNode(node: Node): node is TypeReferenceNode {
+    return node.kind === SyntaxKind.TypeReference;
+}
+
+export function isFunctionTypeNode(node: Node): node is FunctionTypeNode {
+    return node.kind === SyntaxKind.FunctionType;
+}
+
+export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode {
+    return node.kind === SyntaxKind.ConstructorType;
+}
+
+export function isTypeQueryNode(node: Node): node is TypeQueryNode {
+    return node.kind === SyntaxKind.TypeQuery;
+}
+
+export function isTypeLiteralNode(node: Node): node is TypeLiteralNode {
+    return node.kind === SyntaxKind.TypeLiteral;
+}
+
+export function isArrayTypeNode(node: Node): node is ArrayTypeNode {
+    return node.kind === SyntaxKind.ArrayType;
+}
+
+export function isTupleTypeNode(node: Node): node is TupleTypeNode {
+    return node.kind === SyntaxKind.TupleType;
+}
+
+export function isNamedTupleMember(node: Node): node is NamedTupleMember {
+    return node.kind === SyntaxKind.NamedTupleMember;
+}
+
+export function isOptionalTypeNode(node: Node): node is OptionalTypeNode {
+    return node.kind === SyntaxKind.OptionalType;
+}
+
+export function isRestTypeNode(node: Node): node is RestTypeNode {
+    return node.kind === SyntaxKind.RestType;
+}
+
+export function isUnionTypeNode(node: Node): node is UnionTypeNode {
+    return node.kind === SyntaxKind.UnionType;
+}
+
+export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode {
+    return node.kind === SyntaxKind.IntersectionType;
+}
+
+export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode {
+    return node.kind === SyntaxKind.ConditionalType;
+}
+
+export function isInferTypeNode(node: Node): node is InferTypeNode {
+    return node.kind === SyntaxKind.InferType;
+}
+
+export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
+    return node.kind === SyntaxKind.ParenthesizedType;
+}
+
+export function isThisTypeNode(node: Node): node is ThisTypeNode {
+    return node.kind === SyntaxKind.ThisType;
+}
+
+export function isTypeOperatorNode(node: Node): node is TypeOperatorNode {
+    return node.kind === SyntaxKind.TypeOperator;
+}
+
+export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode {
+    return node.kind === SyntaxKind.IndexedAccessType;
+}
+
+export function isMappedTypeNode(node: Node): node is MappedTypeNode {
+    return node.kind === SyntaxKind.MappedType;
+}
+
+export function isLiteralTypeNode(node: Node): node is LiteralTypeNode {
+    return node.kind === SyntaxKind.LiteralType;
+}
+
+export function isImportTypeNode(node: Node): node is ImportTypeNode {
+    return node.kind === SyntaxKind.ImportType;
+}
+
+export function isTemplateLiteralTypeSpan(node: Node): node is TemplateLiteralTypeSpan {
+    return node.kind === SyntaxKind.TemplateLiteralTypeSpan;
+}
+
+export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTypeNode {
+    return node.kind === SyntaxKind.TemplateLiteralType;
+}
+
+// Binding patterns
+
+export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern {
+    return node.kind === SyntaxKind.ObjectBindingPattern;
+}
+
+export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern {
+    return node.kind === SyntaxKind.ArrayBindingPattern;
+}
+
+export function isBindingElement(node: Node): node is BindingElement {
+    return node.kind === SyntaxKind.BindingElement;
+}
+
+// Expression
+
+export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression {
+    return node.kind === SyntaxKind.ArrayLiteralExpression;
+}
+
+export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression {
+    return node.kind === SyntaxKind.ObjectLiteralExpression;
+}
+
+export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression {
+    return node.kind === SyntaxKind.PropertyAccessExpression;
+}
+
+export function isElementAccessExpression(node: Node): node is ElementAccessExpression {
+    return node.kind === SyntaxKind.ElementAccessExpression;
+}
+
+export function isCallExpression(node: Node): node is CallExpression {
+    return node.kind === SyntaxKind.CallExpression;
+}
+
+export function isNewExpression(node: Node): node is NewExpression {
+    return node.kind === SyntaxKind.NewExpression;
+}
+
+export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression {
+    return node.kind === SyntaxKind.TaggedTemplateExpression;
+}
+
+export function isTypeAssertionExpression(node: Node): node is TypeAssertion {
+    return node.kind === SyntaxKind.TypeAssertionExpression;
+}
+
+export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression {
+    return node.kind === SyntaxKind.ParenthesizedExpression;
+}
+
+export function isFunctionExpression(node: Node): node is FunctionExpression {
+    return node.kind === SyntaxKind.FunctionExpression;
+}
+
+export function isArrowFunction(node: Node): node is ArrowFunction {
+    return node.kind === SyntaxKind.ArrowFunction;
+}
+
+export function isDeleteExpression(node: Node): node is DeleteExpression {
+    return node.kind === SyntaxKind.DeleteExpression;
+}
+
+export function isTypeOfExpression(node: Node): node is TypeOfExpression {
+    return node.kind === SyntaxKind.TypeOfExpression;
+}
+
+export function isVoidExpression(node: Node): node is VoidExpression {
+    return node.kind === SyntaxKind.VoidExpression;
+}
+
+export function isAwaitExpression(node: Node): node is AwaitExpression {
+    return node.kind === SyntaxKind.AwaitExpression;
+}
+
+export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression {
+    return node.kind === SyntaxKind.PrefixUnaryExpression;
+}
+
+export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression {
+    return node.kind === SyntaxKind.PostfixUnaryExpression;
+}
+
+export function isBinaryExpression(node: Node): node is BinaryExpression {
+    return node.kind === SyntaxKind.BinaryExpression;
+}
+
+export function isConditionalExpression(node: Node): node is ConditionalExpression {
+    return node.kind === SyntaxKind.ConditionalExpression;
+}
+
+export function isTemplateExpression(node: Node): node is TemplateExpression {
+    return node.kind === SyntaxKind.TemplateExpression;
+}
+
+export function isYieldExpression(node: Node): node is YieldExpression {
+    return node.kind === SyntaxKind.YieldExpression;
+}
+
+export function isSpreadElement(node: Node): node is SpreadElement {
+    return node.kind === SyntaxKind.SpreadElement;
+}
+
+export function isClassExpression(node: Node): node is ClassExpression {
+    return node.kind === SyntaxKind.ClassExpression;
+}
+
+export function isOmittedExpression(node: Node): node is OmittedExpression {
+    return node.kind === SyntaxKind.OmittedExpression;
+}
+
+export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments {
+    return node.kind === SyntaxKind.ExpressionWithTypeArguments;
+}
+
+export function isAsExpression(node: Node): node is AsExpression {
+    return node.kind === SyntaxKind.AsExpression;
+}
+
+export function isSatisfiesExpression(node: Node): node is SatisfiesExpression {
+    return node.kind === SyntaxKind.SatisfiesExpression;
+}
+
+export function isNonNullExpression(node: Node): node is NonNullExpression {
+    return node.kind === SyntaxKind.NonNullExpression;
+}
+
+export function isMetaProperty(node: Node): node is MetaProperty {
+    return node.kind === SyntaxKind.MetaProperty;
+}
+
+export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression {
+    return node.kind === SyntaxKind.PartiallyEmittedExpression;
+}
+
+export function isCommaListExpression(node: Node): node is CommaListExpression {
+    return node.kind === SyntaxKind.CommaListExpression;
+}
+
+// Misc
+
+export function isTemplateSpan(node: Node): node is TemplateSpan {
+    return node.kind === SyntaxKind.TemplateSpan;
+}
+
+export function isSemicolonClassElement(node: Node): node is SemicolonClassElement {
+    return node.kind === SyntaxKind.SemicolonClassElement;
+}
+
+// Elements
+
+export function isBlock(node: Node): node is Block {
+    return node.kind === SyntaxKind.Block;
+}
+
+export function isVariableStatement(node: Node): node is VariableStatement {
+    return node.kind === SyntaxKind.VariableStatement;
+}
+
+export function isEmptyStatement(node: Node): node is EmptyStatement {
+    return node.kind === SyntaxKind.EmptyStatement;
+}
+
+export function isExpressionStatement(node: Node): node is ExpressionStatement {
+    return node.kind === SyntaxKind.ExpressionStatement;
+}
+
+export function isIfStatement(node: Node): node is IfStatement {
+    return node.kind === SyntaxKind.IfStatement;
+}
+
+export function isDoStatement(node: Node): node is DoStatement {
+    return node.kind === SyntaxKind.DoStatement;
+}
+
+export function isWhileStatement(node: Node): node is WhileStatement {
+    return node.kind === SyntaxKind.WhileStatement;
+}
+
+export function isForStatement(node: Node): node is ForStatement {
+    return node.kind === SyntaxKind.ForStatement;
+}
+
+export function isForInStatement(node: Node): node is ForInStatement {
+    return node.kind === SyntaxKind.ForInStatement;
+}
+
+export function isForOfStatement(node: Node): node is ForOfStatement {
+    return node.kind === SyntaxKind.ForOfStatement;
+}
+
+export function isContinueStatement(node: Node): node is ContinueStatement {
+    return node.kind === SyntaxKind.ContinueStatement;
+}
+
+export function isBreakStatement(node: Node): node is BreakStatement {
+    return node.kind === SyntaxKind.BreakStatement;
+}
+
+export function isReturnStatement(node: Node): node is ReturnStatement {
+    return node.kind === SyntaxKind.ReturnStatement;
+}
+
+export function isWithStatement(node: Node): node is WithStatement {
+    return node.kind === SyntaxKind.WithStatement;
+}
+
+export function isSwitchStatement(node: Node): node is SwitchStatement {
+    return node.kind === SyntaxKind.SwitchStatement;
+}
+
+export function isLabeledStatement(node: Node): node is LabeledStatement {
+    return node.kind === SyntaxKind.LabeledStatement;
+}
+
+export function isThrowStatement(node: Node): node is ThrowStatement {
+    return node.kind === SyntaxKind.ThrowStatement;
+}
+
+export function isTryStatement(node: Node): node is TryStatement {
+    return node.kind === SyntaxKind.TryStatement;
+}
+
+export function isDebuggerStatement(node: Node): node is DebuggerStatement {
+    return node.kind === SyntaxKind.DebuggerStatement;
+}
+
+export function isVariableDeclaration(node: Node): node is VariableDeclaration {
+    return node.kind === SyntaxKind.VariableDeclaration;
+}
+
+export function isVariableDeclarationList(node: Node): node is VariableDeclarationList {
+    return node.kind === SyntaxKind.VariableDeclarationList;
+}
+
+export function isFunctionDeclaration(node: Node): node is FunctionDeclaration {
+    return node.kind === SyntaxKind.FunctionDeclaration;
+}
+
+export function isClassDeclaration(node: Node): node is ClassDeclaration {
+    return node.kind === SyntaxKind.ClassDeclaration;
+}
+
+export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration {
+    return node.kind === SyntaxKind.InterfaceDeclaration;
+}
+
+export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration {
+    return node.kind === SyntaxKind.TypeAliasDeclaration;
+}
+
+export function isEnumDeclaration(node: Node): node is EnumDeclaration {
+    return node.kind === SyntaxKind.EnumDeclaration;
+}
+
+export function isModuleDeclaration(node: Node): node is ModuleDeclaration {
+    return node.kind === SyntaxKind.ModuleDeclaration;
+}
+
+export function isModuleBlock(node: Node): node is ModuleBlock {
+    return node.kind === SyntaxKind.ModuleBlock;
+}
+
+export function isCaseBlock(node: Node): node is CaseBlock {
+    return node.kind === SyntaxKind.CaseBlock;
+}
+
+export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration {
+    return node.kind === SyntaxKind.NamespaceExportDeclaration;
+}
+
+export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
+    return node.kind === SyntaxKind.ImportEqualsDeclaration;
+}
+
+export function isImportDeclaration(node: Node): node is ImportDeclaration {
+    return node.kind === SyntaxKind.ImportDeclaration;
+}
+
+export function isImportClause(node: Node): node is ImportClause {
+    return node.kind === SyntaxKind.ImportClause;
+}
+
+export function isImportAttributes(node: Node): node is ImportAttributes {
+    return node.kind === SyntaxKind.ImportAttributes;
+}
+
+export function isImportAttribute(node: Node): node is ImportAttribute {
+    return node.kind === SyntaxKind.ImportAttribute;
+}
+
+export function isNamespaceImport(node: Node): node is NamespaceImport {
+    return node.kind === SyntaxKind.NamespaceImport;
+}
+
+export function isNamespaceExport(node: Node): node is NamespaceExport {
+    return node.kind === SyntaxKind.NamespaceExport;
+}
+
+export function isNamedImports(node: Node): node is NamedImports {
+    return node.kind === SyntaxKind.NamedImports;
+}
+
+export function isImportSpecifier(node: Node): node is ImportSpecifier {
+    return node.kind === SyntaxKind.ImportSpecifier;
+}
+
+export function isExportAssignment(node: Node): node is ExportAssignment {
+    return node.kind === SyntaxKind.ExportAssignment;
+}
+
+export function isExportDeclaration(node: Node): node is ExportDeclaration {
+    return node.kind === SyntaxKind.ExportDeclaration;
+}
+
+export function isNamedExports(node: Node): node is NamedExports {
+    return node.kind === SyntaxKind.NamedExports;
+}
+
+export function isExportSpecifier(node: Node): node is ExportSpecifier {
+    return node.kind === SyntaxKind.ExportSpecifier;
+}
+
+export function isModuleExportName(node: Node): node is ModuleExportName {
+    return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral;
+}
+
+// Module References
+
+export function isExternalModuleReference(node: Node): node is ExternalModuleReference {
+    return node.kind === SyntaxKind.ExternalModuleReference;
+}
+
+// JSX
+
+export function isJsxElement(node: Node): node is JsxElement {
+    return node.kind === SyntaxKind.JsxElement;
+}
+
+export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement {
+    return node.kind === SyntaxKind.JsxSelfClosingElement;
+}
+
+export function isJsxOpeningElement(node: Node): node is JsxOpeningElement {
+    return node.kind === SyntaxKind.JsxOpeningElement;
+}
+
+export function isJsxClosingElement(node: Node): node is JsxClosingElement {
+    return node.kind === SyntaxKind.JsxClosingElement;
+}
+
+export function isJsxFragment(node: Node): node is JsxFragment {
+    return node.kind === SyntaxKind.JsxFragment;
+}
+
+export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment {
+    return node.kind === SyntaxKind.JsxOpeningFragment;
+}
+
+export function isJsxClosingFragment(node: Node): node is JsxClosingFragment {
+    return node.kind === SyntaxKind.JsxClosingFragment;
+}
+
+export function isJsxAttribute(node: Node): node is JsxAttribute {
+    return node.kind === SyntaxKind.JsxAttribute;
+}
+
+export function isJsxAttributes(node: Node): node is JsxAttributes {
+    return node.kind === SyntaxKind.JsxAttributes;
+}
+
+export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute {
+    return node.kind === SyntaxKind.JsxSpreadAttribute;
+}
+
+export function isJsxExpression(node: Node): node is JsxExpression {
+    return node.kind === SyntaxKind.JsxExpression;
+}
+
+export function isJsxNamespacedName(node: Node): node is JsxNamespacedName {
+    return node.kind === SyntaxKind.JsxNamespacedName;
+}
+
+// Clauses
+
+export function isCaseClause(node: Node): node is CaseClause {
+    return node.kind === SyntaxKind.CaseClause;
+}
+
+export function isDefaultClause(node: Node): node is DefaultClause {
+    return node.kind === SyntaxKind.DefaultClause;
+}
+
+export function isHeritageClause(node: Node): node is HeritageClause {
+    return node.kind === SyntaxKind.HeritageClause;
+}
+
+export function isCatchClause(node: Node): node is CatchClause {
+    return node.kind === SyntaxKind.CatchClause;
+}
+
+// Property assignments
+
+export function isPropertyAssignment(node: Node): node is PropertyAssignment {
+    return node.kind === SyntaxKind.PropertyAssignment;
+}
+
+export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment {
+    return node.kind === SyntaxKind.ShorthandPropertyAssignment;
+}
+
+export function isSpreadAssignment(node: Node): node is SpreadAssignment {
+    return node.kind === SyntaxKind.SpreadAssignment;
+}
+
+// Enum
+
+export function isEnumMember(node: Node): node is EnumMember {
+    return node.kind === SyntaxKind.EnumMember;
+}
+
+// Top-level nodes
+export function isSourceFile(node: Node): node is SourceFile {
+    return node.kind === SyntaxKind.SourceFile;
+}
+
+// TODO(rbuckton): isInputFiles
+
+// JSDoc Elements
+
+export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression {
+    return node.kind === SyntaxKind.JSDocTypeExpression;
+}
+
+export function isJSDocNameReference(node: Node): node is JSDocNameReference {
+    return node.kind === SyntaxKind.JSDocNameReference;
+}
+
+export function isJSDocMemberName(node: Node): node is JSDocMemberName {
+    return node.kind === SyntaxKind.JSDocMemberName;
+}
+
+export function isJSDocLink(node: Node): node is JSDocLink {
+    return node.kind === SyntaxKind.JSDocLink;
+}
+
+export function isJSDocLinkCode(node: Node): node is JSDocLinkCode {
+    return node.kind === SyntaxKind.JSDocLinkCode;
+}
+
+export function isJSDocLinkPlain(node: Node): node is JSDocLinkPlain {
+    return node.kind === SyntaxKind.JSDocLinkPlain;
+}
+
+export function isJSDocAllType(node: Node): node is JSDocAllType {
+    return node.kind === SyntaxKind.JSDocAllType;
+}
+
+export function isJSDocNullableType(node: Node): node is JSDocNullableType {
+    return node.kind === SyntaxKind.JSDocNullableType;
+}
+
+export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType {
+    return node.kind === SyntaxKind.JSDocNonNullableType;
+}
+
+export function isJSDocOptionalType(node: Node): node is JSDocOptionalType {
+    return node.kind === SyntaxKind.JSDocOptionalType;
+}
+
+export function isJSDocVariadicType(node: Node): node is JSDocVariadicType {
+    return node.kind === SyntaxKind.JSDocVariadicType;
+}
+
+export function isJSDoc(node: Node): node is JSDoc {
+    return node.kind === SyntaxKind.JSDoc;
+}
+
+export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral {
+    return node.kind === SyntaxKind.JSDocTypeLiteral;
+}
+
+export function isJSDocSignature(node: Node): node is JSDocSignature {
+    return node.kind === SyntaxKind.JSDocSignature;
+}
+
+// JSDoc Tags
+
+export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag {
+    return node.kind === SyntaxKind.JSDocAugmentsTag;
+}
+
+export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag {
+    return node.kind === SyntaxKind.JSDocCallbackTag;
+}
+
+export function isJSDocPublicTag(node: Node): node is JSDocPublicTag {
+    return node.kind === SyntaxKind.JSDocPublicTag;
+}
+
+export function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag {
+    return node.kind === SyntaxKind.JSDocPrivateTag;
+}
+
+export function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag {
+    return node.kind === SyntaxKind.JSDocProtectedTag;
+}
+
+export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag {
+    return node.kind === SyntaxKind.JSDocReadonlyTag;
+}
+
+export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
+    return node.kind === SyntaxKind.JSDocOverrideTag;
+}
+
+export function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag {
+    return node.kind === SyntaxKind.JSDocOverloadTag;
+}
+
+export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
+    return node.kind === SyntaxKind.JSDocDeprecatedTag;
+}
+
+export function isJSDocSeeTag(node: Node): node is JSDocSeeTag {
+    return node.kind === SyntaxKind.JSDocSeeTag;
+}
+
+export function isJSDocParameterTag(node: Node): node is JSDocParameterTag {
+    return node.kind === SyntaxKind.JSDocParameterTag;
+}
+
+export function isJSDocReturnTag(node: Node): node is JSDocReturnTag {
+    return node.kind === SyntaxKind.JSDocReturnTag;
+}
+
+export function isJSDocThisTag(node: Node): node is JSDocThisTag {
+    return node.kind === SyntaxKind.JSDocThisTag;
+}
+
+export function isJSDocTypeTag(node: Node): node is JSDocTypeTag {
+    return node.kind === SyntaxKind.JSDocTypeTag;
+}
+
+export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag {
+    return node.kind === SyntaxKind.JSDocTemplateTag;
+}
+
+export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag {
+    return node.kind === SyntaxKind.JSDocTypedefTag;
+}
+
+export function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag {
+    return node.kind === SyntaxKind.JSDocTag;
+}
+
+export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag {
+    return node.kind === SyntaxKind.JSDocPropertyTag;
+}
+
+export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
+    return node.kind === SyntaxKind.JSDocImplementsTag;
+}
+
+export function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag {
+    return node.kind === SyntaxKind.JSDocSatisfiesTag;
+}
+
+export function isJSDocImportTag(node: Node): node is JSDocImportTag {
+    return node.kind === SyntaxKind.JSDocImportTag;
+}
diff --git a/_packages/ast/src/nodes.ts b/_packages/ast/src/nodes.ts
new file mode 100644
index 0000000000..a265f5545e
--- /dev/null
+++ b/_packages/ast/src/nodes.ts
@@ -0,0 +1,2414 @@
+import { SyntaxKind } from "#syntaxKind";
+import { TokenFlags } from "#tokenFlags";
+
+export interface TextRange {
+    pos: number;
+    end: number;
+}
+
+export interface Node extends ReadonlyTextRange {
+    readonly kind: SyntaxKind;
+    readonly parent: Node;
+}
+
+export interface SourceFile extends Node {
+    readonly kind: SyntaxKind.SourceFile;
+    readonly statements: NodeArray<Statement>;
+    readonly text: string;
+    readonly fileName: string;
+}
+
+export type TriviaSyntaxKind =
+    | SyntaxKind.SingleLineCommentTrivia
+    | SyntaxKind.MultiLineCommentTrivia
+    | SyntaxKind.NewLineTrivia
+    | SyntaxKind.WhitespaceTrivia
+    | SyntaxKind.ConflictMarkerTrivia;
+
+export type LiteralSyntaxKind =
+    | SyntaxKind.NumericLiteral
+    | SyntaxKind.BigIntLiteral
+    | SyntaxKind.StringLiteral
+    | SyntaxKind.JsxText
+    | SyntaxKind.JsxTextAllWhiteSpaces
+    | SyntaxKind.RegularExpressionLiteral
+    | SyntaxKind.NoSubstitutionTemplateLiteral;
+
+export type PseudoLiteralSyntaxKind =
+    | SyntaxKind.TemplateHead
+    | SyntaxKind.TemplateMiddle
+    | SyntaxKind.TemplateTail;
+
+export type PunctuationSyntaxKind =
+    | SyntaxKind.OpenBraceToken
+    | SyntaxKind.CloseBraceToken
+    | SyntaxKind.OpenParenToken
+    | SyntaxKind.CloseParenToken
+    | SyntaxKind.OpenBracketToken
+    | SyntaxKind.CloseBracketToken
+    | SyntaxKind.DotToken
+    | SyntaxKind.DotDotDotToken
+    | SyntaxKind.SemicolonToken
+    | SyntaxKind.CommaToken
+    | SyntaxKind.QuestionDotToken
+    | SyntaxKind.LessThanToken
+    | SyntaxKind.LessThanSlashToken
+    | SyntaxKind.GreaterThanToken
+    | SyntaxKind.LessThanEqualsToken
+    | SyntaxKind.GreaterThanEqualsToken
+    | SyntaxKind.EqualsEqualsToken
+    | SyntaxKind.ExclamationEqualsToken
+    | SyntaxKind.EqualsEqualsEqualsToken
+    | SyntaxKind.ExclamationEqualsEqualsToken
+    | SyntaxKind.EqualsGreaterThanToken
+    | SyntaxKind.PlusToken
+    | SyntaxKind.MinusToken
+    | SyntaxKind.AsteriskToken
+    | SyntaxKind.AsteriskAsteriskToken
+    | SyntaxKind.SlashToken
+    | SyntaxKind.PercentToken
+    | SyntaxKind.PlusPlusToken
+    | SyntaxKind.MinusMinusToken
+    | SyntaxKind.LessThanLessThanToken
+    | SyntaxKind.GreaterThanGreaterThanToken
+    | SyntaxKind.GreaterThanGreaterThanGreaterThanToken
+    | SyntaxKind.AmpersandToken
+    | SyntaxKind.BarToken
+    | SyntaxKind.CaretToken
+    | SyntaxKind.ExclamationToken
+    | SyntaxKind.TildeToken
+    | SyntaxKind.AmpersandAmpersandToken
+    | SyntaxKind.AmpersandAmpersandEqualsToken
+    | SyntaxKind.BarBarToken
+    | SyntaxKind.BarBarEqualsToken
+    | SyntaxKind.QuestionQuestionToken
+    | SyntaxKind.QuestionQuestionEqualsToken
+    | SyntaxKind.QuestionToken
+    | SyntaxKind.ColonToken
+    | SyntaxKind.AtToken
+    | SyntaxKind.BacktickToken
+    | SyntaxKind.HashToken
+    | SyntaxKind.EqualsToken
+    | SyntaxKind.PlusEqualsToken
+    | SyntaxKind.MinusEqualsToken
+    | SyntaxKind.AsteriskEqualsToken
+    | SyntaxKind.AsteriskAsteriskEqualsToken
+    | SyntaxKind.SlashEqualsToken
+    | SyntaxKind.PercentEqualsToken
+    | SyntaxKind.LessThanLessThanEqualsToken
+    | SyntaxKind.GreaterThanGreaterThanEqualsToken
+    | SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken
+    | SyntaxKind.AmpersandEqualsToken
+    | SyntaxKind.BarEqualsToken
+    | SyntaxKind.CaretEqualsToken;
+
+export type KeywordSyntaxKind =
+    | SyntaxKind.AbstractKeyword
+    | SyntaxKind.AccessorKeyword
+    | SyntaxKind.AnyKeyword
+    | SyntaxKind.AsKeyword
+    | SyntaxKind.AssertsKeyword
+    | SyntaxKind.AssertKeyword
+    | SyntaxKind.AsyncKeyword
+    | SyntaxKind.AwaitKeyword
+    | SyntaxKind.BigIntKeyword
+    | SyntaxKind.BooleanKeyword
+    | SyntaxKind.BreakKeyword
+    | SyntaxKind.CaseKeyword
+    | SyntaxKind.CatchKeyword
+    | SyntaxKind.ClassKeyword
+    | SyntaxKind.ConstKeyword
+    | SyntaxKind.ConstructorKeyword
+    | SyntaxKind.ContinueKeyword
+    | SyntaxKind.DebuggerKeyword
+    | SyntaxKind.DeclareKeyword
+    | SyntaxKind.DefaultKeyword
+    | SyntaxKind.DeleteKeyword
+    | SyntaxKind.DoKeyword
+    | SyntaxKind.ElseKeyword
+    | SyntaxKind.EnumKeyword
+    | SyntaxKind.ExportKeyword
+    | SyntaxKind.ExtendsKeyword
+    | SyntaxKind.FalseKeyword
+    | SyntaxKind.FinallyKeyword
+    | SyntaxKind.ForKeyword
+    | SyntaxKind.FromKeyword
+    | SyntaxKind.FunctionKeyword
+    | SyntaxKind.GetKeyword
+    | SyntaxKind.GlobalKeyword
+    | SyntaxKind.IfKeyword
+    | SyntaxKind.ImplementsKeyword
+    | SyntaxKind.ImportKeyword
+    | SyntaxKind.InferKeyword
+    | SyntaxKind.InKeyword
+    | SyntaxKind.InstanceOfKeyword
+    | SyntaxKind.InterfaceKeyword
+    | SyntaxKind.IntrinsicKeyword
+    | SyntaxKind.IsKeyword
+    | SyntaxKind.KeyOfKeyword
+    | SyntaxKind.LetKeyword
+    | SyntaxKind.ModuleKeyword
+    | SyntaxKind.NamespaceKeyword
+    | SyntaxKind.NeverKeyword
+    | SyntaxKind.NewKeyword
+    | SyntaxKind.NullKeyword
+    | SyntaxKind.NumberKeyword
+    | SyntaxKind.ObjectKeyword
+    | SyntaxKind.OfKeyword
+    | SyntaxKind.PackageKeyword
+    | SyntaxKind.PrivateKeyword
+    | SyntaxKind.ProtectedKeyword
+    | SyntaxKind.PublicKeyword
+    | SyntaxKind.ReadonlyKeyword
+    | SyntaxKind.OutKeyword
+    | SyntaxKind.OverrideKeyword
+    | SyntaxKind.RequireKeyword
+    | SyntaxKind.ReturnKeyword
+    | SyntaxKind.SatisfiesKeyword
+    | SyntaxKind.SetKeyword
+    | SyntaxKind.StaticKeyword
+    | SyntaxKind.StringKeyword
+    | SyntaxKind.SuperKeyword
+    | SyntaxKind.SwitchKeyword
+    | SyntaxKind.SymbolKeyword
+    | SyntaxKind.ThisKeyword
+    | SyntaxKind.ThrowKeyword
+    | SyntaxKind.TrueKeyword
+    | SyntaxKind.TryKeyword
+    | SyntaxKind.TypeKeyword
+    | SyntaxKind.TypeOfKeyword
+    | SyntaxKind.UndefinedKeyword
+    | SyntaxKind.UniqueKeyword
+    | SyntaxKind.UnknownKeyword
+    | SyntaxKind.UsingKeyword
+    | SyntaxKind.VarKeyword
+    | SyntaxKind.VoidKeyword
+    | SyntaxKind.WhileKeyword
+    | SyntaxKind.WithKeyword
+    | SyntaxKind.YieldKeyword;
+
+export type ModifierSyntaxKind =
+    | SyntaxKind.AbstractKeyword
+    | SyntaxKind.AccessorKeyword
+    | SyntaxKind.AsyncKeyword
+    | SyntaxKind.ConstKeyword
+    | SyntaxKind.DeclareKeyword
+    | SyntaxKind.DefaultKeyword
+    | SyntaxKind.ExportKeyword
+    | SyntaxKind.InKeyword
+    | SyntaxKind.PrivateKeyword
+    | SyntaxKind.ProtectedKeyword
+    | SyntaxKind.PublicKeyword
+    | SyntaxKind.ReadonlyKeyword
+    | SyntaxKind.OutKeyword
+    | SyntaxKind.OverrideKeyword
+    | SyntaxKind.StaticKeyword;
+
+export type KeywordTypeSyntaxKind =
+    | SyntaxKind.AnyKeyword
+    | SyntaxKind.BigIntKeyword
+    | SyntaxKind.BooleanKeyword
+    | SyntaxKind.IntrinsicKeyword
+    | SyntaxKind.NeverKeyword
+    | SyntaxKind.NumberKeyword
+    | SyntaxKind.ObjectKeyword
+    | SyntaxKind.StringKeyword
+    | SyntaxKind.SymbolKeyword
+    | SyntaxKind.UndefinedKeyword
+    | SyntaxKind.UnknownKeyword
+    | SyntaxKind.VoidKeyword;
+
+export type TokenSyntaxKind =
+    | SyntaxKind.Unknown
+    | SyntaxKind.EndOfFile
+    | TriviaSyntaxKind
+    | LiteralSyntaxKind
+    | PseudoLiteralSyntaxKind
+    | PunctuationSyntaxKind
+    | SyntaxKind.Identifier
+    | KeywordSyntaxKind;
+
+export type JsxTokenSyntaxKind =
+    | SyntaxKind.LessThanSlashToken
+    | SyntaxKind.EndOfFile
+    | SyntaxKind.ConflictMarkerTrivia
+    | SyntaxKind.JsxText
+    | SyntaxKind.JsxTextAllWhiteSpaces
+    | SyntaxKind.OpenBraceToken
+    | SyntaxKind.LessThanToken;
+
+export type JSDocSyntaxKind =
+    | SyntaxKind.EndOfFile
+    | SyntaxKind.WhitespaceTrivia
+    | SyntaxKind.AtToken
+    | SyntaxKind.NewLineTrivia
+    | SyntaxKind.AsteriskToken
+    | SyntaxKind.OpenBraceToken
+    | SyntaxKind.CloseBraceToken
+    | SyntaxKind.LessThanToken
+    | SyntaxKind.GreaterThanToken
+    | SyntaxKind.OpenBracketToken
+    | SyntaxKind.CloseBracketToken
+    | SyntaxKind.OpenParenToken
+    | SyntaxKind.CloseParenToken
+    | SyntaxKind.EqualsToken
+    | SyntaxKind.CommaToken
+    | SyntaxKind.DotToken
+    | SyntaxKind.Identifier
+    | SyntaxKind.BacktickToken
+    | SyntaxKind.HashToken
+    | SyntaxKind.Unknown
+    | KeywordSyntaxKind;
+
+export interface ReadonlyTextRange {
+    readonly pos: number;
+    readonly end: number;
+}
+
+export interface NodeArray<T> extends ReadonlyTextRange, ReadonlyArray<T> {
+    readonly length: number;
+    readonly pos: number;
+    readonly end: number;
+}
+
+// TODO(rbuckton): Constraint 'TKind' to 'TokenSyntaxKind'
+export interface Token<TKind extends SyntaxKind> extends Node {
+    readonly kind: TKind;
+}
+
+export type EndOfFile = Token<SyntaxKind.EndOfFile>;
+
+// Punctuation
+export interface PunctuationToken<TKind extends PunctuationSyntaxKind> extends Token<TKind> {
+}
+
+export type DotToken = PunctuationToken<SyntaxKind.DotToken>;
+export type DotDotDotToken = PunctuationToken<SyntaxKind.DotDotDotToken>;
+export type QuestionToken = PunctuationToken<SyntaxKind.QuestionToken>;
+export type ExclamationToken = PunctuationToken<SyntaxKind.ExclamationToken>;
+export type ColonToken = PunctuationToken<SyntaxKind.ColonToken>;
+export type EqualsToken = PunctuationToken<SyntaxKind.EqualsToken>;
+export type AmpersandAmpersandEqualsToken = PunctuationToken<SyntaxKind.AmpersandAmpersandEqualsToken>;
+export type BarBarEqualsToken = PunctuationToken<SyntaxKind.BarBarEqualsToken>;
+export type QuestionQuestionEqualsToken = PunctuationToken<SyntaxKind.QuestionQuestionEqualsToken>;
+export type AsteriskToken = PunctuationToken<SyntaxKind.AsteriskToken>;
+export type EqualsGreaterThanToken = PunctuationToken<SyntaxKind.EqualsGreaterThanToken>;
+export type PlusToken = PunctuationToken<SyntaxKind.PlusToken>;
+export type MinusToken = PunctuationToken<SyntaxKind.MinusToken>;
+export type QuestionDotToken = PunctuationToken<SyntaxKind.QuestionDotToken>;
+
+// Keywords
+export interface KeywordToken<TKind extends KeywordSyntaxKind> extends Token<TKind> {
+}
+
+export type AssertsKeyword = KeywordToken<SyntaxKind.AssertsKeyword>;
+export type AssertKeyword = KeywordToken<SyntaxKind.AssertKeyword>;
+export type AwaitKeyword = KeywordToken<SyntaxKind.AwaitKeyword>;
+export type CaseKeyword = KeywordToken<SyntaxKind.CaseKeyword>;
+
+export interface ModifierToken<TKind extends ModifierSyntaxKind> extends KeywordToken<TKind> {
+}
+
+export type AbstractKeyword = ModifierToken<SyntaxKind.AbstractKeyword>;
+export type AccessorKeyword = ModifierToken<SyntaxKind.AccessorKeyword>;
+export type AsyncKeyword = ModifierToken<SyntaxKind.AsyncKeyword>;
+export type ConstKeyword = ModifierToken<SyntaxKind.ConstKeyword>;
+export type DeclareKeyword = ModifierToken<SyntaxKind.DeclareKeyword>;
+export type DefaultKeyword = ModifierToken<SyntaxKind.DefaultKeyword>;
+export type ExportKeyword = ModifierToken<SyntaxKind.ExportKeyword>;
+export type InKeyword = ModifierToken<SyntaxKind.InKeyword>;
+export type PrivateKeyword = ModifierToken<SyntaxKind.PrivateKeyword>;
+export type ProtectedKeyword = ModifierToken<SyntaxKind.ProtectedKeyword>;
+export type PublicKeyword = ModifierToken<SyntaxKind.PublicKeyword>;
+export type ReadonlyKeyword = ModifierToken<SyntaxKind.ReadonlyKeyword>;
+export type OutKeyword = ModifierToken<SyntaxKind.OutKeyword>;
+export type OverrideKeyword = ModifierToken<SyntaxKind.OverrideKeyword>;
+export type StaticKeyword = ModifierToken<SyntaxKind.StaticKeyword>;
+
+export type Modifier =
+    | AbstractKeyword
+    | AccessorKeyword
+    | AsyncKeyword
+    | ConstKeyword
+    | DeclareKeyword
+    | DefaultKeyword
+    | ExportKeyword
+    | InKeyword
+    | PrivateKeyword
+    | ProtectedKeyword
+    | PublicKeyword
+    | OutKeyword
+    | OverrideKeyword
+    | ReadonlyKeyword
+    | StaticKeyword;
+
+export type ModifierLike = Modifier | Decorator;
+
+export type AccessibilityModifier =
+    | PublicKeyword
+    | PrivateKeyword
+    | ProtectedKeyword;
+
+export type ParameterPropertyModifier =
+    | AccessibilityModifier
+    | ReadonlyKeyword;
+
+export type ClassMemberModifier =
+    | AccessibilityModifier
+    | ReadonlyKeyword
+    | StaticKeyword
+    | AccessorKeyword;
+
+export type ModifiersArray = NodeArray<Modifier>;
+
+export interface Identifier extends PrimaryExpression, Declaration {
+    readonly kind: SyntaxKind.Identifier;
+    /**
+     * Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
+     * Text of identifier, but if the identifier begins with two underscores, this will begin with three.
+     */
+    readonly text: string;
+}
+
+export interface QualifiedName extends Node {
+    readonly kind: SyntaxKind.QualifiedName;
+    readonly left: EntityName;
+    readonly right: Identifier;
+}
+
+export type EntityName = Identifier | QualifiedName;
+
+export type PropertyName =
+    | Identifier
+    | StringLiteral
+    | NoSubstitutionTemplateLiteral
+    | NumericLiteral
+    | ComputedPropertyName
+    | PrivateIdentifier
+    | BigIntLiteral;
+
+export type MemberName = Identifier | PrivateIdentifier;
+
+export type DeclarationName =
+    | PropertyName
+    | JsxAttributeName
+    | StringLiteralLike
+    | ElementAccessExpression
+    | BindingPattern
+    | EntityNameExpression;
+
+export interface Declaration extends Node {
+}
+
+export interface NamedDeclaration extends Declaration {
+    readonly name?: DeclarationName;
+}
+
+export interface DeclarationStatement extends NamedDeclaration, Statement {
+    readonly name?: Identifier | StringLiteral | NumericLiteral;
+}
+
+export interface ComputedPropertyName extends Node {
+    readonly kind: SyntaxKind.ComputedPropertyName;
+    readonly parent: Declaration;
+    readonly expression: Expression;
+}
+
+// Typed as a PrimaryExpression due to its presence in BinaryExpressions (#field in expr)
+export interface PrivateIdentifier extends PrimaryExpression {
+    readonly kind: SyntaxKind.PrivateIdentifier;
+    // escaping not strictly necessary
+    // avoids gotchas in transforms and utils
+    readonly escapedText: string;
+}
+
+export interface Decorator extends Node {
+    readonly kind: SyntaxKind.Decorator;
+    readonly parent: NamedDeclaration;
+    readonly expression: LeftHandSideExpression;
+}
+
+export interface TypeParameterDeclaration extends NamedDeclaration {
+    readonly kind: SyntaxKind.TypeParameter;
+    readonly parent: DeclarationWithTypeParameterChildren | InferTypeNode;
+    readonly modifiers?: NodeArray<Modifier>;
+    readonly name: Identifier;
+    /** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */
+    readonly constraint?: TypeNode;
+    readonly default?: TypeNode;
+
+    // For error recovery purposes (see `isGrammarError` in utilities.ts).
+    expression?: Expression;
+}
+
+export interface SignatureDeclarationBase extends NamedDeclaration {
+    readonly kind: SignatureDeclaration["kind"];
+    readonly name?: PropertyName;
+    readonly typeParameters?: NodeArray<TypeParameterDeclaration> | undefined;
+    readonly parameters: NodeArray<ParameterDeclaration>;
+    readonly type?: TypeNode | undefined;
+}
+
+export type SignatureDeclaration =
+    | CallSignatureDeclaration
+    | ConstructSignatureDeclaration
+    | MethodSignature
+    | IndexSignatureDeclaration
+    | FunctionTypeNode
+    | ConstructorTypeNode
+    | FunctionDeclaration
+    | MethodDeclaration
+    | ConstructorDeclaration
+    | AccessorDeclaration
+    | FunctionExpression
+    | ArrowFunction;
+
+export interface CallSignatureDeclaration extends SignatureDeclarationBase, TypeElement {
+    readonly kind: SyntaxKind.CallSignature;
+}
+
+export interface ConstructSignatureDeclaration extends SignatureDeclarationBase, TypeElement {
+    readonly kind: SyntaxKind.ConstructSignature;
+}
+
+export type BindingName = Identifier | BindingPattern;
+
+// dprint-ignore
+export interface VariableDeclaration extends NamedDeclaration {
+    readonly kind: SyntaxKind.VariableDeclaration;
+    readonly parent: VariableDeclarationList | CatchClause;
+    readonly name: BindingName;                    // Declared variable name
+    readonly exclamationToken?: ExclamationToken;  // Optional definite assignment assertion
+    readonly type?: TypeNode;                      // Optional type annotation
+    readonly initializer?: Expression;             // Optional initializer
+}
+
+export interface VariableDeclarationList extends Node {
+    readonly kind: SyntaxKind.VariableDeclarationList;
+    readonly parent: VariableStatement | ForStatement | ForOfStatement | ForInStatement;
+    readonly declarations: NodeArray<VariableDeclaration>;
+}
+
+// dprint-ignore
+export interface ParameterDeclaration extends NamedDeclaration {
+    readonly kind: SyntaxKind.Parameter;
+    readonly parent: SignatureDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly dotDotDotToken?: DotDotDotToken;    // Present on rest parameter
+    readonly name: BindingName;                  // Declared parameter name.
+    readonly questionToken?: QuestionToken;      // Present on optional parameter
+    readonly type?: TypeNode;                    // Optional type annotation
+    readonly initializer?: Expression;           // Optional initializer
+}
+
+// dprint-ignore
+export interface BindingElement extends NamedDeclaration {
+    readonly kind: SyntaxKind.BindingElement;
+    readonly parent: BindingPattern;
+    readonly propertyName?: PropertyName;        // Binding property name (in object binding pattern)
+    readonly dotDotDotToken?: DotDotDotToken;    // Present on rest element (in object binding pattern)
+    readonly name: BindingName;                  // Declared binding element name
+    readonly initializer?: Expression;           // Optional initializer
+}
+
+// dprint-ignore
+export interface PropertySignature extends TypeElement {
+    readonly kind: SyntaxKind.PropertySignature;
+    readonly parent: TypeLiteralNode | InterfaceDeclaration;
+    readonly modifiers?: NodeArray<Modifier>;
+    readonly name: PropertyName;                 // Declared property name
+    readonly questionToken?: QuestionToken;      // Present on optional property
+    readonly type?: TypeNode;                    // Optional type annotation
+}
+
+// dprint-ignore
+export interface PropertyDeclaration extends ClassElement {
+    readonly kind: SyntaxKind.PropertyDeclaration;
+    readonly parent: ClassLikeDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: PropertyName;
+    readonly questionToken?: QuestionToken;      // Present for use with reporting a grammar error for auto-accessors (see `isGrammarError` in utilities.ts)
+    readonly exclamationToken?: ExclamationToken;
+    readonly type?: TypeNode;
+    readonly initializer?: Expression;           // Optional initializer
+}
+
+export interface AutoAccessorPropertyDeclaration extends PropertyDeclaration {
+    _autoAccessorBrand: any;
+}
+
+export interface ObjectLiteralElement extends NamedDeclaration {
+    _objectLiteralBrand: any;
+    readonly name?: PropertyName;
+}
+
+/** Unlike ObjectLiteralElement, excludes JSXAttribute and JSXSpreadAttribute. */
+export type ObjectLiteralElementLike =
+    | PropertyAssignment
+    | ShorthandPropertyAssignment
+    | SpreadAssignment
+    | MethodDeclaration
+    | AccessorDeclaration;
+
+export interface PropertyAssignment extends ObjectLiteralElement {
+    readonly kind: SyntaxKind.PropertyAssignment;
+    readonly parent: ObjectLiteralExpression;
+    readonly name: PropertyName;
+    readonly initializer: Expression;
+}
+
+export interface ShorthandPropertyAssignment extends ObjectLiteralElement {
+    readonly kind: SyntaxKind.ShorthandPropertyAssignment;
+    readonly parent: ObjectLiteralExpression;
+    readonly name: Identifier;
+    // used when ObjectLiteralExpression is used in ObjectAssignmentPattern
+    // it is a grammar error to appear in actual object initializer (see `isGrammarError` in utilities.ts):
+    readonly equalsToken?: EqualsToken;
+    readonly objectAssignmentInitializer?: Expression;
+}
+
+export interface SpreadAssignment extends ObjectLiteralElement {
+    readonly kind: SyntaxKind.SpreadAssignment;
+    readonly parent: ObjectLiteralExpression;
+    readonly expression: Expression;
+}
+
+export type VariableLikeDeclaration =
+    | VariableDeclaration
+    | ParameterDeclaration
+    | BindingElement
+    | PropertyDeclaration
+    | PropertyAssignment
+    | PropertySignature
+    | JsxAttribute
+    | ShorthandPropertyAssignment
+    | EnumMember
+    | JSDocPropertyTag
+    | JSDocParameterTag;
+
+export interface ObjectBindingPattern extends Node {
+    readonly kind: SyntaxKind.ObjectBindingPattern;
+    readonly parent: VariableDeclaration | ParameterDeclaration | BindingElement;
+    readonly elements: NodeArray<BindingElement>;
+}
+
+export interface ArrayBindingPattern extends Node {
+    readonly kind: SyntaxKind.ArrayBindingPattern;
+    readonly parent: VariableDeclaration | ParameterDeclaration | BindingElement;
+    readonly elements: NodeArray<ArrayBindingElement>;
+}
+
+export type BindingPattern = ObjectBindingPattern | ArrayBindingPattern;
+
+export type ArrayBindingElement = BindingElement | OmittedExpression;
+
+/**
+ * Several node kinds share function-like features such as a signature,
+ * a name, and a body. These nodes should extend FunctionLikeDeclarationBase.
+ * Examples:
+ * - FunctionDeclaration
+ * - MethodDeclaration
+ * - AccessorDeclaration
+ */
+export interface FunctionLikeDeclarationBase extends SignatureDeclarationBase {
+    _functionLikeDeclarationBrand: any;
+
+    readonly asteriskToken?: AsteriskToken | undefined;
+    readonly questionToken?: QuestionToken | undefined;
+    readonly exclamationToken?: ExclamationToken | undefined;
+    readonly body?: Block | Expression | undefined;
+}
+
+export type FunctionLikeDeclaration =
+    | FunctionDeclaration
+    | MethodDeclaration
+    | GetAccessorDeclaration
+    | SetAccessorDeclaration
+    | ConstructorDeclaration
+    | FunctionExpression
+    | ArrowFunction;
+/** @deprecated Use SignatureDeclaration */
+export type FunctionLike = SignatureDeclaration;
+
+export interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement {
+    readonly kind: SyntaxKind.FunctionDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name?: Identifier;
+    readonly body?: FunctionBody;
+}
+
+export interface MethodSignature extends SignatureDeclarationBase, TypeElement {
+    readonly kind: SyntaxKind.MethodSignature;
+    readonly parent: TypeLiteralNode | InterfaceDeclaration;
+    readonly modifiers?: NodeArray<Modifier>;
+    readonly name: PropertyName;
+}
+
+// Note that a MethodDeclaration is considered both a ClassElement and an ObjectLiteralElement.
+// Both the grammars for ClassDeclaration and ObjectLiteralExpression allow for MethodDeclarations
+// as child elements, and so a MethodDeclaration satisfies both interfaces.  This avoids the
+// alternative where we would need separate kinds/types for ClassMethodDeclaration and
+// ObjectLiteralMethodDeclaration, which would look identical.
+//
+// Because of this, it may be necessary to determine what sort of MethodDeclaration you have
+// at later stages of the compiler pipeline.  In that case, you can either check the parent kind
+// of the method, or use helpers like isObjectLiteralMethodDeclaration
+export interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement {
+    readonly kind: SyntaxKind.MethodDeclaration;
+    readonly parent: ClassLikeDeclaration | ObjectLiteralExpression;
+    readonly modifiers?: NodeArray<ModifierLike> | undefined;
+    readonly name: PropertyName;
+    readonly body?: FunctionBody | undefined;
+}
+
+export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement {
+    readonly kind: SyntaxKind.Constructor;
+    readonly parent: ClassLikeDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike> | undefined;
+    readonly body?: FunctionBody | undefined;
+}
+
+/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */
+export interface SemicolonClassElement extends ClassElement {
+    readonly kind: SyntaxKind.SemicolonClassElement;
+    readonly parent: ClassLikeDeclaration;
+}
+
+// See the comment on MethodDeclaration for the intuition behind GetAccessorDeclaration being a
+// ClassElement and an ObjectLiteralElement.
+export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, TypeElement, ObjectLiteralElement {
+    readonly kind: SyntaxKind.GetAccessor;
+    readonly parent: ClassLikeDeclaration | ObjectLiteralExpression | TypeLiteralNode | InterfaceDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: PropertyName;
+    readonly body?: FunctionBody;
+}
+
+// See the comment on MethodDeclaration for the intuition behind SetAccessorDeclaration being a
+// ClassElement and an ObjectLiteralElement.
+export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, TypeElement, ObjectLiteralElement {
+    readonly kind: SyntaxKind.SetAccessor;
+    readonly parent: ClassLikeDeclaration | ObjectLiteralExpression | TypeLiteralNode | InterfaceDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: PropertyName;
+    readonly body?: FunctionBody;
+}
+
+export type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration;
+
+export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
+    readonly kind: SyntaxKind.IndexSignature;
+    readonly parent: ObjectTypeDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly type: TypeNode;
+}
+
+export interface ClassStaticBlockDeclaration extends ClassElement {
+    readonly kind: SyntaxKind.ClassStaticBlockDeclaration;
+    readonly parent: ClassDeclaration | ClassExpression;
+    readonly body: Block;
+}
+
+export interface TypeNode extends Node {
+    _typeNodeBrand: any;
+}
+
+export interface KeywordTypeNode<TKind extends KeywordTypeSyntaxKind = KeywordTypeSyntaxKind> extends KeywordToken<TKind>, TypeNode {
+    readonly kind: TKind;
+}
+
+export interface ImportTypeNode extends NodeWithTypeArguments {
+    readonly kind: SyntaxKind.ImportType;
+    readonly isTypeOf: boolean;
+    readonly argument: TypeNode;
+    readonly attributes?: ImportAttributes;
+    readonly qualifier?: EntityName;
+}
+
+export interface ThisTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.ThisType;
+}
+
+export type FunctionOrConstructorTypeNode = FunctionTypeNode | ConstructorTypeNode;
+
+export interface FunctionOrConstructorTypeNodeBase extends TypeNode, SignatureDeclarationBase {
+    readonly kind: SyntaxKind.FunctionType | SyntaxKind.ConstructorType;
+    readonly type: TypeNode;
+}
+
+export interface FunctionTypeNode extends FunctionOrConstructorTypeNodeBase {
+    readonly kind: SyntaxKind.FunctionType;
+}
+
+export interface ConstructorTypeNode extends FunctionOrConstructorTypeNodeBase {
+    readonly kind: SyntaxKind.ConstructorType;
+    readonly modifiers?: NodeArray<Modifier>;
+}
+
+export interface NodeWithTypeArguments extends TypeNode {
+    readonly typeArguments?: NodeArray<TypeNode>;
+}
+
+export type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments;
+
+export interface TypeReferenceNode extends NodeWithTypeArguments {
+    readonly kind: SyntaxKind.TypeReference;
+    readonly typeName: EntityName;
+}
+
+export interface TypePredicateNode extends TypeNode {
+    readonly kind: SyntaxKind.TypePredicate;
+    readonly parent: SignatureDeclaration | JSDocTypeExpression;
+    readonly assertsModifier?: AssertsKeyword;
+    readonly parameterName: Identifier | ThisTypeNode;
+    readonly type?: TypeNode;
+}
+
+export interface TypeQueryNode extends NodeWithTypeArguments {
+    readonly kind: SyntaxKind.TypeQuery;
+    readonly exprName: EntityName;
+}
+
+// A TypeLiteral is the declaration node for an anonymous symbol.
+export interface TypeLiteralNode extends TypeNode, Declaration {
+    readonly kind: SyntaxKind.TypeLiteral;
+    readonly members: NodeArray<TypeElement>;
+}
+
+export interface ArrayTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.ArrayType;
+    readonly elementType: TypeNode;
+}
+
+export interface TupleTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.TupleType;
+    readonly elements: NodeArray<TypeNode | NamedTupleMember>;
+}
+
+export interface NamedTupleMember extends TypeNode, Declaration {
+    readonly kind: SyntaxKind.NamedTupleMember;
+    readonly dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
+    readonly name: Identifier;
+    readonly questionToken?: Token<SyntaxKind.QuestionToken>;
+    readonly type: TypeNode;
+}
+
+export interface OptionalTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.OptionalType;
+    readonly type: TypeNode;
+}
+
+export interface RestTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.RestType;
+    readonly type: TypeNode;
+}
+
+export type UnionOrIntersectionTypeNode = UnionTypeNode | IntersectionTypeNode;
+
+export interface UnionTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.UnionType;
+    readonly types: NodeArray<TypeNode>;
+}
+
+export interface IntersectionTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.IntersectionType;
+    readonly types: NodeArray<TypeNode>;
+}
+
+export interface ConditionalTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.ConditionalType;
+    readonly checkType: TypeNode;
+    readonly extendsType: TypeNode;
+    readonly trueType: TypeNode;
+    readonly falseType: TypeNode;
+}
+
+export interface InferTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.InferType;
+    readonly typeParameter: TypeParameterDeclaration;
+}
+
+export interface ParenthesizedTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.ParenthesizedType;
+    readonly type: TypeNode;
+}
+
+export interface TypeOperatorNode extends TypeNode {
+    readonly kind: SyntaxKind.TypeOperator;
+    readonly operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword;
+    readonly type: TypeNode;
+}
+
+export interface IndexedAccessTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.IndexedAccessType;
+    readonly objectType: TypeNode;
+    readonly indexType: TypeNode;
+}
+
+export interface MappedTypeNode extends TypeNode, Declaration {
+    readonly kind: SyntaxKind.MappedType;
+    readonly readonlyToken?: ReadonlyKeyword | PlusToken | MinusToken;
+    readonly typeParameter: TypeParameterDeclaration;
+    readonly nameType?: TypeNode;
+    readonly questionToken?: QuestionToken | PlusToken | MinusToken;
+    readonly type?: TypeNode;
+    /** Used only to produce grammar errors */
+    readonly members?: NodeArray<TypeElement>;
+}
+
+export interface LiteralTypeNode extends TypeNode {
+    readonly kind: SyntaxKind.LiteralType;
+    readonly literal: NullLiteral | BooleanLiteral | LiteralExpression | PrefixUnaryExpression;
+}
+
+export interface StringLiteral extends LiteralExpression, Declaration {
+    readonly kind: SyntaxKind.StringLiteral;
+}
+
+export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
+export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral | JsxNamespacedName | BigIntLiteral;
+
+export interface TemplateLiteralTypeNode extends TypeNode {
+    kind: SyntaxKind.TemplateLiteralType;
+    readonly head: TemplateHead;
+    readonly templateSpans: NodeArray<TemplateLiteralTypeSpan>;
+}
+
+export interface TemplateLiteralTypeSpan extends TypeNode {
+    readonly kind: SyntaxKind.TemplateLiteralTypeSpan;
+    readonly parent: TemplateLiteralTypeNode;
+    readonly type: TypeNode;
+    readonly literal: TemplateMiddle | TemplateTail;
+}
+
+// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
+// Consider 'Expression'.  Without the brand, 'Expression' is actually no different
+// (structurally) than 'Node'.  Because of this you can pass any Node to a function that
+// takes an Expression without any error.  By using the 'brands' we ensure that the type
+// checker actually thinks you have something of the right type.  Note: the brands are
+// never actually given values.  At runtime they have zero cost.
+
+export interface Expression extends Node {
+    _expressionBrand: any;
+}
+
+export interface OmittedExpression extends Expression {
+    readonly kind: SyntaxKind.OmittedExpression;
+}
+
+// Represents an expression that is elided as part of a transformation to emit comments on a
+// not-emitted node. The 'expression' property of a PartiallyEmittedExpression should be emitted.
+export interface PartiallyEmittedExpression extends LeftHandSideExpression {
+    readonly kind: SyntaxKind.PartiallyEmittedExpression;
+    readonly expression: Expression;
+}
+
+export interface UnaryExpression extends Expression {
+    _unaryExpressionBrand: any;
+}
+
+/** Deprecated, please use UpdateExpression */
+export type IncrementExpression = UpdateExpression;
+export interface UpdateExpression extends UnaryExpression {
+    _updateExpressionBrand: any;
+}
+
+// see: https://tc39.github.io/ecma262/#prod-UpdateExpression
+// see: https://tc39.github.io/ecma262/#prod-UnaryExpression
+export type PrefixUnaryOperator =
+    | SyntaxKind.PlusPlusToken
+    | SyntaxKind.MinusMinusToken
+    | SyntaxKind.PlusToken
+    | SyntaxKind.MinusToken
+    | SyntaxKind.TildeToken
+    | SyntaxKind.ExclamationToken;
+
+export interface PrefixUnaryExpression extends UpdateExpression {
+    readonly kind: SyntaxKind.PrefixUnaryExpression;
+    readonly operator: PrefixUnaryOperator;
+    readonly operand: UnaryExpression;
+}
+
+// see: https://tc39.github.io/ecma262/#prod-UpdateExpression
+export type PostfixUnaryOperator =
+    | SyntaxKind.PlusPlusToken
+    | SyntaxKind.MinusMinusToken;
+
+export interface PostfixUnaryExpression extends UpdateExpression {
+    readonly kind: SyntaxKind.PostfixUnaryExpression;
+    readonly operand: LeftHandSideExpression;
+    readonly operator: PostfixUnaryOperator;
+}
+
+export interface LeftHandSideExpression extends UpdateExpression {
+    _leftHandSideExpressionBrand: any;
+}
+
+export interface MemberExpression extends LeftHandSideExpression {
+    _memberExpressionBrand: any;
+}
+
+export interface PrimaryExpression extends MemberExpression {
+    _primaryExpressionBrand: any;
+}
+
+export interface NullLiteral extends PrimaryExpression {
+    readonly kind: SyntaxKind.NullKeyword;
+}
+
+export interface TrueLiteral extends PrimaryExpression {
+    readonly kind: SyntaxKind.TrueKeyword;
+}
+
+export interface FalseLiteral extends PrimaryExpression {
+    readonly kind: SyntaxKind.FalseKeyword;
+}
+
+export type BooleanLiteral = TrueLiteral | FalseLiteral;
+
+export interface ThisExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.ThisKeyword;
+}
+
+export interface SuperExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.SuperKeyword;
+}
+
+export interface ImportExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.ImportKeyword;
+}
+
+export interface DeleteExpression extends UnaryExpression {
+    readonly kind: SyntaxKind.DeleteExpression;
+    readonly expression: UnaryExpression;
+}
+
+export interface TypeOfExpression extends UnaryExpression {
+    readonly kind: SyntaxKind.TypeOfExpression;
+    readonly expression: UnaryExpression;
+}
+
+export interface VoidExpression extends UnaryExpression {
+    readonly kind: SyntaxKind.VoidExpression;
+    readonly expression: UnaryExpression;
+}
+
+export interface AwaitExpression extends UnaryExpression {
+    readonly kind: SyntaxKind.AwaitExpression;
+    readonly expression: UnaryExpression;
+}
+
+export interface YieldExpression extends Expression {
+    readonly kind: SyntaxKind.YieldExpression;
+    readonly asteriskToken?: AsteriskToken;
+    readonly expression?: Expression;
+}
+
+// see: https://tc39.github.io/ecma262/#prod-ExponentiationExpression
+export type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken;
+
+// see: https://tc39.github.io/ecma262/#prod-MultiplicativeOperator
+export type MultiplicativeOperator =
+    | SyntaxKind.AsteriskToken
+    | SyntaxKind.SlashToken
+    | SyntaxKind.PercentToken;
+
+// see: https://tc39.github.io/ecma262/#prod-MultiplicativeExpression
+export type MultiplicativeOperatorOrHigher =
+    | ExponentiationOperator
+    | MultiplicativeOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-AdditiveExpression
+export type AdditiveOperator =
+    | SyntaxKind.PlusToken
+    | SyntaxKind.MinusToken;
+
+// see: https://tc39.github.io/ecma262/#prod-AdditiveExpression
+export type AdditiveOperatorOrHigher =
+    | MultiplicativeOperatorOrHigher
+    | AdditiveOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-ShiftExpression
+export type ShiftOperator =
+    | SyntaxKind.LessThanLessThanToken
+    | SyntaxKind.GreaterThanGreaterThanToken
+    | SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
+
+// see: https://tc39.github.io/ecma262/#prod-ShiftExpression
+export type ShiftOperatorOrHigher =
+    | AdditiveOperatorOrHigher
+    | ShiftOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-RelationalExpression
+export type RelationalOperator =
+    | SyntaxKind.LessThanToken
+    | SyntaxKind.LessThanEqualsToken
+    | SyntaxKind.GreaterThanToken
+    | SyntaxKind.GreaterThanEqualsToken
+    | SyntaxKind.InstanceOfKeyword
+    | SyntaxKind.InKeyword;
+
+// see: https://tc39.github.io/ecma262/#prod-RelationalExpression
+export type RelationalOperatorOrHigher =
+    | ShiftOperatorOrHigher
+    | RelationalOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-EqualityExpression
+export type EqualityOperator =
+    | SyntaxKind.EqualsEqualsToken
+    | SyntaxKind.EqualsEqualsEqualsToken
+    | SyntaxKind.ExclamationEqualsEqualsToken
+    | SyntaxKind.ExclamationEqualsToken;
+
+// see: https://tc39.github.io/ecma262/#prod-EqualityExpression
+export type EqualityOperatorOrHigher =
+    | RelationalOperatorOrHigher
+    | EqualityOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-BitwiseANDExpression
+// see: https://tc39.github.io/ecma262/#prod-BitwiseXORExpression
+// see: https://tc39.github.io/ecma262/#prod-BitwiseORExpression
+export type BitwiseOperator =
+    | SyntaxKind.AmpersandToken
+    | SyntaxKind.BarToken
+    | SyntaxKind.CaretToken;
+
+// see: https://tc39.github.io/ecma262/#prod-BitwiseANDExpression
+// see: https://tc39.github.io/ecma262/#prod-BitwiseXORExpression
+// see: https://tc39.github.io/ecma262/#prod-BitwiseORExpression
+export type BitwiseOperatorOrHigher =
+    | EqualityOperatorOrHigher
+    | BitwiseOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-LogicalANDExpression
+// see: https://tc39.github.io/ecma262/#prod-LogicalORExpression
+export type LogicalOperator =
+    | SyntaxKind.AmpersandAmpersandToken
+    | SyntaxKind.BarBarToken;
+
+// see: https://tc39.github.io/ecma262/#prod-LogicalANDExpression
+// see: https://tc39.github.io/ecma262/#prod-LogicalORExpression
+export type LogicalOperatorOrHigher =
+    | BitwiseOperatorOrHigher
+    | LogicalOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-AssignmentOperator
+export type CompoundAssignmentOperator =
+    | SyntaxKind.PlusEqualsToken
+    | SyntaxKind.MinusEqualsToken
+    | SyntaxKind.AsteriskAsteriskEqualsToken
+    | SyntaxKind.AsteriskEqualsToken
+    | SyntaxKind.SlashEqualsToken
+    | SyntaxKind.PercentEqualsToken
+    | SyntaxKind.AmpersandEqualsToken
+    | SyntaxKind.BarEqualsToken
+    | SyntaxKind.CaretEqualsToken
+    | SyntaxKind.LessThanLessThanEqualsToken
+    | SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken
+    | SyntaxKind.GreaterThanGreaterThanEqualsToken
+    | SyntaxKind.BarBarEqualsToken
+    | SyntaxKind.AmpersandAmpersandEqualsToken
+    | SyntaxKind.QuestionQuestionEqualsToken;
+
+// see: https://tc39.github.io/ecma262/#prod-AssignmentExpression
+export type AssignmentOperator =
+    | SyntaxKind.EqualsToken
+    | CompoundAssignmentOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-AssignmentExpression
+export type AssignmentOperatorOrHigher =
+    | SyntaxKind.QuestionQuestionToken
+    | LogicalOperatorOrHigher
+    | AssignmentOperator;
+
+// see: https://tc39.github.io/ecma262/#prod-Expression
+export type BinaryOperator =
+    | AssignmentOperatorOrHigher
+    | SyntaxKind.CommaToken;
+
+export type LogicalOrCoalescingAssignmentOperator =
+    | SyntaxKind.AmpersandAmpersandEqualsToken
+    | SyntaxKind.BarBarEqualsToken
+    | SyntaxKind.QuestionQuestionEqualsToken;
+
+export type BinaryOperatorToken = Token<BinaryOperator>;
+
+export interface BinaryExpression extends Expression, Declaration {
+    readonly kind: SyntaxKind.BinaryExpression;
+    readonly left: Expression;
+    readonly operatorToken: BinaryOperatorToken;
+    readonly right: Expression;
+}
+
+export type AssignmentOperatorToken = Token<AssignmentOperator>;
+
+export interface AssignmentExpression<TOperator extends AssignmentOperatorToken> extends BinaryExpression {
+    readonly left: LeftHandSideExpression;
+    readonly operatorToken: TOperator;
+}
+
+export interface ObjectDestructuringAssignment extends AssignmentExpression<EqualsToken> {
+    readonly left: ObjectLiteralExpression;
+}
+
+export interface ArrayDestructuringAssignment extends AssignmentExpression<EqualsToken> {
+    readonly left: ArrayLiteralExpression;
+}
+
+export type DestructuringAssignment =
+    | ObjectDestructuringAssignment
+    | ArrayDestructuringAssignment;
+
+export type BindingOrAssignmentElement =
+    | VariableDeclaration
+    | ParameterDeclaration
+    | ObjectBindingOrAssignmentElement
+    | ArrayBindingOrAssignmentElement;
+
+export type ObjectBindingOrAssignmentElement =
+    | BindingElement
+    | PropertyAssignment // AssignmentProperty
+    | ShorthandPropertyAssignment // AssignmentProperty
+    | SpreadAssignment // AssignmentRestProperty
+;
+
+export type ArrayBindingOrAssignmentElement =
+    | BindingElement
+    | OmittedExpression // Elision
+    | SpreadElement // AssignmentRestElement
+    | ArrayLiteralExpression // ArrayAssignmentPattern
+    | ObjectLiteralExpression // ObjectAssignmentPattern
+    | AssignmentExpression<EqualsToken> // AssignmentElement
+    | Identifier // DestructuringAssignmentTarget
+    | PropertyAccessExpression // DestructuringAssignmentTarget
+    | ElementAccessExpression // DestructuringAssignmentTarget
+;
+
+export type BindingOrAssignmentElementRestIndicator =
+    | DotDotDotToken // from BindingElement
+    | SpreadElement // AssignmentRestElement
+    | SpreadAssignment // AssignmentRestProperty
+;
+
+export type BindingOrAssignmentElementTarget =
+    | BindingOrAssignmentPattern
+    | Identifier
+    | PropertyAccessExpression
+    | ElementAccessExpression
+    | OmittedExpression;
+
+export type ObjectBindingOrAssignmentPattern =
+    | ObjectBindingPattern
+    | ObjectLiteralExpression // ObjectAssignmentPattern
+;
+
+export type ArrayBindingOrAssignmentPattern =
+    | ArrayBindingPattern
+    | ArrayLiteralExpression // ArrayAssignmentPattern
+;
+
+export type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression;
+
+export type BindingOrAssignmentPattern = ObjectBindingOrAssignmentPattern | ArrayBindingOrAssignmentPattern;
+
+export interface ConditionalExpression extends Expression {
+    readonly kind: SyntaxKind.ConditionalExpression;
+    readonly condition: Expression;
+    readonly questionToken: QuestionToken;
+    readonly whenTrue: Expression;
+    readonly colonToken: ColonToken;
+    readonly whenFalse: Expression;
+}
+
+export type FunctionBody = Block;
+export type ConciseBody = FunctionBody | Expression;
+
+export interface FunctionExpression extends PrimaryExpression, FunctionLikeDeclarationBase {
+    readonly kind: SyntaxKind.FunctionExpression;
+    readonly modifiers?: NodeArray<Modifier>;
+    readonly name?: Identifier;
+    readonly body: FunctionBody; // Required, whereas the member inherited from FunctionDeclaration is optional
+}
+
+export interface ArrowFunction extends Expression, FunctionLikeDeclarationBase {
+    readonly kind: SyntaxKind.ArrowFunction;
+    readonly modifiers?: NodeArray<Modifier>;
+    readonly equalsGreaterThanToken: EqualsGreaterThanToken;
+    readonly body: ConciseBody;
+    readonly name: never;
+}
+
+// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
+// or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
+// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
+export interface LiteralLikeNode extends Node {
+    text: string;
+    isUnterminated?: boolean;
+    hasExtendedUnicodeEscape?: boolean;
+}
+
+export interface TemplateLiteralLikeNode extends LiteralLikeNode {
+    rawText?: string;
+    templateFlags?: TokenFlags;
+}
+
+// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
+// or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
+// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
+export interface LiteralExpression extends LiteralLikeNode, PrimaryExpression {
+    _literalExpressionBrand: any;
+}
+
+export interface RegularExpressionLiteral extends LiteralExpression {
+    readonly kind: SyntaxKind.RegularExpressionLiteral;
+}
+
+// dprint-ignore
+export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration {
+    readonly kind: SyntaxKind.NoSubstitutionTemplateLiteral;
+}
+
+export interface NumericLiteral extends LiteralExpression, Declaration {
+    readonly kind: SyntaxKind.NumericLiteral;
+    readonly numericLiteralFlags: TokenFlags;
+}
+
+export interface BigIntLiteral extends LiteralExpression {
+    readonly kind: SyntaxKind.BigIntLiteral;
+}
+
+export type LiteralToken =
+    | NumericLiteral
+    | BigIntLiteral
+    | StringLiteral
+    | JsxText
+    | RegularExpressionLiteral
+    | NoSubstitutionTemplateLiteral;
+
+export interface TemplateHead extends TemplateLiteralLikeNode {
+    readonly kind: SyntaxKind.TemplateHead;
+    readonly parent: TemplateExpression | TemplateLiteralTypeNode;
+    templateFlags: TokenFlags;
+}
+
+export interface TemplateMiddle extends TemplateLiteralLikeNode {
+    readonly kind: SyntaxKind.TemplateMiddle;
+    readonly parent: TemplateSpan | TemplateLiteralTypeSpan;
+    templateFlags: TokenFlags;
+}
+
+export interface TemplateTail extends TemplateLiteralLikeNode {
+    readonly kind: SyntaxKind.TemplateTail;
+    readonly parent: TemplateSpan | TemplateLiteralTypeSpan;
+    templateFlags: TokenFlags;
+}
+
+export type PseudoLiteralToken =
+    | TemplateHead
+    | TemplateMiddle
+    | TemplateTail;
+
+export type TemplateLiteralToken =
+    | NoSubstitutionTemplateLiteral
+    | PseudoLiteralToken;
+
+export interface TemplateExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.TemplateExpression;
+    readonly head: TemplateHead;
+    readonly templateSpans: NodeArray<TemplateSpan>;
+}
+
+export type TemplateLiteral =
+    | TemplateExpression
+    | NoSubstitutionTemplateLiteral;
+
+// Each of these corresponds to a substitution expression and a template literal, in that order.
+// The template literal must have kind TemplateMiddleLiteral or TemplateTailLiteral.
+export interface TemplateSpan extends Node {
+    readonly kind: SyntaxKind.TemplateSpan;
+    readonly parent: TemplateExpression;
+    readonly expression: Expression;
+    readonly literal: TemplateMiddle | TemplateTail;
+}
+
+export interface ParenthesizedExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.ParenthesizedExpression;
+    readonly expression: Expression;
+}
+
+export interface ArrayLiteralExpression extends PrimaryExpression {
+    readonly kind: SyntaxKind.ArrayLiteralExpression;
+    readonly elements: NodeArray<Expression>;
+    multiLine?: boolean;
+}
+
+export interface SpreadElement extends Expression {
+    readonly kind: SyntaxKind.SpreadElement;
+    readonly parent: ArrayLiteralExpression | CallExpression | NewExpression;
+    readonly expression: Expression;
+}
+
+/**
+ * This interface is a base interface for ObjectLiteralExpression and JSXAttributes to extend from. JSXAttributes is similar to
+ * ObjectLiteralExpression in that it contains array of properties; however, JSXAttributes' properties can only be
+ * JSXAttribute or JSXSpreadAttribute. ObjectLiteralExpression, on the other hand, can only have properties of type
+ * ObjectLiteralElement (e.g. PropertyAssignment, ShorthandPropertyAssignment etc.)
+ */
+export interface ObjectLiteralExpressionBase<T extends ObjectLiteralElement> extends PrimaryExpression, Declaration {
+    readonly properties: NodeArray<T>;
+}
+
+// An ObjectLiteralExpression is the declaration node for an anonymous symbol.
+export interface ObjectLiteralExpression extends ObjectLiteralExpressionBase<ObjectLiteralElementLike> {
+    readonly kind: SyntaxKind.ObjectLiteralExpression;
+    multiLine?: boolean;
+}
+
+export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
+export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
+export type AccessExpression = PropertyAccessExpression | ElementAccessExpression;
+
+export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration {
+    readonly kind: SyntaxKind.PropertyAccessExpression;
+    readonly expression: LeftHandSideExpression;
+    readonly questionDotToken?: QuestionDotToken;
+    readonly name: MemberName;
+}
+
+export interface PropertyAccessChain extends PropertyAccessExpression {
+    _optionalChainBrand: any;
+    readonly name: MemberName;
+}
+
+export interface SuperPropertyAccessExpression extends PropertyAccessExpression {
+    readonly expression: SuperExpression;
+}
+
+/** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */
+export interface PropertyAccessEntityNameExpression extends PropertyAccessExpression {
+    _propertyAccessExpressionLikeQualifiedNameBrand?: any;
+    readonly expression: EntityNameExpression;
+    readonly name: Identifier;
+}
+
+export interface ElementAccessExpression extends MemberExpression, Declaration {
+    readonly kind: SyntaxKind.ElementAccessExpression;
+    readonly expression: LeftHandSideExpression;
+    readonly questionDotToken?: QuestionDotToken;
+    readonly argumentExpression: Expression;
+}
+
+export interface ElementAccessChain extends ElementAccessExpression {
+    _optionalChainBrand: any;
+}
+
+export interface SuperElementAccessExpression extends ElementAccessExpression {
+    readonly expression: SuperExpression;
+}
+
+// see: https://tc39.github.io/ecma262/#prod-SuperProperty
+export type SuperProperty = SuperPropertyAccessExpression | SuperElementAccessExpression;
+
+export interface CallExpression extends LeftHandSideExpression, Declaration {
+    readonly kind: SyntaxKind.CallExpression;
+    readonly expression: LeftHandSideExpression;
+    readonly questionDotToken?: QuestionDotToken;
+    readonly typeArguments?: NodeArray<TypeNode>;
+    readonly arguments: NodeArray<Expression>;
+}
+
+export interface CallChain extends CallExpression {
+    _optionalChainBrand: any;
+}
+
+export type OptionalChain =
+    | PropertyAccessChain
+    | ElementAccessChain
+    | CallChain
+    | NonNullChain;
+
+// see: https://tc39.github.io/ecma262/#prod-SuperCall
+export interface SuperCall extends CallExpression {
+    readonly expression: SuperExpression;
+}
+
+export interface ImportCall extends CallExpression {
+    readonly expression: ImportExpression;
+}
+
+export interface ExpressionWithTypeArguments extends MemberExpression, NodeWithTypeArguments {
+    readonly kind: SyntaxKind.ExpressionWithTypeArguments;
+    readonly expression: LeftHandSideExpression;
+}
+
+export interface NewExpression extends PrimaryExpression, Declaration {
+    readonly kind: SyntaxKind.NewExpression;
+    readonly expression: LeftHandSideExpression;
+    readonly typeArguments?: NodeArray<TypeNode>;
+    readonly arguments?: NodeArray<Expression>;
+}
+
+export interface TaggedTemplateExpression extends MemberExpression {
+    readonly kind: SyntaxKind.TaggedTemplateExpression;
+    readonly tag: LeftHandSideExpression;
+    readonly typeArguments?: NodeArray<TypeNode>;
+    readonly template: TemplateLiteral;
+}
+
+export interface InstanceofExpression extends BinaryExpression {
+    readonly operatorToken: Token<SyntaxKind.InstanceOfKeyword>;
+}
+
+export type CallLikeExpression =
+    | CallExpression
+    | NewExpression
+    | TaggedTemplateExpression
+    | Decorator
+    | JsxOpeningLikeElement
+    | InstanceofExpression;
+
+export interface AsExpression extends Expression {
+    readonly kind: SyntaxKind.AsExpression;
+    readonly expression: Expression;
+    readonly type: TypeNode;
+}
+
+export interface TypeAssertion extends UnaryExpression {
+    readonly kind: SyntaxKind.TypeAssertionExpression;
+    readonly type: TypeNode;
+    readonly expression: UnaryExpression;
+}
+
+export interface SatisfiesExpression extends Expression {
+    readonly kind: SyntaxKind.SatisfiesExpression;
+    readonly expression: Expression;
+    readonly type: TypeNode;
+}
+
+export type AssertionExpression =
+    | TypeAssertion
+    | AsExpression;
+
+export interface NonNullExpression extends LeftHandSideExpression {
+    readonly kind: SyntaxKind.NonNullExpression;
+    readonly expression: Expression;
+}
+
+export interface NonNullChain extends NonNullExpression {
+    _optionalChainBrand: any;
+}
+
+// NOTE: MetaProperty is really a MemberExpression, but we consider it a PrimaryExpression
+//       for the same reasons we treat NewExpression as a PrimaryExpression.
+export interface MetaProperty extends PrimaryExpression {
+    readonly kind: SyntaxKind.MetaProperty;
+    readonly keywordToken: SyntaxKind.NewKeyword | SyntaxKind.ImportKeyword;
+    readonly name: Identifier;
+}
+
+/// A JSX expression of the form <TagName attrs>...</TagName>
+export interface JsxElement extends PrimaryExpression {
+    readonly kind: SyntaxKind.JsxElement;
+    readonly openingElement: JsxOpeningElement;
+    readonly children: NodeArray<JsxChild>;
+    readonly closingElement: JsxClosingElement;
+}
+
+/// Either the opening tag in a <Tag>...</Tag> pair or the lone <Tag /> in a self-closing form
+export type JsxOpeningLikeElement =
+    | JsxSelfClosingElement
+    | JsxOpeningElement;
+
+export type JsxAttributeLike =
+    | JsxAttribute
+    | JsxSpreadAttribute;
+
+export type JsxAttributeName =
+    | Identifier
+    | JsxNamespacedName;
+
+export type JsxTagNameExpression =
+    | Identifier
+    | ThisExpression
+    | JsxTagNamePropertyAccess
+    | JsxNamespacedName;
+
+export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
+    readonly expression: Identifier | ThisExpression | JsxTagNamePropertyAccess;
+}
+
+export interface JsxAttributes extends PrimaryExpression, Declaration {
+    readonly properties: NodeArray<JsxAttributeLike>;
+    readonly kind: SyntaxKind.JsxAttributes;
+    readonly parent: JsxOpeningLikeElement;
+}
+
+export interface JsxNamespacedName extends Node {
+    readonly kind: SyntaxKind.JsxNamespacedName;
+    readonly name: Identifier;
+    readonly namespace: Identifier;
+}
+
+/// The opening element of a <Tag>...</Tag> JsxElement
+export interface JsxOpeningElement extends Expression {
+    readonly kind: SyntaxKind.JsxOpeningElement;
+    readonly parent: JsxElement;
+    readonly tagName: JsxTagNameExpression;
+    readonly typeArguments?: NodeArray<TypeNode>;
+    readonly attributes: JsxAttributes;
+}
+
+/// A JSX expression of the form <TagName attrs />
+export interface JsxSelfClosingElement extends PrimaryExpression {
+    readonly kind: SyntaxKind.JsxSelfClosingElement;
+    readonly tagName: JsxTagNameExpression;
+    readonly typeArguments?: NodeArray<TypeNode>;
+    readonly attributes: JsxAttributes;
+}
+
+/// A JSX expression of the form <>...</>
+export interface JsxFragment extends PrimaryExpression {
+    readonly kind: SyntaxKind.JsxFragment;
+    readonly openingFragment: JsxOpeningFragment;
+    readonly children: NodeArray<JsxChild>;
+    readonly closingFragment: JsxClosingFragment;
+}
+
+/// The opening element of a <>...</> JsxFragment
+export interface JsxOpeningFragment extends Expression {
+    readonly kind: SyntaxKind.JsxOpeningFragment;
+    readonly parent: JsxFragment;
+}
+
+/// The closing element of a <>...</> JsxFragment
+export interface JsxClosingFragment extends Expression {
+    readonly kind: SyntaxKind.JsxClosingFragment;
+    readonly parent: JsxFragment;
+}
+
+export interface JsxAttribute extends Declaration {
+    readonly kind: SyntaxKind.JsxAttribute;
+    readonly parent: JsxAttributes;
+    readonly name: JsxAttributeName;
+    /// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
+    readonly initializer?: JsxAttributeValue;
+}
+
+export type JsxAttributeValue =
+    | StringLiteral
+    | JsxExpression
+    | JsxElement
+    | JsxSelfClosingElement
+    | JsxFragment;
+
+export interface JsxSpreadAttribute extends ObjectLiteralElement {
+    readonly kind: SyntaxKind.JsxSpreadAttribute;
+    readonly parent: JsxAttributes;
+    readonly expression: Expression;
+}
+
+export interface JsxClosingElement extends Node {
+    readonly kind: SyntaxKind.JsxClosingElement;
+    readonly parent: JsxElement;
+    readonly tagName: JsxTagNameExpression;
+}
+
+export interface JsxExpression extends Expression {
+    readonly kind: SyntaxKind.JsxExpression;
+    readonly parent: JsxElement | JsxFragment | JsxAttributeLike;
+    readonly dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
+    readonly expression?: Expression;
+}
+
+export interface JsxText extends LiteralLikeNode {
+    readonly kind: SyntaxKind.JsxText;
+    readonly parent: JsxElement | JsxFragment;
+    readonly containsOnlyTriviaWhiteSpaces: boolean;
+}
+
+export type JsxChild =
+    | JsxText
+    | JsxExpression
+    | JsxElement
+    | JsxSelfClosingElement
+    | JsxFragment;
+
+export interface Statement extends Node {
+    _statementBrand: any;
+}
+
+/**
+ * A list of comma-separated expressions. This node is only created by transformations.
+ */
+export interface CommaListExpression extends Expression {
+    readonly kind: SyntaxKind.CommaListExpression;
+    readonly elements: NodeArray<Expression>;
+}
+
+export interface EmptyStatement extends Statement {
+    readonly kind: SyntaxKind.EmptyStatement;
+}
+
+export interface DebuggerStatement extends Statement {
+    readonly kind: SyntaxKind.DebuggerStatement;
+}
+
+export interface Block extends Statement {
+    readonly kind: SyntaxKind.Block;
+    readonly statements: NodeArray<Statement>;
+    multiLine?: boolean;
+}
+
+export interface VariableStatement extends Statement {
+    readonly kind: SyntaxKind.VariableStatement;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly declarationList: VariableDeclarationList;
+}
+
+export interface ExpressionStatement extends Statement {
+    readonly kind: SyntaxKind.ExpressionStatement;
+    readonly expression: Expression;
+}
+
+export interface IfStatement extends Statement {
+    readonly kind: SyntaxKind.IfStatement;
+    readonly expression: Expression;
+    readonly thenStatement: Statement;
+    readonly elseStatement?: Statement;
+}
+
+export interface IterationStatement extends Statement {
+    readonly statement: Statement;
+}
+
+export interface DoStatement extends IterationStatement {
+    readonly kind: SyntaxKind.DoStatement;
+    readonly expression: Expression;
+}
+
+export interface WhileStatement extends IterationStatement {
+    readonly kind: SyntaxKind.WhileStatement;
+    readonly expression: Expression;
+}
+
+export type ForInitializer =
+    | VariableDeclarationList
+    | Expression;
+
+export interface ForStatement extends IterationStatement {
+    readonly kind: SyntaxKind.ForStatement;
+    readonly initializer?: ForInitializer;
+    readonly condition?: Expression;
+    readonly incrementor?: Expression;
+}
+
+export type ForInOrOfStatement =
+    | ForInStatement
+    | ForOfStatement;
+
+export interface ForInStatement extends IterationStatement {
+    readonly kind: SyntaxKind.ForInStatement;
+    readonly initializer: ForInitializer;
+    readonly expression: Expression;
+}
+
+export interface ForOfStatement extends IterationStatement {
+    readonly kind: SyntaxKind.ForOfStatement;
+    readonly awaitModifier?: AwaitKeyword;
+    readonly initializer: ForInitializer;
+    readonly expression: Expression;
+}
+
+export interface BreakStatement extends Statement {
+    readonly kind: SyntaxKind.BreakStatement;
+    readonly label?: Identifier;
+}
+
+export interface ContinueStatement extends Statement {
+    readonly kind: SyntaxKind.ContinueStatement;
+    readonly label?: Identifier;
+}
+
+export type BreakOrContinueStatement =
+    | BreakStatement
+    | ContinueStatement;
+
+export interface ReturnStatement extends Statement {
+    readonly kind: SyntaxKind.ReturnStatement;
+    readonly expression?: Expression;
+}
+
+export interface WithStatement extends Statement {
+    readonly kind: SyntaxKind.WithStatement;
+    readonly expression: Expression;
+    readonly statement: Statement;
+}
+
+export interface SwitchStatement extends Statement {
+    readonly kind: SyntaxKind.SwitchStatement;
+    readonly expression: Expression;
+    readonly caseBlock: CaseBlock;
+    possiblyExhaustive?: boolean; // initialized by binding
+}
+
+export interface CaseBlock extends Node {
+    readonly kind: SyntaxKind.CaseBlock;
+    readonly parent: SwitchStatement;
+    readonly clauses: NodeArray<CaseOrDefaultClause>;
+}
+
+export interface CaseClause extends Node {
+    readonly kind: SyntaxKind.CaseClause;
+    readonly parent: CaseBlock;
+    readonly expression: Expression;
+    readonly statements: NodeArray<Statement>;
+}
+
+export interface DefaultClause extends Node {
+    readonly kind: SyntaxKind.DefaultClause;
+    readonly parent: CaseBlock;
+    readonly statements: NodeArray<Statement>;
+}
+
+export type CaseOrDefaultClause =
+    | CaseClause
+    | DefaultClause;
+
+export interface LabeledStatement extends Statement {
+    readonly kind: SyntaxKind.LabeledStatement;
+    readonly label: Identifier;
+    readonly statement: Statement;
+}
+
+export interface ThrowStatement extends Statement {
+    readonly kind: SyntaxKind.ThrowStatement;
+    readonly expression: Expression;
+}
+
+export interface TryStatement extends Statement {
+    readonly kind: SyntaxKind.TryStatement;
+    readonly tryBlock: Block;
+    readonly catchClause?: CatchClause;
+    readonly finallyBlock?: Block;
+}
+
+export interface CatchClause extends Node {
+    readonly kind: SyntaxKind.CatchClause;
+    readonly parent: TryStatement;
+    readonly variableDeclaration?: VariableDeclaration;
+    readonly block: Block;
+}
+
+export type ObjectTypeDeclaration =
+    | ClassLikeDeclaration
+    | InterfaceDeclaration
+    | TypeLiteralNode;
+
+export type DeclarationWithTypeParameters =
+    | DeclarationWithTypeParameterChildren
+    | JSDocTypedefTag
+    | JSDocCallbackTag
+    | JSDocSignature;
+
+export type DeclarationWithTypeParameterChildren =
+    | SignatureDeclaration
+    | ClassLikeDeclaration
+    | InterfaceDeclaration
+    | TypeAliasDeclaration
+    | JSDocTemplateTag;
+
+export interface ClassLikeDeclarationBase extends NamedDeclaration {
+    readonly kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
+    readonly name?: Identifier;
+    readonly typeParameters?: NodeArray<TypeParameterDeclaration>;
+    readonly heritageClauses?: NodeArray<HeritageClause>;
+    readonly members: NodeArray<ClassElement>;
+}
+
+export interface ClassDeclaration extends ClassLikeDeclarationBase, DeclarationStatement {
+    readonly kind: SyntaxKind.ClassDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    /** May be undefined in `export default class { ... }`. */
+    readonly name?: Identifier;
+}
+
+export interface ClassExpression extends ClassLikeDeclarationBase, PrimaryExpression {
+    readonly kind: SyntaxKind.ClassExpression;
+    readonly modifiers?: NodeArray<ModifierLike>;
+}
+
+export type ClassLikeDeclaration =
+    | ClassDeclaration
+    | ClassExpression;
+
+export interface ClassElement extends NamedDeclaration {
+    _classElementBrand: any;
+    readonly name?: PropertyName;
+}
+
+export interface TypeElement extends NamedDeclaration {
+    _typeElementBrand: any;
+    readonly name?: PropertyName;
+    readonly questionToken?: QuestionToken | undefined;
+}
+
+export interface InterfaceDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.InterfaceDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: Identifier;
+    readonly typeParameters?: NodeArray<TypeParameterDeclaration>;
+    readonly heritageClauses?: NodeArray<HeritageClause>;
+    readonly members: NodeArray<TypeElement>;
+}
+
+export interface HeritageClause extends Node {
+    readonly kind: SyntaxKind.HeritageClause;
+    readonly parent: InterfaceDeclaration | ClassLikeDeclaration;
+    readonly token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword;
+    readonly types: NodeArray<ExpressionWithTypeArguments>;
+}
+
+export interface TypeAliasDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.TypeAliasDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: Identifier;
+    readonly typeParameters?: NodeArray<TypeParameterDeclaration>;
+    readonly type: TypeNode;
+}
+
+export interface EnumMember extends NamedDeclaration {
+    readonly kind: SyntaxKind.EnumMember;
+    readonly parent: EnumDeclaration;
+    // This does include ComputedPropertyName, but the parser will give an error
+    // if it parses a ComputedPropertyName in an EnumMember
+    readonly name: PropertyName;
+    readonly initializer?: Expression;
+}
+
+export interface EnumDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.EnumDeclaration;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: Identifier;
+    readonly members: NodeArray<EnumMember>;
+}
+
+export type ModuleName =
+    | Identifier
+    | StringLiteral;
+
+export type ModuleBody =
+    | NamespaceBody
+    | JSDocNamespaceBody;
+
+export interface ModuleDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.ModuleDeclaration;
+    readonly parent: ModuleBody | SourceFile;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: ModuleName;
+    readonly body?: ModuleBody | JSDocNamespaceDeclaration;
+}
+
+export type NamespaceBody =
+    | ModuleBlock
+    | NamespaceDeclaration;
+
+export interface NamespaceDeclaration extends ModuleDeclaration {
+    readonly name: Identifier;
+    readonly body: NamespaceBody;
+}
+
+export type JSDocNamespaceBody =
+    | Identifier
+    | JSDocNamespaceDeclaration;
+
+export interface JSDocNamespaceDeclaration extends ModuleDeclaration {
+    readonly name: Identifier;
+    readonly body?: JSDocNamespaceBody;
+}
+
+export interface ModuleBlock extends Node, Statement {
+    readonly kind: SyntaxKind.ModuleBlock;
+    readonly parent: ModuleDeclaration;
+    readonly statements: NodeArray<Statement>;
+}
+
+export type ModuleReference =
+    | EntityName
+    | ExternalModuleReference;
+
+/**
+ * One of:
+ * - import x = require("mod");
+ * - import x = M.x;
+ */
+export interface ImportEqualsDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.ImportEqualsDeclaration;
+    readonly parent: SourceFile | ModuleBlock;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly name: Identifier;
+    readonly isTypeOnly: boolean;
+
+    // 'EntityName' for an internal module reference, 'ExternalModuleReference' for an external
+    // module reference.
+    readonly moduleReference: ModuleReference;
+}
+
+export interface ExternalModuleReference extends Node {
+    readonly kind: SyntaxKind.ExternalModuleReference;
+    readonly parent: ImportEqualsDeclaration;
+    readonly expression: Expression;
+}
+
+// In case of:
+// import "mod"  => importClause = undefined, moduleSpecifier = "mod"
+// In rest of the cases, module specifier is string literal corresponding to module
+// ImportClause information is shown at its declaration below.
+export interface ImportDeclaration extends Statement {
+    readonly kind: SyntaxKind.ImportDeclaration;
+    readonly parent: SourceFile | ModuleBlock;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly importClause?: ImportClause;
+    /** If this is not a StringLiteral it will be a grammar error. */
+    readonly moduleSpecifier: Expression;
+    /** @deprecated */ readonly assertClause?: AssertClause;
+    readonly attributes?: ImportAttributes;
+}
+
+export type NamedImportBindings =
+    | NamespaceImport
+    | NamedImports;
+
+export type NamedExportBindings =
+    | NamespaceExport
+    | NamedExports;
+
+// In case of:
+// import d from "mod" => name = d, namedBinding = undefined
+// import * as ns from "mod" => name = undefined, namedBinding: NamespaceImport = { name: ns }
+// import d, * as ns from "mod" => name = d, namedBinding: NamespaceImport = { name: ns }
+// import { a, b as x } from "mod" => name = undefined, namedBinding: NamedImports = { elements: [{ name: a }, { name: x, propertyName: b}]}
+// import d, { a, b as x } from "mod" => name = d, namedBinding: NamedImports = { elements: [{ name: a }, { name: x, propertyName: b}]}
+export interface ImportClause extends NamedDeclaration {
+    readonly kind: SyntaxKind.ImportClause;
+    readonly parent: ImportDeclaration | JSDocImportTag;
+    readonly isTypeOnly: boolean;
+    readonly name?: Identifier; // Default binding
+    readonly namedBindings?: NamedImportBindings;
+}
+
+/** @deprecated */
+export type AssertionKey = ImportAttributeName;
+
+/** @deprecated */
+export interface AssertEntry extends ImportAttribute {}
+
+/** @deprecated */
+export interface AssertClause extends ImportAttributes {}
+
+export type ImportAttributeName = Identifier | StringLiteral;
+
+export interface ImportAttribute extends Node {
+    readonly kind: SyntaxKind.ImportAttribute;
+    readonly parent: ImportAttributes;
+    readonly name: ImportAttributeName;
+    readonly value: Expression;
+}
+
+export interface ImportAttributes extends Node {
+    readonly token: SyntaxKind.WithKeyword | SyntaxKind.AssertKeyword;
+    readonly kind: SyntaxKind.ImportAttributes;
+    readonly parent: ImportDeclaration | ExportDeclaration;
+    readonly elements: NodeArray<ImportAttribute>;
+    readonly multiLine?: boolean;
+}
+
+export interface NamespaceImport extends NamedDeclaration {
+    readonly kind: SyntaxKind.NamespaceImport;
+    readonly parent: ImportClause;
+    readonly name: Identifier;
+}
+
+export interface NamespaceExport extends NamedDeclaration {
+    readonly kind: SyntaxKind.NamespaceExport;
+    readonly parent: ExportDeclaration;
+    readonly name: ModuleExportName;
+}
+
+export interface NamespaceExportDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.NamespaceExportDeclaration;
+    readonly name: Identifier;
+}
+
+export interface ExportDeclaration extends DeclarationStatement {
+    readonly kind: SyntaxKind.ExportDeclaration;
+    readonly parent: SourceFile | ModuleBlock;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly isTypeOnly: boolean;
+    /** Will not be assigned in the case of `export * from "foo";` */
+    readonly exportClause?: NamedExportBindings;
+    /** If this is not a StringLiteral it will be a grammar error. */
+    readonly moduleSpecifier?: Expression;
+    /** @deprecated */ readonly assertClause?: AssertClause;
+    readonly attributes?: ImportAttributes;
+}
+
+export interface NamedImports extends Node {
+    readonly kind: SyntaxKind.NamedImports;
+    readonly parent: ImportClause;
+    readonly elements: NodeArray<ImportSpecifier>;
+}
+
+export interface NamedExports extends Node {
+    readonly kind: SyntaxKind.NamedExports;
+    readonly parent: ExportDeclaration;
+    readonly elements: NodeArray<ExportSpecifier>;
+}
+
+export type NamedImportsOrExports = NamedImports | NamedExports;
+
+export interface ImportSpecifier extends NamedDeclaration {
+    readonly kind: SyntaxKind.ImportSpecifier;
+    readonly parent: NamedImports;
+    readonly propertyName?: ModuleExportName; // Name preceding "as" keyword (or undefined when "as" is absent)
+    readonly name: Identifier; // Declared name
+    readonly isTypeOnly: boolean;
+}
+
+export interface ExportSpecifier extends NamedDeclaration {
+    readonly kind: SyntaxKind.ExportSpecifier;
+    readonly parent: NamedExports;
+    readonly isTypeOnly: boolean;
+    readonly propertyName?: ModuleExportName; // Name preceding "as" keyword (or undefined when "as" is absent)
+    readonly name: ModuleExportName; // Declared name
+}
+
+export type ModuleExportName = Identifier | StringLiteral;
+
+export type ImportOrExportSpecifier =
+    | ImportSpecifier
+    | ExportSpecifier;
+
+export type TypeOnlyCompatibleAliasDeclaration =
+    | ImportClause
+    | ImportEqualsDeclaration
+    | NamespaceImport
+    | ImportOrExportSpecifier
+    | ExportDeclaration
+    | NamespaceExport;
+
+export type TypeOnlyImportDeclaration =
+    | ImportClause & { readonly isTypeOnly: true; readonly name: Identifier; }
+    | ImportEqualsDeclaration & { readonly isTypeOnly: true; }
+    | NamespaceImport & { readonly parent: ImportClause & { readonly isTypeOnly: true; }; }
+    | ImportSpecifier & ({ readonly isTypeOnly: true; } | { readonly parent: NamedImports & { readonly parent: ImportClause & { readonly isTypeOnly: true; }; }; });
+
+export type TypeOnlyExportDeclaration =
+    | ExportSpecifier & ({ readonly isTypeOnly: true; } | { readonly parent: NamedExports & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true; }; }; })
+    | ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; } // export * from "mod"
+    | NamespaceExport & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }; } // export * as ns from "mod"
+;
+
+export type TypeOnlyAliasDeclaration = TypeOnlyImportDeclaration | TypeOnlyExportDeclaration;
+
+/**
+ * This is either an `export =` or an `export default` declaration.
+ * Unless `isExportEquals` is set, this node was parsed as an `export default`.
+ */
+export interface ExportAssignment extends DeclarationStatement {
+    readonly kind: SyntaxKind.ExportAssignment;
+    readonly parent: SourceFile;
+    readonly modifiers?: NodeArray<ModifierLike>;
+    readonly isExportEquals?: boolean;
+    readonly expression: Expression;
+}
+
+// represents a top level: { type } expression in a JSDoc comment.
+export interface JSDocTypeExpression extends TypeNode {
+    readonly kind: SyntaxKind.JSDocTypeExpression;
+    readonly type: TypeNode;
+}
+
+export interface JSDocNameReference extends Node {
+    readonly kind: SyntaxKind.JSDocNameReference;
+    readonly name: EntityName | JSDocMemberName;
+}
+
+/** Class#method reference in JSDoc */
+export interface JSDocMemberName extends Node {
+    readonly kind: SyntaxKind.JSDocMemberName;
+    readonly left: EntityName | JSDocMemberName;
+    readonly right: Identifier;
+}
+
+export interface JSDocType extends TypeNode {
+    _jsDocTypeBrand: any;
+}
+
+export interface JSDocAllType extends JSDocType {
+    readonly kind: SyntaxKind.JSDocAllType;
+}
+
+export interface JSDocNonNullableType extends JSDocType {
+    readonly kind: SyntaxKind.JSDocNonNullableType;
+    readonly type: TypeNode;
+    readonly postfix: boolean;
+}
+
+export interface JSDocNullableType extends JSDocType {
+    readonly kind: SyntaxKind.JSDocNullableType;
+    readonly type: TypeNode;
+    readonly postfix: boolean;
+}
+
+export interface JSDocOptionalType extends JSDocType {
+    readonly kind: SyntaxKind.JSDocOptionalType;
+    readonly type: TypeNode;
+}
+
+export interface JSDocVariadicType extends JSDocType {
+    readonly kind: SyntaxKind.JSDocVariadicType;
+    readonly type: TypeNode;
+}
+
+export type JSDocTypeReferencingNode =
+    | JSDocVariadicType
+    | JSDocOptionalType
+    | JSDocNullableType
+    | JSDocNonNullableType;
+
+export interface JSDoc extends Node {
+    readonly kind: SyntaxKind.JSDoc;
+    readonly parent: HasJSDoc;
+    readonly tags?: NodeArray<JSDocTag>;
+    readonly comment?: string | NodeArray<JSDocComment>;
+}
+
+export interface JSDocTag extends Node {
+    readonly parent: JSDoc | JSDocTypeLiteral;
+    readonly tagName: Identifier;
+    readonly comment?: string | NodeArray<JSDocComment>;
+}
+
+export interface JSDocLink extends Node {
+    readonly kind: SyntaxKind.JSDocLink;
+    readonly name?: EntityName | JSDocMemberName;
+    text: string;
+}
+
+export interface JSDocLinkCode extends Node {
+    readonly kind: SyntaxKind.JSDocLinkCode;
+    readonly name?: EntityName | JSDocMemberName;
+    text: string;
+}
+
+export interface JSDocLinkPlain extends Node {
+    readonly kind: SyntaxKind.JSDocLinkPlain;
+    readonly name?: EntityName | JSDocMemberName;
+    text: string;
+}
+
+export type JSDocComment = JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain;
+
+export interface JSDocText extends Node {
+    readonly kind: SyntaxKind.JSDocText;
+    text: string;
+}
+
+export interface JSDocUnknownTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocTag;
+}
+
+/**
+ * Note that `@extends` is a synonym of `@augments`.
+ * Both tags are represented by this interface.
+ */
+export interface JSDocAugmentsTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocAugmentsTag;
+    readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression; };
+}
+
+export interface JSDocImplementsTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocImplementsTag;
+    readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression; };
+}
+
+export interface JSDocDeprecatedTag extends JSDocTag {
+    kind: SyntaxKind.JSDocDeprecatedTag;
+}
+
+export interface JSDocPublicTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocPublicTag;
+}
+
+export interface JSDocPrivateTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocPrivateTag;
+}
+
+export interface JSDocProtectedTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocProtectedTag;
+}
+
+export interface JSDocReadonlyTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocReadonlyTag;
+}
+
+export interface JSDocOverrideTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocOverrideTag;
+}
+
+export interface JSDocThisTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocThisTag;
+    readonly typeExpression: JSDocTypeExpression;
+}
+
+export interface JSDocTemplateTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocTemplateTag;
+    readonly constraint: JSDocTypeExpression | undefined;
+    readonly typeParameters: NodeArray<TypeParameterDeclaration>;
+}
+
+export interface JSDocSeeTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocSeeTag;
+    readonly name?: JSDocNameReference;
+}
+
+export interface JSDocReturnTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocReturnTag;
+    readonly typeExpression?: JSDocTypeExpression;
+}
+
+export interface JSDocTypeTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocTypeTag;
+    readonly typeExpression: JSDocTypeExpression;
+}
+
+export interface JSDocTypedefTag extends JSDocTag, NamedDeclaration {
+    readonly kind: SyntaxKind.JSDocTypedefTag;
+    readonly parent: JSDoc;
+    readonly fullName?: JSDocNamespaceDeclaration | Identifier;
+    readonly name?: Identifier;
+    readonly typeExpression?: JSDocTypeExpression | JSDocTypeLiteral;
+}
+
+export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration {
+    readonly kind: SyntaxKind.JSDocCallbackTag;
+    readonly parent: JSDoc;
+    readonly fullName?: JSDocNamespaceDeclaration | Identifier;
+    readonly name?: Identifier;
+    readonly typeExpression: JSDocSignature;
+}
+
+export interface JSDocOverloadTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocOverloadTag;
+    readonly parent: JSDoc;
+    readonly typeExpression: JSDocSignature;
+}
+
+export interface JSDocSignature extends JSDocType, Declaration {
+    readonly kind: SyntaxKind.JSDocSignature;
+    readonly typeParameters?: readonly JSDocTemplateTag[];
+    readonly parameters: readonly JSDocParameterTag[];
+    readonly type: JSDocReturnTag | undefined;
+}
+
+export interface JSDocPropertyLikeTag extends JSDocTag, Declaration {
+    readonly parent: JSDoc;
+    readonly name: EntityName;
+    readonly typeExpression?: JSDocTypeExpression;
+    /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */
+    readonly isNameFirst: boolean;
+    readonly isBracketed: boolean;
+}
+
+export interface JSDocPropertyTag extends JSDocPropertyLikeTag {
+    readonly kind: SyntaxKind.JSDocPropertyTag;
+}
+
+export interface JSDocParameterTag extends JSDocPropertyLikeTag {
+    readonly kind: SyntaxKind.JSDocParameterTag;
+}
+
+export interface JSDocTypeLiteral extends JSDocType, Declaration {
+    readonly kind: SyntaxKind.JSDocTypeLiteral;
+    readonly jsDocPropertyTags?: readonly JSDocPropertyLikeTag[];
+    /** If true, then this type literal represents an *array* of its type. */
+    readonly isArrayType: boolean;
+}
+
+export interface JSDocSatisfiesTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocSatisfiesTag;
+    readonly typeExpression: JSDocTypeExpression;
+}
+
+export interface JSDocImportTag extends JSDocTag {
+    readonly kind: SyntaxKind.JSDocImportTag;
+    readonly parent: JSDoc;
+    readonly importClause?: ImportClause;
+    readonly moduleSpecifier: Expression;
+    readonly attributes?: ImportAttributes;
+}
+
+export type HasJSDoc =
+    | AccessorDeclaration
+    | ArrowFunction
+    | BinaryExpression
+    | Block
+    | BreakStatement
+    | CallSignatureDeclaration
+    | CaseClause
+    | ClassLikeDeclaration
+    | ClassStaticBlockDeclaration
+    | ConstructorDeclaration
+    | ConstructorTypeNode
+    | ConstructSignatureDeclaration
+    | ContinueStatement
+    | DebuggerStatement
+    | DoStatement
+    | ElementAccessExpression
+    | EmptyStatement
+    | EndOfFile
+    | EnumDeclaration
+    | EnumMember
+    | ExportAssignment
+    | ExportDeclaration
+    | ExportSpecifier
+    | ExpressionStatement
+    | ForInStatement
+    | ForOfStatement
+    | ForStatement
+    | FunctionDeclaration
+    | FunctionExpression
+    | FunctionTypeNode
+    | Identifier
+    | IfStatement
+    | ImportDeclaration
+    | ImportEqualsDeclaration
+    | IndexSignatureDeclaration
+    | InterfaceDeclaration
+    | JSDocSignature
+    | LabeledStatement
+    | MethodDeclaration
+    | MethodSignature
+    | ModuleDeclaration
+    | NamedTupleMember
+    | NamespaceExportDeclaration
+    | ObjectLiteralExpression
+    | ParameterDeclaration
+    | ParenthesizedExpression
+    | PropertyAccessExpression
+    | PropertyAssignment
+    | PropertyDeclaration
+    | PropertySignature
+    | ReturnStatement
+    | SemicolonClassElement
+    | ShorthandPropertyAssignment
+    | SpreadAssignment
+    | SwitchStatement
+    | ThrowStatement
+    | TryStatement
+    | TypeAliasDeclaration
+    | TypeParameterDeclaration
+    | VariableDeclaration
+    | VariableStatement
+    | WhileStatement
+    | WithStatement;
diff --git a/_packages/ast/src/syntaxKind.enum.ts b/_packages/ast/src/syntaxKind.enum.ts
new file mode 100644
index 0000000000..31fdf059d7
--- /dev/null
+++ b/_packages/ast/src/syntaxKind.enum.ts
@@ -0,0 +1,352 @@
+export enum SyntaxKind {
+    Unknown,
+    EndOfFile,
+    SingleLineCommentTrivia,
+    MultiLineCommentTrivia,
+    NewLineTrivia,
+    WhitespaceTrivia,
+    ConflictMarkerTrivia,
+    NonTextFileMarkerTrivia,
+    NumericLiteral,
+    BigIntLiteral,
+    StringLiteral,
+    JsxText,
+    JsxTextAllWhiteSpaces,
+    RegularExpressionLiteral,
+    NoSubstitutionTemplateLiteral,
+    TemplateHead,
+    TemplateMiddle,
+    TemplateTail,
+    OpenBraceToken,
+    CloseBraceToken,
+    OpenParenToken,
+    CloseParenToken,
+    OpenBracketToken,
+    CloseBracketToken,
+    DotToken,
+    DotDotDotToken,
+    SemicolonToken,
+    CommaToken,
+    QuestionDotToken,
+    LessThanToken,
+    LessThanSlashToken,
+    GreaterThanToken,
+    LessThanEqualsToken,
+    GreaterThanEqualsToken,
+    EqualsEqualsToken,
+    ExclamationEqualsToken,
+    EqualsEqualsEqualsToken,
+    ExclamationEqualsEqualsToken,
+    EqualsGreaterThanToken,
+    PlusToken,
+    MinusToken,
+    AsteriskToken,
+    AsteriskAsteriskToken,
+    SlashToken,
+    PercentToken,
+    PlusPlusToken,
+    MinusMinusToken,
+    LessThanLessThanToken,
+    GreaterThanGreaterThanToken,
+    GreaterThanGreaterThanGreaterThanToken,
+    AmpersandToken,
+    BarToken,
+    CaretToken,
+    ExclamationToken,
+    TildeToken,
+    AmpersandAmpersandToken,
+    BarBarToken,
+    QuestionToken,
+    ColonToken,
+    AtToken,
+    QuestionQuestionToken,
+    BacktickToken,
+    HashToken,
+    EqualsToken,
+    PlusEqualsToken,
+    MinusEqualsToken,
+    AsteriskEqualsToken,
+    AsteriskAsteriskEqualsToken,
+    SlashEqualsToken,
+    PercentEqualsToken,
+    LessThanLessThanEqualsToken,
+    GreaterThanGreaterThanEqualsToken,
+    GreaterThanGreaterThanGreaterThanEqualsToken,
+    AmpersandEqualsToken,
+    BarEqualsToken,
+    BarBarEqualsToken,
+    AmpersandAmpersandEqualsToken,
+    QuestionQuestionEqualsToken,
+    CaretEqualsToken,
+    Identifier,
+    PrivateIdentifier,
+    JSDocCommentTextToken,
+    BreakKeyword,
+    CaseKeyword,
+    CatchKeyword,
+    ClassKeyword,
+    ConstKeyword,
+    ContinueKeyword,
+    DebuggerKeyword,
+    DefaultKeyword,
+    DeleteKeyword,
+    DoKeyword,
+    ElseKeyword,
+    EnumKeyword,
+    ExportKeyword,
+    ExtendsKeyword,
+    FalseKeyword,
+    FinallyKeyword,
+    ForKeyword,
+    FunctionKeyword,
+    IfKeyword,
+    ImportKeyword,
+    InKeyword,
+    InstanceOfKeyword,
+    NewKeyword,
+    NullKeyword,
+    ReturnKeyword,
+    SuperKeyword,
+    SwitchKeyword,
+    ThisKeyword,
+    ThrowKeyword,
+    TrueKeyword,
+    TryKeyword,
+    TypeOfKeyword,
+    VarKeyword,
+    VoidKeyword,
+    WhileKeyword,
+    WithKeyword,
+    ImplementsKeyword,
+    InterfaceKeyword,
+    LetKeyword,
+    PackageKeyword,
+    PrivateKeyword,
+    ProtectedKeyword,
+    PublicKeyword,
+    StaticKeyword,
+    YieldKeyword,
+    AbstractKeyword,
+    AccessorKeyword,
+    AsKeyword,
+    AssertsKeyword,
+    AssertKeyword,
+    AnyKeyword,
+    AsyncKeyword,
+    AwaitKeyword,
+    BooleanKeyword,
+    ConstructorKeyword,
+    DeclareKeyword,
+    GetKeyword,
+    ImmediateKeyword,
+    InferKeyword,
+    IntrinsicKeyword,
+    IsKeyword,
+    KeyOfKeyword,
+    ModuleKeyword,
+    NamespaceKeyword,
+    NeverKeyword,
+    OutKeyword,
+    ReadonlyKeyword,
+    RequireKeyword,
+    NumberKeyword,
+    ObjectKeyword,
+    SatisfiesKeyword,
+    SetKeyword,
+    StringKeyword,
+    SymbolKeyword,
+    TypeKeyword,
+    UndefinedKeyword,
+    UniqueKeyword,
+    UnknownKeyword,
+    UsingKeyword,
+    FromKeyword,
+    GlobalKeyword,
+    BigIntKeyword,
+    OverrideKeyword,
+    OfKeyword,
+    QualifiedName,
+    ComputedPropertyName,
+    TypeParameter,
+    Parameter,
+    Decorator,
+    PropertySignature,
+    PropertyDeclaration,
+    MethodSignature,
+    MethodDeclaration,
+    ClassStaticBlockDeclaration,
+    Constructor,
+    GetAccessor,
+    SetAccessor,
+    CallSignature,
+    ConstructSignature,
+    IndexSignature,
+    TypePredicate,
+    TypeReference,
+    FunctionType,
+    ConstructorType,
+    TypeQuery,
+    TypeLiteral,
+    ArrayType,
+    TupleType,
+    OptionalType,
+    RestType,
+    UnionType,
+    IntersectionType,
+    ConditionalType,
+    InferType,
+    ParenthesizedType,
+    ThisType,
+    TypeOperator,
+    IndexedAccessType,
+    MappedType,
+    LiteralType,
+    NamedTupleMember,
+    TemplateLiteralType,
+    TemplateLiteralTypeSpan,
+    ImportType,
+    ObjectBindingPattern,
+    ArrayBindingPattern,
+    BindingElement,
+    ArrayLiteralExpression,
+    ObjectLiteralExpression,
+    PropertyAccessExpression,
+    ElementAccessExpression,
+    CallExpression,
+    NewExpression,
+    TaggedTemplateExpression,
+    TypeAssertionExpression,
+    ParenthesizedExpression,
+    FunctionExpression,
+    ArrowFunction,
+    DeleteExpression,
+    TypeOfExpression,
+    VoidExpression,
+    AwaitExpression,
+    PrefixUnaryExpression,
+    PostfixUnaryExpression,
+    BinaryExpression,
+    ConditionalExpression,
+    TemplateExpression,
+    YieldExpression,
+    SpreadElement,
+    ClassExpression,
+    OmittedExpression,
+    ExpressionWithTypeArguments,
+    AsExpression,
+    NonNullExpression,
+    MetaProperty,
+    SyntheticExpression,
+    SatisfiesExpression,
+    TemplateSpan,
+    SemicolonClassElement,
+    Block,
+    EmptyStatement,
+    VariableStatement,
+    ExpressionStatement,
+    IfStatement,
+    DoStatement,
+    WhileStatement,
+    ForStatement,
+    ForInStatement,
+    ForOfStatement,
+    ContinueStatement,
+    BreakStatement,
+    ReturnStatement,
+    WithStatement,
+    SwitchStatement,
+    LabeledStatement,
+    ThrowStatement,
+    TryStatement,
+    DebuggerStatement,
+    VariableDeclaration,
+    VariableDeclarationList,
+    FunctionDeclaration,
+    ClassDeclaration,
+    InterfaceDeclaration,
+    TypeAliasDeclaration,
+    EnumDeclaration,
+    ModuleDeclaration,
+    ModuleBlock,
+    CaseBlock,
+    NamespaceExportDeclaration,
+    ImportEqualsDeclaration,
+    ImportDeclaration,
+    ImportClause,
+    NamespaceImport,
+    NamedImports,
+    ImportSpecifier,
+    ExportAssignment,
+    ExportDeclaration,
+    NamedExports,
+    NamespaceExport,
+    ExportSpecifier,
+    MissingDeclaration,
+    ExternalModuleReference,
+    JsxElement,
+    JsxSelfClosingElement,
+    JsxOpeningElement,
+    JsxClosingElement,
+    JsxFragment,
+    JsxOpeningFragment,
+    JsxClosingFragment,
+    JsxAttribute,
+    JsxAttributes,
+    JsxSpreadAttribute,
+    JsxExpression,
+    JsxNamespacedName,
+    CaseClause,
+    DefaultClause,
+    HeritageClause,
+    CatchClause,
+    ImportAttributes,
+    ImportAttribute,
+    PropertyAssignment,
+    ShorthandPropertyAssignment,
+    SpreadAssignment,
+    EnumMember,
+    SourceFile,
+    Bundle,
+    JSDocTypeExpression,
+    JSDocNameReference,
+    JSDocMemberName,
+    JSDocAllType,
+    JSDocNullableType,
+    JSDocNonNullableType,
+    JSDocOptionalType,
+    JSDocVariadicType,
+    JSDoc,
+    JSDocText,
+    JSDocTypeLiteral,
+    JSDocSignature,
+    JSDocLink,
+    JSDocLinkCode,
+    JSDocLinkPlain,
+    JSDocTag,
+    JSDocAugmentsTag,
+    JSDocImplementsTag,
+    JSDocDeprecatedTag,
+    JSDocPublicTag,
+    JSDocPrivateTag,
+    JSDocProtectedTag,
+    JSDocReadonlyTag,
+    JSDocOverrideTag,
+    JSDocCallbackTag,
+    JSDocOverloadTag,
+    JSDocParameterTag,
+    JSDocReturnTag,
+    JSDocThisTag,
+    JSDocTypeTag,
+    JSDocTemplateTag,
+    JSDocTypedefTag,
+    JSDocSeeTag,
+    JSDocPropertyTag,
+    JSDocSatisfiesTag,
+    JSDocImportTag,
+    SyntaxList,
+    NotEmittedStatement,
+    PartiallyEmittedExpression,
+    CommaListExpression,
+    SyntheticReferenceExpression,
+    Count,
+}
diff --git a/_packages/ast/src/syntaxKind.ts b/_packages/ast/src/syntaxKind.ts
new file mode 100644
index 0000000000..4d86197027
--- /dev/null
+++ b/_packages/ast/src/syntaxKind.ts
@@ -0,0 +1,355 @@
+export var SyntaxKind: any;
+(function (SyntaxKind) {
+    SyntaxKind[SyntaxKind["Unknown"] = 0] = "Unknown";
+    SyntaxKind[SyntaxKind["EndOfFile"] = 1] = "EndOfFile";
+    SyntaxKind[SyntaxKind["SingleLineCommentTrivia"] = 2] = "SingleLineCommentTrivia";
+    SyntaxKind[SyntaxKind["MultiLineCommentTrivia"] = 3] = "MultiLineCommentTrivia";
+    SyntaxKind[SyntaxKind["NewLineTrivia"] = 4] = "NewLineTrivia";
+    SyntaxKind[SyntaxKind["WhitespaceTrivia"] = 5] = "WhitespaceTrivia";
+    SyntaxKind[SyntaxKind["ConflictMarkerTrivia"] = 6] = "ConflictMarkerTrivia";
+    SyntaxKind[SyntaxKind["NonTextFileMarkerTrivia"] = 7] = "NonTextFileMarkerTrivia";
+    SyntaxKind[SyntaxKind["NumericLiteral"] = 8] = "NumericLiteral";
+    SyntaxKind[SyntaxKind["BigIntLiteral"] = 9] = "BigIntLiteral";
+    SyntaxKind[SyntaxKind["StringLiteral"] = 10] = "StringLiteral";
+    SyntaxKind[SyntaxKind["JsxText"] = 11] = "JsxText";
+    SyntaxKind[SyntaxKind["JsxTextAllWhiteSpaces"] = 12] = "JsxTextAllWhiteSpaces";
+    SyntaxKind[SyntaxKind["RegularExpressionLiteral"] = 13] = "RegularExpressionLiteral";
+    SyntaxKind[SyntaxKind["NoSubstitutionTemplateLiteral"] = 14] = "NoSubstitutionTemplateLiteral";
+    SyntaxKind[SyntaxKind["TemplateHead"] = 15] = "TemplateHead";
+    SyntaxKind[SyntaxKind["TemplateMiddle"] = 16] = "TemplateMiddle";
+    SyntaxKind[SyntaxKind["TemplateTail"] = 17] = "TemplateTail";
+    SyntaxKind[SyntaxKind["OpenBraceToken"] = 18] = "OpenBraceToken";
+    SyntaxKind[SyntaxKind["CloseBraceToken"] = 19] = "CloseBraceToken";
+    SyntaxKind[SyntaxKind["OpenParenToken"] = 20] = "OpenParenToken";
+    SyntaxKind[SyntaxKind["CloseParenToken"] = 21] = "CloseParenToken";
+    SyntaxKind[SyntaxKind["OpenBracketToken"] = 22] = "OpenBracketToken";
+    SyntaxKind[SyntaxKind["CloseBracketToken"] = 23] = "CloseBracketToken";
+    SyntaxKind[SyntaxKind["DotToken"] = 24] = "DotToken";
+    SyntaxKind[SyntaxKind["DotDotDotToken"] = 25] = "DotDotDotToken";
+    SyntaxKind[SyntaxKind["SemicolonToken"] = 26] = "SemicolonToken";
+    SyntaxKind[SyntaxKind["CommaToken"] = 27] = "CommaToken";
+    SyntaxKind[SyntaxKind["QuestionDotToken"] = 28] = "QuestionDotToken";
+    SyntaxKind[SyntaxKind["LessThanToken"] = 29] = "LessThanToken";
+    SyntaxKind[SyntaxKind["LessThanSlashToken"] = 30] = "LessThanSlashToken";
+    SyntaxKind[SyntaxKind["GreaterThanToken"] = 31] = "GreaterThanToken";
+    SyntaxKind[SyntaxKind["LessThanEqualsToken"] = 32] = "LessThanEqualsToken";
+    SyntaxKind[SyntaxKind["GreaterThanEqualsToken"] = 33] = "GreaterThanEqualsToken";
+    SyntaxKind[SyntaxKind["EqualsEqualsToken"] = 34] = "EqualsEqualsToken";
+    SyntaxKind[SyntaxKind["ExclamationEqualsToken"] = 35] = "ExclamationEqualsToken";
+    SyntaxKind[SyntaxKind["EqualsEqualsEqualsToken"] = 36] = "EqualsEqualsEqualsToken";
+    SyntaxKind[SyntaxKind["ExclamationEqualsEqualsToken"] = 37] = "ExclamationEqualsEqualsToken";
+    SyntaxKind[SyntaxKind["EqualsGreaterThanToken"] = 38] = "EqualsGreaterThanToken";
+    SyntaxKind[SyntaxKind["PlusToken"] = 39] = "PlusToken";
+    SyntaxKind[SyntaxKind["MinusToken"] = 40] = "MinusToken";
+    SyntaxKind[SyntaxKind["AsteriskToken"] = 41] = "AsteriskToken";
+    SyntaxKind[SyntaxKind["AsteriskAsteriskToken"] = 42] = "AsteriskAsteriskToken";
+    SyntaxKind[SyntaxKind["SlashToken"] = 43] = "SlashToken";
+    SyntaxKind[SyntaxKind["PercentToken"] = 44] = "PercentToken";
+    SyntaxKind[SyntaxKind["PlusPlusToken"] = 45] = "PlusPlusToken";
+    SyntaxKind[SyntaxKind["MinusMinusToken"] = 46] = "MinusMinusToken";
+    SyntaxKind[SyntaxKind["LessThanLessThanToken"] = 47] = "LessThanLessThanToken";
+    SyntaxKind[SyntaxKind["GreaterThanGreaterThanToken"] = 48] = "GreaterThanGreaterThanToken";
+    SyntaxKind[SyntaxKind["GreaterThanGreaterThanGreaterThanToken"] = 49] = "GreaterThanGreaterThanGreaterThanToken";
+    SyntaxKind[SyntaxKind["AmpersandToken"] = 50] = "AmpersandToken";
+    SyntaxKind[SyntaxKind["BarToken"] = 51] = "BarToken";
+    SyntaxKind[SyntaxKind["CaretToken"] = 52] = "CaretToken";
+    SyntaxKind[SyntaxKind["ExclamationToken"] = 53] = "ExclamationToken";
+    SyntaxKind[SyntaxKind["TildeToken"] = 54] = "TildeToken";
+    SyntaxKind[SyntaxKind["AmpersandAmpersandToken"] = 55] = "AmpersandAmpersandToken";
+    SyntaxKind[SyntaxKind["BarBarToken"] = 56] = "BarBarToken";
+    SyntaxKind[SyntaxKind["QuestionToken"] = 57] = "QuestionToken";
+    SyntaxKind[SyntaxKind["ColonToken"] = 58] = "ColonToken";
+    SyntaxKind[SyntaxKind["AtToken"] = 59] = "AtToken";
+    SyntaxKind[SyntaxKind["QuestionQuestionToken"] = 60] = "QuestionQuestionToken";
+    SyntaxKind[SyntaxKind["BacktickToken"] = 61] = "BacktickToken";
+    SyntaxKind[SyntaxKind["HashToken"] = 62] = "HashToken";
+    SyntaxKind[SyntaxKind["EqualsToken"] = 63] = "EqualsToken";
+    SyntaxKind[SyntaxKind["PlusEqualsToken"] = 64] = "PlusEqualsToken";
+    SyntaxKind[SyntaxKind["MinusEqualsToken"] = 65] = "MinusEqualsToken";
+    SyntaxKind[SyntaxKind["AsteriskEqualsToken"] = 66] = "AsteriskEqualsToken";
+    SyntaxKind[SyntaxKind["AsteriskAsteriskEqualsToken"] = 67] = "AsteriskAsteriskEqualsToken";
+    SyntaxKind[SyntaxKind["SlashEqualsToken"] = 68] = "SlashEqualsToken";
+    SyntaxKind[SyntaxKind["PercentEqualsToken"] = 69] = "PercentEqualsToken";
+    SyntaxKind[SyntaxKind["LessThanLessThanEqualsToken"] = 70] = "LessThanLessThanEqualsToken";
+    SyntaxKind[SyntaxKind["GreaterThanGreaterThanEqualsToken"] = 71] = "GreaterThanGreaterThanEqualsToken";
+    SyntaxKind[
+        SyntaxKind["GreaterThanGreaterThanGreaterThanEqualsToken"] = 72
+    ] = "GreaterThanGreaterThanGreaterThanEqualsToken";
+    SyntaxKind[SyntaxKind["AmpersandEqualsToken"] = 73] = "AmpersandEqualsToken";
+    SyntaxKind[SyntaxKind["BarEqualsToken"] = 74] = "BarEqualsToken";
+    SyntaxKind[SyntaxKind["BarBarEqualsToken"] = 75] = "BarBarEqualsToken";
+    SyntaxKind[SyntaxKind["AmpersandAmpersandEqualsToken"] = 76] = "AmpersandAmpersandEqualsToken";
+    SyntaxKind[SyntaxKind["QuestionQuestionEqualsToken"] = 77] = "QuestionQuestionEqualsToken";
+    SyntaxKind[SyntaxKind["CaretEqualsToken"] = 78] = "CaretEqualsToken";
+    SyntaxKind[SyntaxKind["Identifier"] = 79] = "Identifier";
+    SyntaxKind[SyntaxKind["PrivateIdentifier"] = 80] = "PrivateIdentifier";
+    SyntaxKind[SyntaxKind["JSDocCommentTextToken"] = 81] = "JSDocCommentTextToken";
+    SyntaxKind[SyntaxKind["BreakKeyword"] = 82] = "BreakKeyword";
+    SyntaxKind[SyntaxKind["CaseKeyword"] = 83] = "CaseKeyword";
+    SyntaxKind[SyntaxKind["CatchKeyword"] = 84] = "CatchKeyword";
+    SyntaxKind[SyntaxKind["ClassKeyword"] = 85] = "ClassKeyword";
+    SyntaxKind[SyntaxKind["ConstKeyword"] = 86] = "ConstKeyword";
+    SyntaxKind[SyntaxKind["ContinueKeyword"] = 87] = "ContinueKeyword";
+    SyntaxKind[SyntaxKind["DebuggerKeyword"] = 88] = "DebuggerKeyword";
+    SyntaxKind[SyntaxKind["DefaultKeyword"] = 89] = "DefaultKeyword";
+    SyntaxKind[SyntaxKind["DeleteKeyword"] = 90] = "DeleteKeyword";
+    SyntaxKind[SyntaxKind["DoKeyword"] = 91] = "DoKeyword";
+    SyntaxKind[SyntaxKind["ElseKeyword"] = 92] = "ElseKeyword";
+    SyntaxKind[SyntaxKind["EnumKeyword"] = 93] = "EnumKeyword";
+    SyntaxKind[SyntaxKind["ExportKeyword"] = 94] = "ExportKeyword";
+    SyntaxKind[SyntaxKind["ExtendsKeyword"] = 95] = "ExtendsKeyword";
+    SyntaxKind[SyntaxKind["FalseKeyword"] = 96] = "FalseKeyword";
+    SyntaxKind[SyntaxKind["FinallyKeyword"] = 97] = "FinallyKeyword";
+    SyntaxKind[SyntaxKind["ForKeyword"] = 98] = "ForKeyword";
+    SyntaxKind[SyntaxKind["FunctionKeyword"] = 99] = "FunctionKeyword";
+    SyntaxKind[SyntaxKind["IfKeyword"] = 100] = "IfKeyword";
+    SyntaxKind[SyntaxKind["ImportKeyword"] = 101] = "ImportKeyword";
+    SyntaxKind[SyntaxKind["InKeyword"] = 102] = "InKeyword";
+    SyntaxKind[SyntaxKind["InstanceOfKeyword"] = 103] = "InstanceOfKeyword";
+    SyntaxKind[SyntaxKind["NewKeyword"] = 104] = "NewKeyword";
+    SyntaxKind[SyntaxKind["NullKeyword"] = 105] = "NullKeyword";
+    SyntaxKind[SyntaxKind["ReturnKeyword"] = 106] = "ReturnKeyword";
+    SyntaxKind[SyntaxKind["SuperKeyword"] = 107] = "SuperKeyword";
+    SyntaxKind[SyntaxKind["SwitchKeyword"] = 108] = "SwitchKeyword";
+    SyntaxKind[SyntaxKind["ThisKeyword"] = 109] = "ThisKeyword";
+    SyntaxKind[SyntaxKind["ThrowKeyword"] = 110] = "ThrowKeyword";
+    SyntaxKind[SyntaxKind["TrueKeyword"] = 111] = "TrueKeyword";
+    SyntaxKind[SyntaxKind["TryKeyword"] = 112] = "TryKeyword";
+    SyntaxKind[SyntaxKind["TypeOfKeyword"] = 113] = "TypeOfKeyword";
+    SyntaxKind[SyntaxKind["VarKeyword"] = 114] = "VarKeyword";
+    SyntaxKind[SyntaxKind["VoidKeyword"] = 115] = "VoidKeyword";
+    SyntaxKind[SyntaxKind["WhileKeyword"] = 116] = "WhileKeyword";
+    SyntaxKind[SyntaxKind["WithKeyword"] = 117] = "WithKeyword";
+    SyntaxKind[SyntaxKind["ImplementsKeyword"] = 118] = "ImplementsKeyword";
+    SyntaxKind[SyntaxKind["InterfaceKeyword"] = 119] = "InterfaceKeyword";
+    SyntaxKind[SyntaxKind["LetKeyword"] = 120] = "LetKeyword";
+    SyntaxKind[SyntaxKind["PackageKeyword"] = 121] = "PackageKeyword";
+    SyntaxKind[SyntaxKind["PrivateKeyword"] = 122] = "PrivateKeyword";
+    SyntaxKind[SyntaxKind["ProtectedKeyword"] = 123] = "ProtectedKeyword";
+    SyntaxKind[SyntaxKind["PublicKeyword"] = 124] = "PublicKeyword";
+    SyntaxKind[SyntaxKind["StaticKeyword"] = 125] = "StaticKeyword";
+    SyntaxKind[SyntaxKind["YieldKeyword"] = 126] = "YieldKeyword";
+    SyntaxKind[SyntaxKind["AbstractKeyword"] = 127] = "AbstractKeyword";
+    SyntaxKind[SyntaxKind["AccessorKeyword"] = 128] = "AccessorKeyword";
+    SyntaxKind[SyntaxKind["AsKeyword"] = 129] = "AsKeyword";
+    SyntaxKind[SyntaxKind["AssertsKeyword"] = 130] = "AssertsKeyword";
+    SyntaxKind[SyntaxKind["AssertKeyword"] = 131] = "AssertKeyword";
+    SyntaxKind[SyntaxKind["AnyKeyword"] = 132] = "AnyKeyword";
+    SyntaxKind[SyntaxKind["AsyncKeyword"] = 133] = "AsyncKeyword";
+    SyntaxKind[SyntaxKind["AwaitKeyword"] = 134] = "AwaitKeyword";
+    SyntaxKind[SyntaxKind["BooleanKeyword"] = 135] = "BooleanKeyword";
+    SyntaxKind[SyntaxKind["ConstructorKeyword"] = 136] = "ConstructorKeyword";
+    SyntaxKind[SyntaxKind["DeclareKeyword"] = 137] = "DeclareKeyword";
+    SyntaxKind[SyntaxKind["GetKeyword"] = 138] = "GetKeyword";
+    SyntaxKind[SyntaxKind["ImmediateKeyword"] = 139] = "ImmediateKeyword";
+    SyntaxKind[SyntaxKind["InferKeyword"] = 140] = "InferKeyword";
+    SyntaxKind[SyntaxKind["IntrinsicKeyword"] = 141] = "IntrinsicKeyword";
+    SyntaxKind[SyntaxKind["IsKeyword"] = 142] = "IsKeyword";
+    SyntaxKind[SyntaxKind["KeyOfKeyword"] = 143] = "KeyOfKeyword";
+    SyntaxKind[SyntaxKind["ModuleKeyword"] = 144] = "ModuleKeyword";
+    SyntaxKind[SyntaxKind["NamespaceKeyword"] = 145] = "NamespaceKeyword";
+    SyntaxKind[SyntaxKind["NeverKeyword"] = 146] = "NeverKeyword";
+    SyntaxKind[SyntaxKind["OutKeyword"] = 147] = "OutKeyword";
+    SyntaxKind[SyntaxKind["ReadonlyKeyword"] = 148] = "ReadonlyKeyword";
+    SyntaxKind[SyntaxKind["RequireKeyword"] = 149] = "RequireKeyword";
+    SyntaxKind[SyntaxKind["NumberKeyword"] = 150] = "NumberKeyword";
+    SyntaxKind[SyntaxKind["ObjectKeyword"] = 151] = "ObjectKeyword";
+    SyntaxKind[SyntaxKind["SatisfiesKeyword"] = 152] = "SatisfiesKeyword";
+    SyntaxKind[SyntaxKind["SetKeyword"] = 153] = "SetKeyword";
+    SyntaxKind[SyntaxKind["StringKeyword"] = 154] = "StringKeyword";
+    SyntaxKind[SyntaxKind["SymbolKeyword"] = 155] = "SymbolKeyword";
+    SyntaxKind[SyntaxKind["TypeKeyword"] = 156] = "TypeKeyword";
+    SyntaxKind[SyntaxKind["UndefinedKeyword"] = 157] = "UndefinedKeyword";
+    SyntaxKind[SyntaxKind["UniqueKeyword"] = 158] = "UniqueKeyword";
+    SyntaxKind[SyntaxKind["UnknownKeyword"] = 159] = "UnknownKeyword";
+    SyntaxKind[SyntaxKind["UsingKeyword"] = 160] = "UsingKeyword";
+    SyntaxKind[SyntaxKind["FromKeyword"] = 161] = "FromKeyword";
+    SyntaxKind[SyntaxKind["GlobalKeyword"] = 162] = "GlobalKeyword";
+    SyntaxKind[SyntaxKind["BigIntKeyword"] = 163] = "BigIntKeyword";
+    SyntaxKind[SyntaxKind["OverrideKeyword"] = 164] = "OverrideKeyword";
+    SyntaxKind[SyntaxKind["OfKeyword"] = 165] = "OfKeyword";
+    SyntaxKind[SyntaxKind["QualifiedName"] = 166] = "QualifiedName";
+    SyntaxKind[SyntaxKind["ComputedPropertyName"] = 167] = "ComputedPropertyName";
+    SyntaxKind[SyntaxKind["TypeParameter"] = 168] = "TypeParameter";
+    SyntaxKind[SyntaxKind["Parameter"] = 169] = "Parameter";
+    SyntaxKind[SyntaxKind["Decorator"] = 170] = "Decorator";
+    SyntaxKind[SyntaxKind["PropertySignature"] = 171] = "PropertySignature";
+    SyntaxKind[SyntaxKind["PropertyDeclaration"] = 172] = "PropertyDeclaration";
+    SyntaxKind[SyntaxKind["MethodSignature"] = 173] = "MethodSignature";
+    SyntaxKind[SyntaxKind["MethodDeclaration"] = 174] = "MethodDeclaration";
+    SyntaxKind[SyntaxKind["ClassStaticBlockDeclaration"] = 175] = "ClassStaticBlockDeclaration";
+    SyntaxKind[SyntaxKind["Constructor"] = 176] = "Constructor";
+    SyntaxKind[SyntaxKind["GetAccessor"] = 177] = "GetAccessor";
+    SyntaxKind[SyntaxKind["SetAccessor"] = 178] = "SetAccessor";
+    SyntaxKind[SyntaxKind["CallSignature"] = 179] = "CallSignature";
+    SyntaxKind[SyntaxKind["ConstructSignature"] = 180] = "ConstructSignature";
+    SyntaxKind[SyntaxKind["IndexSignature"] = 181] = "IndexSignature";
+    SyntaxKind[SyntaxKind["TypePredicate"] = 182] = "TypePredicate";
+    SyntaxKind[SyntaxKind["TypeReference"] = 183] = "TypeReference";
+    SyntaxKind[SyntaxKind["FunctionType"] = 184] = "FunctionType";
+    SyntaxKind[SyntaxKind["ConstructorType"] = 185] = "ConstructorType";
+    SyntaxKind[SyntaxKind["TypeQuery"] = 186] = "TypeQuery";
+    SyntaxKind[SyntaxKind["TypeLiteral"] = 187] = "TypeLiteral";
+    SyntaxKind[SyntaxKind["ArrayType"] = 188] = "ArrayType";
+    SyntaxKind[SyntaxKind["TupleType"] = 189] = "TupleType";
+    SyntaxKind[SyntaxKind["OptionalType"] = 190] = "OptionalType";
+    SyntaxKind[SyntaxKind["RestType"] = 191] = "RestType";
+    SyntaxKind[SyntaxKind["UnionType"] = 192] = "UnionType";
+    SyntaxKind[SyntaxKind["IntersectionType"] = 193] = "IntersectionType";
+    SyntaxKind[SyntaxKind["ConditionalType"] = 194] = "ConditionalType";
+    SyntaxKind[SyntaxKind["InferType"] = 195] = "InferType";
+    SyntaxKind[SyntaxKind["ParenthesizedType"] = 196] = "ParenthesizedType";
+    SyntaxKind[SyntaxKind["ThisType"] = 197] = "ThisType";
+    SyntaxKind[SyntaxKind["TypeOperator"] = 198] = "TypeOperator";
+    SyntaxKind[SyntaxKind["IndexedAccessType"] = 199] = "IndexedAccessType";
+    SyntaxKind[SyntaxKind["MappedType"] = 200] = "MappedType";
+    SyntaxKind[SyntaxKind["LiteralType"] = 201] = "LiteralType";
+    SyntaxKind[SyntaxKind["NamedTupleMember"] = 202] = "NamedTupleMember";
+    SyntaxKind[SyntaxKind["TemplateLiteralType"] = 203] = "TemplateLiteralType";
+    SyntaxKind[SyntaxKind["TemplateLiteralTypeSpan"] = 204] = "TemplateLiteralTypeSpan";
+    SyntaxKind[SyntaxKind["ImportType"] = 205] = "ImportType";
+    SyntaxKind[SyntaxKind["ObjectBindingPattern"] = 206] = "ObjectBindingPattern";
+    SyntaxKind[SyntaxKind["ArrayBindingPattern"] = 207] = "ArrayBindingPattern";
+    SyntaxKind[SyntaxKind["BindingElement"] = 208] = "BindingElement";
+    SyntaxKind[SyntaxKind["ArrayLiteralExpression"] = 209] = "ArrayLiteralExpression";
+    SyntaxKind[SyntaxKind["ObjectLiteralExpression"] = 210] = "ObjectLiteralExpression";
+    SyntaxKind[SyntaxKind["PropertyAccessExpression"] = 211] = "PropertyAccessExpression";
+    SyntaxKind[SyntaxKind["ElementAccessExpression"] = 212] = "ElementAccessExpression";
+    SyntaxKind[SyntaxKind["CallExpression"] = 213] = "CallExpression";
+    SyntaxKind[SyntaxKind["NewExpression"] = 214] = "NewExpression";
+    SyntaxKind[SyntaxKind["TaggedTemplateExpression"] = 215] = "TaggedTemplateExpression";
+    SyntaxKind[SyntaxKind["TypeAssertionExpression"] = 216] = "TypeAssertionExpression";
+    SyntaxKind[SyntaxKind["ParenthesizedExpression"] = 217] = "ParenthesizedExpression";
+    SyntaxKind[SyntaxKind["FunctionExpression"] = 218] = "FunctionExpression";
+    SyntaxKind[SyntaxKind["ArrowFunction"] = 219] = "ArrowFunction";
+    SyntaxKind[SyntaxKind["DeleteExpression"] = 220] = "DeleteExpression";
+    SyntaxKind[SyntaxKind["TypeOfExpression"] = 221] = "TypeOfExpression";
+    SyntaxKind[SyntaxKind["VoidExpression"] = 222] = "VoidExpression";
+    SyntaxKind[SyntaxKind["AwaitExpression"] = 223] = "AwaitExpression";
+    SyntaxKind[SyntaxKind["PrefixUnaryExpression"] = 224] = "PrefixUnaryExpression";
+    SyntaxKind[SyntaxKind["PostfixUnaryExpression"] = 225] = "PostfixUnaryExpression";
+    SyntaxKind[SyntaxKind["BinaryExpression"] = 226] = "BinaryExpression";
+    SyntaxKind[SyntaxKind["ConditionalExpression"] = 227] = "ConditionalExpression";
+    SyntaxKind[SyntaxKind["TemplateExpression"] = 228] = "TemplateExpression";
+    SyntaxKind[SyntaxKind["YieldExpression"] = 229] = "YieldExpression";
+    SyntaxKind[SyntaxKind["SpreadElement"] = 230] = "SpreadElement";
+    SyntaxKind[SyntaxKind["ClassExpression"] = 231] = "ClassExpression";
+    SyntaxKind[SyntaxKind["OmittedExpression"] = 232] = "OmittedExpression";
+    SyntaxKind[SyntaxKind["ExpressionWithTypeArguments"] = 233] = "ExpressionWithTypeArguments";
+    SyntaxKind[SyntaxKind["AsExpression"] = 234] = "AsExpression";
+    SyntaxKind[SyntaxKind["NonNullExpression"] = 235] = "NonNullExpression";
+    SyntaxKind[SyntaxKind["MetaProperty"] = 236] = "MetaProperty";
+    SyntaxKind[SyntaxKind["SyntheticExpression"] = 237] = "SyntheticExpression";
+    SyntaxKind[SyntaxKind["SatisfiesExpression"] = 238] = "SatisfiesExpression";
+    SyntaxKind[SyntaxKind["TemplateSpan"] = 239] = "TemplateSpan";
+    SyntaxKind[SyntaxKind["SemicolonClassElement"] = 240] = "SemicolonClassElement";
+    SyntaxKind[SyntaxKind["Block"] = 241] = "Block";
+    SyntaxKind[SyntaxKind["EmptyStatement"] = 242] = "EmptyStatement";
+    SyntaxKind[SyntaxKind["VariableStatement"] = 243] = "VariableStatement";
+    SyntaxKind[SyntaxKind["ExpressionStatement"] = 244] = "ExpressionStatement";
+    SyntaxKind[SyntaxKind["IfStatement"] = 245] = "IfStatement";
+    SyntaxKind[SyntaxKind["DoStatement"] = 246] = "DoStatement";
+    SyntaxKind[SyntaxKind["WhileStatement"] = 247] = "WhileStatement";
+    SyntaxKind[SyntaxKind["ForStatement"] = 248] = "ForStatement";
+    SyntaxKind[SyntaxKind["ForInStatement"] = 249] = "ForInStatement";
+    SyntaxKind[SyntaxKind["ForOfStatement"] = 250] = "ForOfStatement";
+    SyntaxKind[SyntaxKind["ContinueStatement"] = 251] = "ContinueStatement";
+    SyntaxKind[SyntaxKind["BreakStatement"] = 252] = "BreakStatement";
+    SyntaxKind[SyntaxKind["ReturnStatement"] = 253] = "ReturnStatement";
+    SyntaxKind[SyntaxKind["WithStatement"] = 254] = "WithStatement";
+    SyntaxKind[SyntaxKind["SwitchStatement"] = 255] = "SwitchStatement";
+    SyntaxKind[SyntaxKind["LabeledStatement"] = 256] = "LabeledStatement";
+    SyntaxKind[SyntaxKind["ThrowStatement"] = 257] = "ThrowStatement";
+    SyntaxKind[SyntaxKind["TryStatement"] = 258] = "TryStatement";
+    SyntaxKind[SyntaxKind["DebuggerStatement"] = 259] = "DebuggerStatement";
+    SyntaxKind[SyntaxKind["VariableDeclaration"] = 260] = "VariableDeclaration";
+    SyntaxKind[SyntaxKind["VariableDeclarationList"] = 261] = "VariableDeclarationList";
+    SyntaxKind[SyntaxKind["FunctionDeclaration"] = 262] = "FunctionDeclaration";
+    SyntaxKind[SyntaxKind["ClassDeclaration"] = 263] = "ClassDeclaration";
+    SyntaxKind[SyntaxKind["InterfaceDeclaration"] = 264] = "InterfaceDeclaration";
+    SyntaxKind[SyntaxKind["TypeAliasDeclaration"] = 265] = "TypeAliasDeclaration";
+    SyntaxKind[SyntaxKind["EnumDeclaration"] = 266] = "EnumDeclaration";
+    SyntaxKind[SyntaxKind["ModuleDeclaration"] = 267] = "ModuleDeclaration";
+    SyntaxKind[SyntaxKind["ModuleBlock"] = 268] = "ModuleBlock";
+    SyntaxKind[SyntaxKind["CaseBlock"] = 269] = "CaseBlock";
+    SyntaxKind[SyntaxKind["NamespaceExportDeclaration"] = 270] = "NamespaceExportDeclaration";
+    SyntaxKind[SyntaxKind["ImportEqualsDeclaration"] = 271] = "ImportEqualsDeclaration";
+    SyntaxKind[SyntaxKind["ImportDeclaration"] = 272] = "ImportDeclaration";
+    SyntaxKind[SyntaxKind["ImportClause"] = 273] = "ImportClause";
+    SyntaxKind[SyntaxKind["NamespaceImport"] = 274] = "NamespaceImport";
+    SyntaxKind[SyntaxKind["NamedImports"] = 275] = "NamedImports";
+    SyntaxKind[SyntaxKind["ImportSpecifier"] = 276] = "ImportSpecifier";
+    SyntaxKind[SyntaxKind["ExportAssignment"] = 277] = "ExportAssignment";
+    SyntaxKind[SyntaxKind["ExportDeclaration"] = 278] = "ExportDeclaration";
+    SyntaxKind[SyntaxKind["NamedExports"] = 279] = "NamedExports";
+    SyntaxKind[SyntaxKind["NamespaceExport"] = 280] = "NamespaceExport";
+    SyntaxKind[SyntaxKind["ExportSpecifier"] = 281] = "ExportSpecifier";
+    SyntaxKind[SyntaxKind["MissingDeclaration"] = 282] = "MissingDeclaration";
+    SyntaxKind[SyntaxKind["ExternalModuleReference"] = 283] = "ExternalModuleReference";
+    SyntaxKind[SyntaxKind["JsxElement"] = 284] = "JsxElement";
+    SyntaxKind[SyntaxKind["JsxSelfClosingElement"] = 285] = "JsxSelfClosingElement";
+    SyntaxKind[SyntaxKind["JsxOpeningElement"] = 286] = "JsxOpeningElement";
+    SyntaxKind[SyntaxKind["JsxClosingElement"] = 287] = "JsxClosingElement";
+    SyntaxKind[SyntaxKind["JsxFragment"] = 288] = "JsxFragment";
+    SyntaxKind[SyntaxKind["JsxOpeningFragment"] = 289] = "JsxOpeningFragment";
+    SyntaxKind[SyntaxKind["JsxClosingFragment"] = 290] = "JsxClosingFragment";
+    SyntaxKind[SyntaxKind["JsxAttribute"] = 291] = "JsxAttribute";
+    SyntaxKind[SyntaxKind["JsxAttributes"] = 292] = "JsxAttributes";
+    SyntaxKind[SyntaxKind["JsxSpreadAttribute"] = 293] = "JsxSpreadAttribute";
+    SyntaxKind[SyntaxKind["JsxExpression"] = 294] = "JsxExpression";
+    SyntaxKind[SyntaxKind["JsxNamespacedName"] = 295] = "JsxNamespacedName";
+    SyntaxKind[SyntaxKind["CaseClause"] = 296] = "CaseClause";
+    SyntaxKind[SyntaxKind["DefaultClause"] = 297] = "DefaultClause";
+    SyntaxKind[SyntaxKind["HeritageClause"] = 298] = "HeritageClause";
+    SyntaxKind[SyntaxKind["CatchClause"] = 299] = "CatchClause";
+    SyntaxKind[SyntaxKind["ImportAttributes"] = 300] = "ImportAttributes";
+    SyntaxKind[SyntaxKind["ImportAttribute"] = 301] = "ImportAttribute";
+    SyntaxKind[SyntaxKind["PropertyAssignment"] = 302] = "PropertyAssignment";
+    SyntaxKind[SyntaxKind["ShorthandPropertyAssignment"] = 303] = "ShorthandPropertyAssignment";
+    SyntaxKind[SyntaxKind["SpreadAssignment"] = 304] = "SpreadAssignment";
+    SyntaxKind[SyntaxKind["EnumMember"] = 305] = "EnumMember";
+    SyntaxKind[SyntaxKind["SourceFile"] = 306] = "SourceFile";
+    SyntaxKind[SyntaxKind["Bundle"] = 307] = "Bundle";
+    SyntaxKind[SyntaxKind["JSDocTypeExpression"] = 308] = "JSDocTypeExpression";
+    SyntaxKind[SyntaxKind["JSDocNameReference"] = 309] = "JSDocNameReference";
+    SyntaxKind[SyntaxKind["JSDocMemberName"] = 310] = "JSDocMemberName";
+    SyntaxKind[SyntaxKind["JSDocAllType"] = 311] = "JSDocAllType";
+    SyntaxKind[SyntaxKind["JSDocNullableType"] = 312] = "JSDocNullableType";
+    SyntaxKind[SyntaxKind["JSDocNonNullableType"] = 313] = "JSDocNonNullableType";
+    SyntaxKind[SyntaxKind["JSDocOptionalType"] = 314] = "JSDocOptionalType";
+    SyntaxKind[SyntaxKind["JSDocVariadicType"] = 315] = "JSDocVariadicType";
+    SyntaxKind[SyntaxKind["JSDoc"] = 316] = "JSDoc";
+    SyntaxKind[SyntaxKind["JSDocText"] = 317] = "JSDocText";
+    SyntaxKind[SyntaxKind["JSDocTypeLiteral"] = 318] = "JSDocTypeLiteral";
+    SyntaxKind[SyntaxKind["JSDocSignature"] = 319] = "JSDocSignature";
+    SyntaxKind[SyntaxKind["JSDocLink"] = 320] = "JSDocLink";
+    SyntaxKind[SyntaxKind["JSDocLinkCode"] = 321] = "JSDocLinkCode";
+    SyntaxKind[SyntaxKind["JSDocLinkPlain"] = 322] = "JSDocLinkPlain";
+    SyntaxKind[SyntaxKind["JSDocTag"] = 323] = "JSDocTag";
+    SyntaxKind[SyntaxKind["JSDocAugmentsTag"] = 324] = "JSDocAugmentsTag";
+    SyntaxKind[SyntaxKind["JSDocImplementsTag"] = 325] = "JSDocImplementsTag";
+    SyntaxKind[SyntaxKind["JSDocDeprecatedTag"] = 326] = "JSDocDeprecatedTag";
+    SyntaxKind[SyntaxKind["JSDocPublicTag"] = 327] = "JSDocPublicTag";
+    SyntaxKind[SyntaxKind["JSDocPrivateTag"] = 328] = "JSDocPrivateTag";
+    SyntaxKind[SyntaxKind["JSDocProtectedTag"] = 329] = "JSDocProtectedTag";
+    SyntaxKind[SyntaxKind["JSDocReadonlyTag"] = 330] = "JSDocReadonlyTag";
+    SyntaxKind[SyntaxKind["JSDocOverrideTag"] = 331] = "JSDocOverrideTag";
+    SyntaxKind[SyntaxKind["JSDocCallbackTag"] = 332] = "JSDocCallbackTag";
+    SyntaxKind[SyntaxKind["JSDocOverloadTag"] = 333] = "JSDocOverloadTag";
+    SyntaxKind[SyntaxKind["JSDocParameterTag"] = 334] = "JSDocParameterTag";
+    SyntaxKind[SyntaxKind["JSDocReturnTag"] = 335] = "JSDocReturnTag";
+    SyntaxKind[SyntaxKind["JSDocThisTag"] = 336] = "JSDocThisTag";
+    SyntaxKind[SyntaxKind["JSDocTypeTag"] = 337] = "JSDocTypeTag";
+    SyntaxKind[SyntaxKind["JSDocTemplateTag"] = 338] = "JSDocTemplateTag";
+    SyntaxKind[SyntaxKind["JSDocTypedefTag"] = 339] = "JSDocTypedefTag";
+    SyntaxKind[SyntaxKind["JSDocSeeTag"] = 340] = "JSDocSeeTag";
+    SyntaxKind[SyntaxKind["JSDocPropertyTag"] = 341] = "JSDocPropertyTag";
+    SyntaxKind[SyntaxKind["JSDocSatisfiesTag"] = 342] = "JSDocSatisfiesTag";
+    SyntaxKind[SyntaxKind["JSDocImportTag"] = 343] = "JSDocImportTag";
+    SyntaxKind[SyntaxKind["SyntaxList"] = 344] = "SyntaxList";
+    SyntaxKind[SyntaxKind["NotEmittedStatement"] = 345] = "NotEmittedStatement";
+    SyntaxKind[SyntaxKind["PartiallyEmittedExpression"] = 346] = "PartiallyEmittedExpression";
+    SyntaxKind[SyntaxKind["CommaListExpression"] = 347] = "CommaListExpression";
+    SyntaxKind[SyntaxKind["SyntheticReferenceExpression"] = 348] = "SyntheticReferenceExpression";
+    SyntaxKind[SyntaxKind["Count"] = 349] = "Count";
+})(SyntaxKind || (SyntaxKind = {}));
diff --git a/_packages/ast/src/tokenFlags.enum.ts b/_packages/ast/src/tokenFlags.enum.ts
new file mode 100644
index 0000000000..1e5160421c
--- /dev/null
+++ b/_packages/ast/src/tokenFlags.enum.ts
@@ -0,0 +1,26 @@
+// dprint-ignore
+export const enum TokenFlags {
+    None = 0,
+    PrecedingLineBreak = 1 << 0,
+    PrecedingJSDocComment = 1 << 1,
+    Unterminated = 1 << 2,
+    ExtendedUnicodeEscape = 1 << 3,     // e.g. `\u{10ffff}`
+    Scientific = 1 << 4,                // e.g. `10e2`
+    Octal = 1 << 5,                     // e.g. `0777`
+    HexSpecifier = 1 << 6,              // e.g. `0x00000000`
+    BinarySpecifier = 1 << 7,           // e.g. `0b0110010000000000`
+    OctalSpecifier = 1 << 8,            // e.g. `0o777`
+    ContainsSeparator = 1 << 9,         // e.g. `0b1100_0101`
+    UnicodeEscape = 1 << 10,            // e.g. `\u00a0`
+    ContainsInvalidEscape = 1 << 11,    // e.g. `\uhello`
+    HexEscape = 1 << 12,                // e.g. `\xa0`
+    ContainsLeadingZero = 1 << 13,      // e.g. `0888`
+    ContainsInvalidSeparator = 1 << 14, // e.g. `0_1`
+    PrecedingJSDocLeadingAsterisks = 1 << 15,
+    BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
+    WithSpecifier = HexSpecifier | BinaryOrOctalSpecifier,
+    StringLiteralFlags = HexEscape | UnicodeEscape | ExtendedUnicodeEscape | ContainsInvalidEscape,
+    NumericLiteralFlags = Scientific | Octal | ContainsLeadingZero | WithSpecifier | ContainsSeparator | ContainsInvalidSeparator,
+    TemplateLiteralLikeFlags = HexEscape | UnicodeEscape | ExtendedUnicodeEscape | ContainsInvalidEscape,
+    IsInvalid = Octal | ContainsLeadingZero | ContainsInvalidSeparator | ContainsInvalidEscape,
+}
diff --git a/_packages/ast/src/tokenFlags.ts b/_packages/ast/src/tokenFlags.ts
new file mode 100644
index 0000000000..4b0a8cd272
--- /dev/null
+++ b/_packages/ast/src/tokenFlags.ts
@@ -0,0 +1,27 @@
+// dprint-ignore
+export var TokenFlags: any;
+(function (TokenFlags) {
+    TokenFlags[TokenFlags["None"] = 0] = "None";
+    TokenFlags[TokenFlags["PrecedingLineBreak"] = 1] = "PrecedingLineBreak";
+    TokenFlags[TokenFlags["PrecedingJSDocComment"] = 2] = "PrecedingJSDocComment";
+    TokenFlags[TokenFlags["Unterminated"] = 4] = "Unterminated";
+    TokenFlags[TokenFlags["ExtendedUnicodeEscape"] = 8] = "ExtendedUnicodeEscape";
+    TokenFlags[TokenFlags["Scientific"] = 16] = "Scientific";
+    TokenFlags[TokenFlags["Octal"] = 32] = "Octal";
+    TokenFlags[TokenFlags["HexSpecifier"] = 64] = "HexSpecifier";
+    TokenFlags[TokenFlags["BinarySpecifier"] = 128] = "BinarySpecifier";
+    TokenFlags[TokenFlags["OctalSpecifier"] = 256] = "OctalSpecifier";
+    TokenFlags[TokenFlags["ContainsSeparator"] = 512] = "ContainsSeparator";
+    TokenFlags[TokenFlags["UnicodeEscape"] = 1024] = "UnicodeEscape";
+    TokenFlags[TokenFlags["ContainsInvalidEscape"] = 2048] = "ContainsInvalidEscape";
+    TokenFlags[TokenFlags["HexEscape"] = 4096] = "HexEscape";
+    TokenFlags[TokenFlags["ContainsLeadingZero"] = 8192] = "ContainsLeadingZero";
+    TokenFlags[TokenFlags["ContainsInvalidSeparator"] = 16384] = "ContainsInvalidSeparator";
+    TokenFlags[TokenFlags["PrecedingJSDocLeadingAsterisks"] = 32768] = "PrecedingJSDocLeadingAsterisks";
+    TokenFlags[TokenFlags["BinaryOrOctalSpecifier"] = 384] = "BinaryOrOctalSpecifier";
+    TokenFlags[TokenFlags["WithSpecifier"] = 448] = "WithSpecifier";
+    TokenFlags[TokenFlags["StringLiteralFlags"] = 7176] = "StringLiteralFlags";
+    TokenFlags[TokenFlags["NumericLiteralFlags"] = 25584] = "NumericLiteralFlags";
+    TokenFlags[TokenFlags["TemplateLiteralLikeFlags"] = 7176] = "TemplateLiteralLikeFlags";
+    TokenFlags[TokenFlags["IsInvalid"] = 26656] = "IsInvalid";
+})(TokenFlags || (TokenFlags = {}));
diff --git a/_packages/ast/src/utils.ts b/_packages/ast/src/utils.ts
new file mode 100644
index 0000000000..0b77d02fb6
--- /dev/null
+++ b/_packages/ast/src/utils.ts
@@ -0,0 +1,9 @@
+export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
+    return value !== undefined && test(value) ? value : undefined;
+}
+
+export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut {
+    if (value !== undefined && test(value)) return value;
+
+    throw new Error(`Invalid cast. The supplied value ${value} did not pass the test '${test.name}'.`);
+}
diff --git a/_packages/ast/tsconfig.json b/_packages/ast/tsconfig.json
new file mode 100644
index 0000000000..5be4954833
--- /dev/null
+++ b/_packages/ast/tsconfig.json
@@ -0,0 +1,17 @@
+{
+    "compilerOptions": {
+        "module": "node16",
+        "strict": true,
+        "composite": true,
+        "forceConsistentCasingInFileNames": true,
+        "rewriteRelativeImportExtensions": true,
+        "verbatimModuleSyntax": true,
+        "isolatedDeclarations": true,
+        "sourceMap": true,
+        "declaration": true,
+        "declarationMap": true,
+        "rootDir": "src",
+        "outDir": "dist"
+    },
+    "include": ["src"]
+}
diff --git a/cmd/tsgo/api.go b/cmd/tsgo/api.go
new file mode 100644
index 0000000000..702ce143a2
--- /dev/null
+++ b/cmd/tsgo/api.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/microsoft/typescript-go/internal/api"
+	"github.com/microsoft/typescript-go/internal/bundled"
+	"github.com/microsoft/typescript-go/internal/core"
+)
+
+func runAPI(args []string) int {
+	flag := flag.NewFlagSet("api", flag.ContinueOnError)
+	cwd := flag.String("cwd", core.Must(os.Getwd()), "current working directory")
+	if err := flag.Parse(args); err != nil {
+		return 2
+	}
+
+	defaultLibraryPath := bundled.LibPath()
+
+	s := api.NewServer(&api.ServerOptions{
+		In:                 os.Stdin,
+		Out:                os.Stdout,
+		Err:                os.Stderr,
+		Cwd:                *cwd,
+		NewLine:            "\n",
+		DefaultLibraryPath: defaultLibraryPath,
+	})
+
+	if err := s.Run(); err != nil && !errors.Is(err, io.EOF) {
+		fmt.Println(err)
+		return 1
+	}
+	return 0
+}
diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go
index f40ed39bd8..ae23a493ce 100644
--- a/cmd/tsgo/main.go
+++ b/cmd/tsgo/main.go
@@ -133,6 +133,8 @@ func runMain() int {
 			return int(execute.CommandLine(newSystem(), nil, args[1:]))
 		case "lsp":
 			return runLSP(args[1:])
+		case "api":
+			return runAPI(args[1:])
 		}
 	}
 	opts := parseArgs()
diff --git a/internal/api/api.go b/internal/api/api.go
new file mode 100644
index 0000000000..6b04c741aa
--- /dev/null
+++ b/internal/api/api.go
@@ -0,0 +1,375 @@
+package api
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"sync"
+
+	"github.com/microsoft/typescript-go/internal/api/encoder"
+	"github.com/microsoft/typescript-go/internal/ast"
+	"github.com/microsoft/typescript-go/internal/astnav"
+	"github.com/microsoft/typescript-go/internal/checker"
+	"github.com/microsoft/typescript-go/internal/core"
+	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
+	"github.com/microsoft/typescript-go/internal/project"
+	"github.com/microsoft/typescript-go/internal/tsoptions"
+	"github.com/microsoft/typescript-go/internal/tspath"
+	"github.com/microsoft/typescript-go/internal/vfs"
+)
+
+type handleMap[T any] map[Handle[T]]*T
+
+type APIOptions struct {
+	Logger *project.Logger
+}
+
+type API struct {
+	host    APIHost
+	options APIOptions
+
+	documentRegistry *project.DocumentRegistry
+	scriptInfosMu    sync.RWMutex
+	scriptInfos      map[tspath.Path]*project.ScriptInfo
+
+	projects  handleMap[project.Project]
+	filesMu   sync.Mutex
+	files     handleMap[ast.SourceFile]
+	symbolsMu sync.Mutex
+	symbols   handleMap[ast.Symbol]
+	typesMu   sync.Mutex
+	types     handleMap[checker.Type]
+}
+
+var _ project.ProjectHost = (*API)(nil)
+
+func NewAPI(host APIHost, options APIOptions) *API {
+	api := &API{
+		host:        host,
+		options:     options,
+		scriptInfos: make(map[tspath.Path]*project.ScriptInfo),
+		projects:    make(handleMap[project.Project]),
+		files:       make(handleMap[ast.SourceFile]),
+		symbols:     make(handleMap[ast.Symbol]),
+		types:       make(handleMap[checker.Type]),
+	}
+	api.documentRegistry = &project.DocumentRegistry{
+		Options: tspath.ComparePathsOptions{
+			UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
+			CurrentDirectory:          host.GetCurrentDirectory(),
+		},
+		Hooks: project.DocumentRegistryHooks{
+			OnReleaseDocument: func(file *ast.SourceFile) {
+				_ = api.releaseHandle(string(FileHandle(file)))
+			},
+		},
+	}
+	return api
+}
+
+// DefaultLibraryPath implements ProjectHost.
+func (api *API) DefaultLibraryPath() string {
+	return api.host.DefaultLibraryPath()
+}
+
+// DocumentRegistry implements ProjectHost.
+func (api *API) DocumentRegistry() *project.DocumentRegistry {
+	return api.documentRegistry
+}
+
+// FS implements ProjectHost.
+func (api *API) FS() vfs.FS {
+	return api.host.FS()
+}
+
+// GetCurrentDirectory implements ProjectHost.
+func (api *API) GetCurrentDirectory() string {
+	return api.host.GetCurrentDirectory()
+}
+
+// GetOrCreateScriptInfoForFile implements ProjectHost.
+func (api *API) GetOrCreateScriptInfoForFile(fileName string, path tspath.Path, scriptKind core.ScriptKind) *project.ScriptInfo {
+	return api.getOrCreateScriptInfo(fileName, path, scriptKind)
+}
+
+// GetScriptInfoByPath implements ProjectHost.
+func (api *API) GetScriptInfoByPath(path tspath.Path) *project.ScriptInfo {
+	api.scriptInfosMu.RLock()
+	defer api.scriptInfosMu.RUnlock()
+	return api.scriptInfos[path]
+}
+
+// OnDiscoveredSymlink implements ProjectHost.
+func (api *API) OnDiscoveredSymlink(info *project.ScriptInfo) {
+	// !!!
+}
+
+// Log implements ProjectHost.
+func (api *API) Log(s string) {
+	api.options.Logger.Info(s)
+}
+
+// NewLine implements ProjectHost.
+func (api *API) NewLine() string {
+	return api.host.NewLine()
+}
+
+// PositionEncoding implements ProjectHost.
+func (api *API) PositionEncoding() lsproto.PositionEncodingKind {
+	return lsproto.PositionEncodingKindUTF8
+}
+
+func (api *API) HandleRequest(id int, method string, payload []byte) ([]byte, error) {
+	params, err := unmarshalPayload(method, payload)
+	if err != nil {
+		return nil, err
+	}
+
+	switch Method(method) {
+	case MethodRelease:
+		if id, ok := params.(*string); ok {
+			return nil, api.releaseHandle(*id)
+		} else {
+			return nil, fmt.Errorf("expected string for release handle, got %T", params)
+		}
+	case MethodGetSourceFile:
+		params := params.(*GetSourceFileParams)
+		sourceFile, err := api.GetSourceFile(params.Project, params.FileName)
+		if err != nil {
+			return nil, err
+		}
+		return encoder.EncodeSourceFile(sourceFile, string(FileHandle(sourceFile)))
+	case MethodParseConfigFile:
+		return encodeJSON(api.ParseConfigFile(params.(*ParseConfigFileParams).FileName))
+	case MethodLoadProject:
+		return encodeJSON(api.LoadProject(params.(*LoadProjectParams).ConfigFileName))
+	case MethodGetSymbolAtPosition:
+		params := params.(*GetSymbolAtPositionParams)
+		return encodeJSON(api.GetSymbolAtPosition(params.Project, params.FileName, int(params.Position)))
+	case MethodGetSymbolsAtPositions:
+		params := params.(*GetSymbolsAtPositionsParams)
+		return encodeJSON(core.TryMap(params.Positions, func(position uint32) (any, error) {
+			return api.GetSymbolAtPosition(params.Project, params.FileName, int(position))
+		}))
+	case MethodGetSymbolAtLocation:
+		params := params.(*GetSymbolAtLocationParams)
+		return encodeJSON(api.GetSymbolAtLocation(params.Project, params.Location))
+	case MethodGetSymbolsAtLocations:
+		params := params.(*GetSymbolsAtLocationsParams)
+		return encodeJSON(core.TryMap(params.Locations, func(location Handle[ast.Node]) (any, error) {
+			return api.GetSymbolAtLocation(params.Project, location)
+		}))
+	case MethodGetTypeOfSymbol:
+		params := params.(*GetTypeOfSymbolParams)
+		return encodeJSON(api.GetTypeOfSymbol(params.Project, params.Symbol))
+	case MethodGetTypesOfSymbols:
+		params := params.(*GetTypesOfSymbolsParams)
+		return encodeJSON(core.TryMap(params.Symbols, func(symbol Handle[ast.Symbol]) (any, error) {
+			return api.GetTypeOfSymbol(params.Project, symbol)
+		}))
+	default:
+		return nil, fmt.Errorf("unhandled API method %q", method)
+	}
+}
+
+func (api *API) Close() {
+	api.options.Logger.Close()
+}
+
+func (api *API) ParseConfigFile(configFileName string) (*ConfigFileResponse, error) {
+	configFileName = api.toAbsoluteFileName(configFileName)
+	configFileContent, ok := api.host.FS().ReadFile(configFileName)
+	if !ok {
+		return nil, fmt.Errorf("could not read file %q", configFileName)
+	}
+	configDir := tspath.GetDirectoryPath(configFileName)
+	tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(configFileName, api.toPath(configFileName), configFileContent)
+	parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
+		tsConfigSourceFile,
+		api.host,
+		configDir,
+		nil, /*existingOptions*/
+		configFileName,
+		nil, /*resolutionStack*/
+		nil, /*extraFileExtensions*/
+		nil, /*extendedConfigCache*/
+	)
+	return &ConfigFileResponse{
+		FileNames: parsedCommandLine.FileNames(),
+		Options:   parsedCommandLine.CompilerOptions(),
+	}, nil
+}
+
+func (api *API) LoadProject(configFileName string) (*ProjectResponse, error) {
+	configFileName = api.toAbsoluteFileName(configFileName)
+	configFilePath := api.toPath(configFileName)
+	p := project.NewConfiguredProject(configFileName, configFilePath, api)
+	if err := p.LoadConfig(); err != nil {
+		return nil, err
+	}
+	p.GetProgram()
+	data := NewProjectResponse(p)
+	api.projects[data.Id] = p
+	return data, nil
+}
+
+func (api *API) GetSymbolAtPosition(projectId Handle[project.Project], fileName string, position int) (*SymbolResponse, error) {
+	project, ok := api.projects[projectId]
+	if !ok {
+		return nil, errors.New("project not found")
+	}
+	symbol, err := project.LanguageService().GetSymbolAtPosition(fileName, position)
+	if err != nil || symbol == nil {
+		return nil, err
+	}
+	data := NewSymbolResponse(symbol)
+	api.symbolsMu.Lock()
+	defer api.symbolsMu.Unlock()
+	api.symbols[data.Id] = symbol
+	return data, nil
+}
+
+func (api *API) GetSymbolAtLocation(projectId Handle[project.Project], location Handle[ast.Node]) (*SymbolResponse, error) {
+	project, ok := api.projects[projectId]
+	if !ok {
+		return nil, errors.New("project not found")
+	}
+	fileHandle, pos, kind, err := parseNodeHandle(location)
+	if err != nil {
+		return nil, err
+	}
+	api.filesMu.Lock()
+	defer api.filesMu.Unlock()
+	sourceFile, ok := api.files[fileHandle]
+	if !ok {
+		return nil, fmt.Errorf("file %q not found", fileHandle)
+	}
+	token := astnav.GetTokenAtPosition(sourceFile, pos)
+	if token == nil {
+		return nil, fmt.Errorf("token not found at position %d in file %q", pos, sourceFile.FileName())
+	}
+	node := ast.FindAncestorKind(token, kind)
+	if node == nil {
+		return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName())
+	}
+	symbol := project.LanguageService().GetSymbolAtLocation(node)
+	if symbol == nil {
+		return nil, nil
+	}
+	data := NewSymbolResponse(symbol)
+	api.symbolsMu.Lock()
+	defer api.symbolsMu.Unlock()
+	api.symbols[data.Id] = symbol
+	return data, nil
+}
+
+func (api *API) GetTypeOfSymbol(projectId Handle[project.Project], symbolHandle Handle[ast.Symbol]) (*TypeResponse, error) {
+	project, ok := api.projects[projectId]
+	if !ok {
+		return nil, errors.New("project not found")
+	}
+	api.symbolsMu.Lock()
+	defer api.symbolsMu.Unlock()
+	symbol, ok := api.symbols[symbolHandle]
+	if !ok {
+		return nil, fmt.Errorf("symbol %q not found", symbolHandle)
+	}
+	t := project.LanguageService().GetTypeOfSymbol(symbol)
+	if t == nil {
+		return nil, nil
+	}
+	return NewTypeData(t), nil
+}
+
+func (api *API) GetSourceFile(projectId Handle[project.Project], fileName string) (*ast.SourceFile, error) {
+	project, ok := api.projects[projectId]
+	if !ok {
+		return nil, errors.New("project not found")
+	}
+	sourceFile := project.GetProgram().GetSourceFile(fileName)
+	if sourceFile == nil {
+		return nil, fmt.Errorf("source file %q not found", fileName)
+	}
+	api.filesMu.Lock()
+	defer api.filesMu.Unlock()
+	api.files[FileHandle(sourceFile)] = sourceFile
+	return sourceFile, nil
+}
+
+func (api *API) releaseHandle(handle string) error {
+	switch handle[0] {
+	case handlePrefixProject:
+		projectId := Handle[project.Project](handle)
+		project, ok := api.projects[projectId]
+		if !ok {
+			return fmt.Errorf("project %q not found", handle)
+		}
+		delete(api.projects, projectId)
+		project.Close()
+	case handlePrefixFile:
+		fileId := Handle[ast.SourceFile](handle)
+		api.filesMu.Lock()
+		defer api.filesMu.Unlock()
+		_, ok := api.files[fileId]
+		if !ok {
+			return fmt.Errorf("file %q not found", handle)
+		}
+		delete(api.files, fileId)
+	case handlePrefixSymbol:
+		symbolId := Handle[ast.Symbol](handle)
+		api.symbolsMu.Lock()
+		defer api.symbolsMu.Unlock()
+		_, ok := api.symbols[symbolId]
+		if !ok {
+			return fmt.Errorf("symbol %q not found", handle)
+		}
+		delete(api.symbols, symbolId)
+	case handlePrefixType:
+		typeId := Handle[checker.Type](handle)
+		api.typesMu.Lock()
+		defer api.typesMu.Unlock()
+		_, ok := api.types[typeId]
+		if !ok {
+			return fmt.Errorf("type %q not found", handle)
+		}
+		delete(api.types, typeId)
+	default:
+		return fmt.Errorf("unhandled handle type %q", handle[0])
+	}
+	return nil
+}
+
+func (api *API) getOrCreateScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind) *project.ScriptInfo {
+	api.scriptInfosMu.RLock()
+	info, ok := api.scriptInfos[path]
+	api.scriptInfosMu.RUnlock()
+	if ok {
+		return info
+	}
+
+	content, ok := api.host.FS().ReadFile(fileName)
+	if !ok {
+		return nil
+	}
+	info = project.NewScriptInfo(fileName, path, scriptKind)
+	info.SetTextFromDisk(content)
+	api.scriptInfosMu.Lock()
+	defer api.scriptInfosMu.Unlock()
+	api.scriptInfos[path] = info
+	return info
+}
+
+func (api *API) toAbsoluteFileName(fileName string) string {
+	return tspath.GetNormalizedAbsolutePath(fileName, api.host.GetCurrentDirectory())
+}
+
+func (api *API) toPath(fileName string) tspath.Path {
+	return tspath.ToPath(fileName, api.host.GetCurrentDirectory(), api.host.FS().UseCaseSensitiveFileNames())
+}
+
+func encodeJSON(v any, err error) ([]byte, error) {
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(v)
+}
diff --git a/internal/api/encoder/encoder.go b/internal/api/encoder/encoder.go
new file mode 100644
index 0000000000..5a1502eba3
--- /dev/null
+++ b/internal/api/encoder/encoder.go
@@ -0,0 +1,824 @@
+package encoder
+
+import (
+	"encoding/binary"
+	"fmt"
+	"slices"
+
+	"github.com/microsoft/typescript-go/internal/ast"
+)
+
+const (
+	NodeOffsetKind = iota * 4
+	NodeOffsetPos
+	NodeOffsetEnd
+	NodeOffsetNext
+	NodeOffsetParent
+	NodeOffsetData
+	// NodeSize is the number of bytes that represents a single node in the encoded format.
+	NodeSize
+)
+
+const (
+	NodeDataTypeChildren uint32 = iota << 30
+	NodeDataTypeString
+	NodeDataTypeExtendedData
+)
+
+const (
+	NodeDataTypeMask        uint32 = 0xc0_00_00_00
+	NodeDataChildMask       uint32 = 0x00_00_00_ff
+	NodeDataStringIndexMask uint32 = 0x00_ff_ff_ff
+)
+
+const (
+	SyntaxKindNodeList uint32 = 1<<32 - 1
+)
+
+const (
+	HeaderOffsetMetadata = iota * 4
+	HeaderOffsetStringOffsets
+	HeaderOffsetStringData
+	HeaderOffsetExtendedData
+	HeaderOffsetNodes
+	HeaderSize
+)
+
+const (
+	ProtocolVersion uint8 = 1
+)
+
+// Source File Binary Format
+// =========================
+//
+// The following defines a protocol for serializing TypeScript SourceFile objects to a compact binary format. All integer
+// values are little-endian.
+//
+// Overview
+// --------
+//
+// The format comprises six sections:
+//
+// | Section            | Length             | Description                                                                              |
+// | ------------------ | ------------------ | ---------------------------------------------------------------------------------------- |
+// | Header             | 20 bytes           | Contains byte offsets to the start of each section.                                      |
+// | String offsets     | 8 bytes per string | Pairs of starting byte offsets and ending byte offsets into the **string data** section. |
+// | String data        | variable           | UTF-8 encoded string data.                                                               |
+// | Extended node data | variable           | Extra data for some kinds of nodes.                                                      |
+// | Nodes              | 24 bytes per node  | Defines the AST structure of the file, with references to strings and extended data.     |
+//
+// Header (20 bytes)
+// -----------------
+//
+// The header contains the following fields:
+//
+// | Byte offset | Type   | Field                                     |
+// | ----------- | ------ | ----------------------------------------- |
+// | 0           | uint8  | Protocol version                          |
+// | 1-4         |        | Reserved                                  |
+// | 4-8         | uint32 | Byte offset to string offsets section     |
+// | 8-12        | uint32 | Byte offset to string data section        |
+// | 12-16       | uint32 | Byte offset to extended node data section |
+// | 16-20       | uint32 | Byte offset to nodes section              |
+//
+// String offsets (8 bytes per string)
+// -----------------------------------
+//
+// Each string offset entry consists of two 4-byte unsigned integers, representing the start and end byte offsets into the
+// **string data** section.
+//
+// String data (variable)
+// ----------------------
+//
+// The string data section contains UTF-8 encoded string data. In typical cases, the entirety of the string data is the
+// source file text, and individual nodes with string properties reference their positional slice of the file text. In
+// cases where a node's string property is not equal to the slice of file text at its position, the unique string is
+// appended to the string data section after the file text.
+//
+// Extended node data (variable)
+// -----------------------------
+//
+// The extended node data section contains additional data for specific node types. The length and meaning of each entry
+// is defined by the node type.
+//
+// Currently, the only node types that use this section are `TemplateHead`, `TemplateMiddle`, `TemplateTail`, and
+// `SourceFile`. The extended data format for the first three is:
+//
+// | Byte offset | Type   | Field                                            |
+// | ----------- | ------ | ------------------------------------------------ |
+// | 0-4         | uint32 | Index of `text` in the string offsets section    |
+// | 4-8         | uint32 | Index of `rawText` in the string offsets section |
+// | 8-12        | uint32 | Value of `templateFlags`                         |
+//
+// and for `SourceFile` is:
+//
+// | Byte offset | Type   | Field                                             |
+// | ----------- | ------ | ------------------------------------------------- |
+// | 0-4         | uint32 | Index of `text` in the string offsets section     |
+// | 4-8         | uint32 | Index of `fileName` in the string offsets section |
+// | 8-12        | uint32 | Index of `id` in the string offsets section       |
+//
+// Nodes (24 bytes per node)
+// -------------------------
+//
+// The nodes section contains the AST structure of the file. Nodes are represented in a flat array in source order,
+// heavily inspired by https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-11/. Each node has the following
+// structure:
+//
+// | Byte offset | Type   | Field                      |
+// | ----------- | ------ | -------------------------- |
+// | 0-4         | uint32 | Kind                       |
+// | 4-8         | uint32 | Pos                        |
+// | 8-12        | uint32 | End                        |
+// | 12-16       | uint32 | Node index of next sibling |
+// | 16-20       | uint32 | Node index of parent       |
+// | 20-24       |        | Node data                  |
+//
+// The first 24 bytes of the nodes section are zeros representing a nil node, such that nodes without a parent or next
+// sibling can unambiuously use `0` for those indices.
+//
+// NodeLists are represented as normal nodes with the special `kind` value `0xff_ff_ff_ff`. They are considered the parent
+// of their contents in the encoded format. A client reconstructing an AST similar to TypeScript's internal representation
+// should instead set the `parent` pointers of a NodeList's children to the NodeList's parent. A NodeList's `data` field
+// is the uint32 length of the list, and does not use one of the data types described below.
+//
+// For node types other than NodeList, the node data field encodes one of the following, determined by the first 2 bits of
+// the field:
+//
+// | Value | Data type | Description                                                                          |
+// | ----- | --------- | ------------------------------------------------------------------------------------ |
+// | 0b00  | Children  | Disambiguates which named properties of the node its children should be assigned to. |
+// | 0b01  | String    | The index of the node's string property in the **string offsets** section.           |
+// | 0b10  | Extended  | The byte offset of the node's extended data into the **extended node data** section. |
+// | 0b11  | Reserved  | Reserved for future use.                                                             |
+//
+// In all node data types, the remaining 6 bits of the first byte are used to encode booleans specific to the node type:
+//
+// | Node type                 | Bits 2-5 | Bit 1         | Bit 0                           |
+// | ------------------------- | -------- | ------------- | ------------------------------- |
+// | `ImportSpecifier`         |          |               | `isTypeOnly`                    |
+// | `ImportClause`            |          |               | `isTypeOnly`                    |
+// | `ExportSpecifier`         |          |               | `isTypeOnly`                    |
+// | `ImportEqualsDeclaration` |          |               | `isTypeOnly`                    |
+// | `ExportDeclaration`       |          |               | `isTypeOnly`                    |
+// | `ImportTypeNode`          |          |               | `isTypeOf`                      |
+// | `ExportAssignment`        |          |               | `isExportEquals`                |
+// | `Block`                   |          |               | `multiline`                     |
+// | `ArrayLiteralExpression`  |          |               | `multiline`                     |
+// | `ObjectLiteralExpression` |          |               | `multiline`                     |
+// | `JsxText`                 |          |               | `containsOnlyTriviaWhiteSpaces` |
+// | `JSDocTypeLiteral`        |          |               | `isArrayType`                   |
+// | `JsDocPropertyTag`        |          | `isNameFirst` | `isBracketed`                   |
+// | `JsDocParameterTag`       |          | `isNameFirst` | `isBracketed`                   |
+// | `VariableDeclarationList` |          | is `const`    | is `let`                        |
+// | `ImportAttributes`        |          | is `assert`   | `multiline`                     |
+//
+// The remaining 3 bytes of the node data field vary by data type:
+//
+// ### Children (0b00)
+//
+// If a node has fewer children than its type allows, additional data is needed to determine which properties the children
+// correspond to. The last byte of the 4-byte data field is a bitmask representing the child properties of the node type,
+// in visitor order, where `1` indicates that the child at that property is present and `0` indicates that the property is
+// nil. For example, a `MethodDeclaration` has the following child properties:
+//
+// | Property name  | Bit position |
+// | -------------- | ------------ |
+// | modifiers      | 0            |
+// | asteriskToken  | 1            |
+// | name           | 2            |
+// | postfixToken   | 3            |
+// | typeParameters | 4            |
+// | parameters     | 5            |
+// | returnType     | 6            |
+// | body           | 7            |
+//
+// A bitmask with value `0b01100101` would indicate that the next four direct descendants (i.e., node records that have a
+// `parent` set to the node index of the `MethodDeclaration`) of the node are its `modifiers`, `name`, `parameters`, and
+// `body` properties, in that order. The remaining properties are nil. (To reconstruct the node with named properties, the
+// client must consult a static table of each node type's child property names.)
+//
+// The bitmask may be zero for node types that can only have a single child, since no disambiguation is needed.
+// Additionally, the children data type may be used for nodes that can never have children, but do not require other
+// data types.
+//
+// ### String (0b01)
+//
+// The string data type is used for nodes with a single string property. (Currently, the name of that property is always
+// `text`.) The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
+// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is an index into the **string offsets** section. The *N*th 32-bit
+// unsigned integer in the **string offsets** section is the byte offset of the start of the string in the **string data**
+// section, and the *N+1*th 32-bit unsigned integer is the byte offset of the end of the string in the
+// **string data** section.
+//
+// ### Extended (0b10)
+//
+// The extended data type is used for nodes with properties that don't fit into either the children or string data types.
+// The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
+// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is a byte offset into the **extended node data** section. The length and
+// meaning of the data at that offset is defined by the node type. See the **Extended node data** section for details on
+// the format of the extended data for specific node types.
+
+func EncodeSourceFile(sourceFile *ast.SourceFile, id string) ([]byte, error) {
+	var parentIndex, nodeCount, prevIndex uint32
+	var extendedData []byte
+	strs := newStringTable(sourceFile.Text(), sourceFile.TextCount)
+	nodes := make([]byte, 0, (sourceFile.NodeCount+1)*NodeSize)
+
+	visitor := &ast.NodeVisitor{
+		Hooks: ast.NodeVisitorHooks{
+			VisitNodes: func(nodeList *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
+				if nodeList == nil || len(nodeList.Nodes) == 0 {
+					return nodeList
+				}
+
+				nodeCount++
+				if prevIndex != 0 {
+					// this is the next sibling of `prevNode`
+					b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
+					nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
+					nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
+					nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
+					nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
+				}
+
+				nodes = appendUint32s(nodes, SyntaxKindNodeList, uint32(nodeList.Pos()), uint32(nodeList.End()), 0, parentIndex, uint32(len(nodeList.Nodes)))
+
+				saveParentIndex := parentIndex
+
+				currentIndex := nodeCount
+				prevIndex = 0
+				parentIndex = currentIndex
+				visitor.VisitSlice(nodeList.Nodes)
+				prevIndex = currentIndex
+				parentIndex = saveParentIndex
+
+				return nodeList
+			},
+			VisitModifiers: func(modifiers *ast.ModifierList, visitor *ast.NodeVisitor) *ast.ModifierList {
+				if modifiers != nil && len(modifiers.Nodes) > 0 {
+					visitor.Hooks.VisitNodes(&modifiers.NodeList, visitor)
+				}
+				return modifiers
+			},
+		},
+	}
+	visitor.Visit = func(node *ast.Node) *ast.Node {
+		nodeCount++
+		if prevIndex != 0 {
+			// this is the next sibling of `prevNode`
+			b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
+			nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
+			nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
+			nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
+			nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
+		}
+
+		nodes = appendUint32s(nodes, uint32(node.Kind), uint32(node.Pos()), uint32(node.End()), 0, parentIndex, getNodeData(node, strs, &extendedData))
+
+		saveParentIndex := parentIndex
+
+		currentIndex := nodeCount
+		prevIndex = 0
+		parentIndex = currentIndex
+		visitor.VisitEachChild(node)
+		prevIndex = currentIndex
+		parentIndex = saveParentIndex
+		return node
+	}
+
+	nodes = appendUint32s(nodes, 0, 0, 0, 0, 0, 0)
+
+	nodeCount++
+	parentIndex++
+	nodes = appendUint32s(nodes, uint32(sourceFile.Kind), uint32(sourceFile.Pos()), uint32(sourceFile.End()), 0, 0, getSourceFileData(sourceFile, id, strs, &extendedData))
+
+	visitor.VisitEachChild(sourceFile.AsNode())
+
+	metadata := uint32(ProtocolVersion) << 24
+	offsetStringTableOffsets := HeaderSize
+	offsetStringTableData := HeaderSize + len(strs.offsets)*4
+	offsetExtendedData := offsetStringTableData + strs.stringLength()
+	offsetNodes := offsetExtendedData + len(extendedData)
+
+	header := []uint32{
+		metadata,
+		uint32(offsetStringTableOffsets),
+		uint32(offsetStringTableData),
+		uint32(offsetExtendedData),
+		uint32(offsetNodes),
+	}
+
+	var headerBytes, strsBytes []byte
+	headerBytes = appendUint32s(nil, header...)
+	strsBytes = strs.encode()
+
+	return slices.Concat(
+		headerBytes,
+		strsBytes,
+		extendedData,
+		nodes,
+	), nil
+}
+
+func appendUint32s(buf []byte, values ...uint32) []byte {
+	for _, value := range values {
+		var err error
+		if buf, err = binary.Append(buf, binary.LittleEndian, value); err != nil {
+			// The only error binary.Append can return is for values that are not fixed-size.
+			// This can never happen here, since we are always appending uint32.
+			panic(fmt.Sprintf("failed to append uint32: %v", err))
+		}
+	}
+	return buf
+}
+
+func getSourceFileData(sourceFile *ast.SourceFile, id string, strs *stringTable, extendedData *[]byte) uint32 {
+	t := NodeDataTypeExtendedData
+	extendedDataOffset := len(*extendedData)
+	textIndex := strs.add(sourceFile.Text(), sourceFile.Kind, sourceFile.Pos(), sourceFile.End())
+	fileNameIndex := strs.add(sourceFile.FileName(), 0, 0, 0)
+	idIndex := strs.add(id, 0, 0, 0)
+	*extendedData = appendUint32s(*extendedData, textIndex, fileNameIndex, idIndex)
+	return t | uint32(extendedDataOffset)
+}
+
+func getNodeData(node *ast.Node, strs *stringTable, extendedData *[]byte) uint32 {
+	t := getNodeDataType(node)
+	switch t {
+	case NodeDataTypeChildren:
+		return t | getNodeDefinedData(node) | uint32(getChildrenPropertyMask(node))
+	case NodeDataTypeString:
+		return t | getNodeDefinedData(node) | recordNodeStrings(node, strs)
+	case NodeDataTypeExtendedData:
+		return t | getNodeDefinedData(node) | recordExtendedData(node, strs, extendedData)
+	default:
+		panic("unreachable")
+	}
+}
+
+func getNodeDataType(node *ast.Node) uint32 {
+	switch node.Kind {
+	case ast.KindJsxText,
+		ast.KindIdentifier,
+		ast.KindPrivateIdentifier,
+		ast.KindStringLiteral,
+		ast.KindNumericLiteral,
+		ast.KindBigIntLiteral,
+		ast.KindRegularExpressionLiteral,
+		ast.KindNoSubstitutionTemplateLiteral,
+		ast.KindJSDocText:
+		return NodeDataTypeString
+	case ast.KindTemplateHead,
+		ast.KindTemplateMiddle,
+		ast.KindTemplateTail,
+		ast.KindSourceFile:
+		return NodeDataTypeExtendedData
+	default:
+		return NodeDataTypeChildren
+	}
+}
+
+// getChildrenPropertyMask returns a mask of which children properties are present in the node.
+// It is defined for node kinds that have more than one property that is a pointer to a child node.
+// Example: QualifiedName has two children properties: Left and Right, which are visited in that order.
+// result&1 is non-zero if Left is present, and result&2 is non-zero if Right is present. If the client
+// knows that QualifiedName has properties ["Left", "Right"] and sees an encoded node with only one
+// child, it can use the mask to determine which property is present.
+func getChildrenPropertyMask(node *ast.Node) uint8 {
+	switch node.Kind {
+	case ast.KindQualifiedName:
+		n := node.AsQualifiedName()
+		return (boolToByte(n.Left != nil) << 0) | (boolToByte(n.Right != nil) << 1)
+	case ast.KindTypeParameter:
+		n := node.AsTypeParameter()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Constraint != nil) << 2) | (boolToByte(n.DefaultType != nil) << 3)
+	case ast.KindIfStatement:
+		n := node.AsIfStatement()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.ThenStatement != nil) << 1) | (boolToByte(n.ElseStatement != nil) << 2)
+	case ast.KindDoStatement:
+		n := node.AsDoStatement()
+		return (boolToByte(n.Statement != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
+	case ast.KindWhileStatement:
+		n := node.AsWhileStatement()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
+	case ast.KindForStatement:
+		n := node.AsForStatement()
+		return (boolToByte(n.Initializer != nil) << 0) | (boolToByte(n.Condition != nil) << 1) | (boolToByte(n.Incrementor != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
+	case ast.KindForInStatement, ast.KindForOfStatement:
+		n := node.AsForInOrOfStatement()
+		return (boolToByte(n.AwaitModifier != nil) << 0) | (boolToByte(n.Initializer != nil) << 1) | (boolToByte(n.Expression != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
+	case ast.KindWithStatement:
+		n := node.AsWithStatement()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
+	case ast.KindSwitchStatement:
+		n := node.AsSwitchStatement()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.CaseBlock != nil) << 1)
+	case ast.KindCaseClause, ast.KindDefaultClause:
+		n := node.AsCaseOrDefaultClause()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statements != nil) << 1)
+	case ast.KindTryStatement:
+		n := node.AsTryStatement()
+		return (boolToByte(n.TryBlock != nil) << 0) | (boolToByte(n.CatchClause != nil) << 1) | (boolToByte(n.FinallyBlock != nil) << 2)
+	case ast.KindCatchClause:
+		n := node.AsCatchClause()
+		return (boolToByte(n.VariableDeclaration != nil) << 0) | (boolToByte(n.Block != nil) << 1)
+	case ast.KindLabeledStatement:
+		n := node.AsLabeledStatement()
+		return (boolToByte(n.Label != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
+	case ast.KindVariableStatement:
+		n := node.AsVariableStatement()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.DeclarationList != nil) << 1)
+	case ast.KindVariableDeclaration:
+		n := node.AsVariableDeclaration()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.ExclamationToken != nil) << 1) | (boolToByte(n.Type != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
+	case ast.KindParameter:
+		n := node.AsParameterDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.DotDotDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Initializer != nil) << 5)
+	case ast.KindBindingElement:
+		n := node.AsBindingElement()
+		return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.PropertyName != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
+	case ast.KindFunctionDeclaration:
+		n := node.AsFunctionDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
+	case ast.KindInterfaceDeclaration:
+		n := node.AsInterfaceDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
+	case ast.KindTypeAliasDeclaration:
+		n := node.AsTypeAliasDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
+	case ast.KindEnumMember:
+		n := node.AsEnumMember()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
+	case ast.KindEnumDeclaration:
+		n := node.AsEnumDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Members != nil) << 2)
+	case ast.KindModuleDeclaration:
+		n := node.AsModuleDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Body != nil) << 2)
+	case ast.KindImportEqualsDeclaration:
+		n := node.AsImportEqualsDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.ModuleReference != nil) << 2)
+	case ast.KindImportDeclaration:
+		n := node.AsImportDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
+	case ast.KindImportSpecifier:
+		n := node.AsImportSpecifier()
+		return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
+	case ast.KindImportClause:
+		n := node.AsImportClause()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.NamedBindings != nil) << 1)
+	case ast.KindExportAssignment:
+		n := node.AsExportAssignment()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
+	case ast.KindNamespaceExportDeclaration:
+		n := node.AsNamespaceExportDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
+	case ast.KindExportDeclaration:
+		n := node.AsExportDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.ExportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
+	case ast.KindExportSpecifier:
+		n := node.AsExportSpecifier()
+		return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
+	case ast.KindCallSignature:
+		n := node.AsCallSignatureDeclaration()
+		return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindConstructSignature:
+		n := node.AsConstructSignatureDeclaration()
+		return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindConstructor:
+		n := node.AsConstructorDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Body != nil) << 4)
+	case ast.KindGetAccessor:
+		n := node.AsGetAccessorDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
+	case ast.KindSetAccessor:
+		n := node.AsSetAccessorDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
+	case ast.KindIndexSignature:
+		n := node.AsIndexSignatureDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindMethodSignature:
+		n := node.AsMethodSignatureDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5)
+	case ast.KindMethodDeclaration:
+		n := node.AsMethodDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.PostfixToken != nil) << 3) | (boolToByte(n.TypeParameters != nil) << 4) | (boolToByte(n.Parameters != nil) << 5) | (boolToByte(n.Type != nil) << 6) | (boolToByte(n.Body != nil) << 7)
+	case ast.KindPropertySignature:
+		n := node.AsPropertySignatureDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
+	case ast.KindPropertyDeclaration:
+		n := node.AsPropertyDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
+	case ast.KindBinaryExpression:
+		n := node.AsBinaryExpression()
+		return (boolToByte(n.Left != nil) << 0) | (boolToByte(n.OperatorToken != nil) << 1) | (boolToByte(n.Right != nil) << 2)
+	case ast.KindYieldExpression:
+		n := node.AsYieldExpression()
+		return (boolToByte(n.AsteriskToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
+	case ast.KindArrowFunction:
+		n := node.AsArrowFunction()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.EqualsGreaterThanToken != nil) << 4) | (boolToByte(n.Body != nil) << 5)
+	case ast.KindFunctionExpression:
+		n := node.AsFunctionExpression()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
+	case ast.KindAsExpression:
+		n := node.AsAsExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
+	case ast.KindSatisfiesExpression:
+		n := node.AsSatisfiesExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
+	case ast.KindConditionalExpression:
+		n := node.AsConditionalExpression()
+		return (boolToByte(n.Condition != nil) << 0) | (boolToByte(n.QuestionToken != nil) << 1) | (boolToByte(n.WhenTrue != nil) << 2) | (boolToByte(n.ColonToken != nil) << 3) | (boolToByte(n.WhenFalse != nil) << 4)
+	case ast.KindPropertyAccessExpression:
+		n := node.AsPropertyAccessExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2)
+	case ast.KindElementAccessExpression:
+		n := node.AsElementAccessExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.ArgumentExpression != nil) << 2)
+	case ast.KindCallExpression:
+		n := node.AsCallExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Arguments != nil) << 3)
+	case ast.KindNewExpression:
+		n := node.AsNewExpression()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Arguments != nil) << 2)
+	case ast.KindTemplateExpression:
+		n := node.AsTemplateExpression()
+		return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
+	case ast.KindTemplateSpan:
+		n := node.AsTemplateSpan()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
+	case ast.KindTaggedTemplateExpression:
+		n := node.AsTaggedTemplateExpression()
+		return (boolToByte(n.Tag != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Template != nil) << 3)
+	case ast.KindPropertyAssignment:
+		n := node.AsPropertyAssignment()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
+	case ast.KindShorthandPropertyAssignment:
+		n := node.AsShorthandPropertyAssignment()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.EqualsToken != nil) << 3) | (boolToByte(n.ObjectAssignmentInitializer != nil) << 4)
+	case ast.KindTypeAssertionExpression:
+		n := node.AsTypeAssertion()
+		return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
+	case ast.KindConditionalType:
+		n := node.AsConditionalTypeNode()
+		return (boolToByte(n.CheckType != nil) << 0) | (boolToByte(n.ExtendsType != nil) << 1) | (boolToByte(n.TrueType != nil) << 2) | (boolToByte(n.FalseType != nil) << 3)
+	case ast.KindIndexedAccessType:
+		n := node.AsIndexedAccessTypeNode()
+		return (boolToByte(n.ObjectType != nil) << 0) | (boolToByte(n.IndexType != nil) << 1)
+	case ast.KindTypeReference:
+		n := node.AsTypeReferenceNode()
+		return (boolToByte(n.TypeName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
+	case ast.KindExpressionWithTypeArguments:
+		n := node.AsExpressionWithTypeArguments()
+		return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
+	case ast.KindTypePredicate:
+		n := node.AsTypePredicateNode()
+		return (boolToByte(n.AssertsModifier != nil) << 0) | (boolToByte(n.ParameterName != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindImportType:
+		n := node.AsImportTypeNode()
+		return (boolToByte(n.Argument != nil) << 0) | (boolToByte(n.Attributes != nil) << 1) | (boolToByte(n.Qualifier != nil) << 2) | (boolToByte(n.TypeArguments != nil) << 3)
+	case ast.KindImportAttribute:
+		n := node.AsImportAttribute()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Value != nil) << 1)
+	case ast.KindTypeQuery:
+		n := node.AsTypeQueryNode()
+		return (boolToByte(n.ExprName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
+	case ast.KindMappedType:
+		n := node.AsMappedTypeNode()
+		return (boolToByte(n.ReadonlyToken != nil) << 0) | (boolToByte(n.TypeParameter != nil) << 1) | (boolToByte(n.NameType != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Members != nil) << 5)
+	case ast.KindNamedTupleMember:
+		n := node.AsNamedTupleMember()
+		return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.QuestionToken != nil) << 2) | (boolToByte(n.Type != nil) << 3)
+	case ast.KindFunctionType:
+		n := node.AsFunctionTypeNode()
+		return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindConstructorType:
+		n := node.AsConstructorTypeNode()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
+	case ast.KindTemplateLiteralType:
+		n := node.AsTemplateLiteralTypeNode()
+		return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
+	case ast.KindTemplateLiteralTypeSpan:
+		n := node.AsTemplateLiteralTypeSpan()
+		return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
+	case ast.KindJsxElement:
+		n := node.AsJsxElement()
+		return (boolToByte(n.OpeningElement != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingElement != nil) << 2)
+	case ast.KindJsxNamespacedName:
+		n := node.AsJsxNamespacedName()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Namespace != nil) << 1)
+	case ast.KindJsxOpeningElement:
+		n := node.AsJsxOpeningElement()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
+	case ast.KindJsxSelfClosingElement:
+		n := node.AsJsxSelfClosingElement()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
+	case ast.KindJsxFragment:
+		n := node.AsJsxFragment()
+		return (boolToByte(n.OpeningFragment != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingFragment != nil) << 2)
+	case ast.KindJsxAttribute:
+		n := node.AsJsxAttribute()
+		return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
+	case ast.KindJsxExpression:
+		n := node.AsJsxExpression()
+		return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
+	case ast.KindJSDoc:
+		n := node.AsJSDoc()
+		return (boolToByte(n.Comment != nil) << 0) | (boolToByte(n.Tags != nil) << 1)
+	case ast.KindJSDocTypeTag:
+		n := node.AsJSDocTypeTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocTag:
+		n := node.AsJSDocUnknownTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocTemplateTag:
+		n := node.AsJSDocTemplateTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Constraint != nil) << 1) | (boolToByte(n.TypeParameters() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
+	case ast.KindJSDocReturnTag:
+		n := node.AsJSDocReturnTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocPublicTag:
+		n := node.AsJSDocPublicTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocPrivateTag:
+		n := node.AsJSDocPrivateTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocProtectedTag:
+		n := node.AsJSDocProtectedTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocReadonlyTag:
+		n := node.AsJSDocReadonlyTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocOverrideTag:
+		n := node.AsJSDocOverrideTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocDeprecatedTag:
+		n := node.AsJSDocDeprecatedTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
+	case ast.KindJSDocSeeTag:
+		n := node.AsJSDocSeeTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.NameExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocImplementsTag:
+		n := node.AsJSDocImplementsTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocAugmentsTag:
+		n := node.AsJSDocAugmentsTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocSatisfiesTag:
+		n := node.AsJSDocSatisfiesTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocThisTag:
+		n := node.AsJSDocThisTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocImportTag:
+		n := node.AsJSDocImportTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3) | (boolToByte(n.Comment != nil) << 4)
+	case ast.KindJSDocCallbackTag:
+		n := node.AsJSDocCallbackTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.FullName != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
+	case ast.KindJSDocOverloadTag:
+		n := node.AsJSDocOverloadTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
+	case ast.KindJSDocTypedefTag:
+		n := node.AsJSDocTypedefTag()
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
+	case ast.KindJSDocSignature:
+		n := node.AsJSDocSignature()
+		return (boolToByte(n.TypeParameters() != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
+	case ast.KindClassStaticBlockDeclaration:
+		n := node.AsClassStaticBlockDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Body != nil) << 1)
+	case ast.KindClassDeclaration:
+		n := node.AsClassDeclaration()
+		return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
+	case ast.KindJSDocPropertyTag:
+		n := node.AsJSDocPropertyTag()
+		if n.IsNameFirst {
+			return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1)
+		}
+		return (boolToByte(n.TypeExpression != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
+	case ast.KindJSDocParameterTag:
+		n := node.AsJSDocParameterTag()
+		if n.IsNameFirst {
+			return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeExpression != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
+		}
+		return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
+	default:
+		return 0
+	}
+}
+
+func getNodeDefinedData(node *ast.Node) uint32 {
+	switch node.Kind {
+	case ast.KindJSDocTypeLiteral:
+		n := node.AsJSDocTypeLiteral()
+		return uint32(boolToByte(n.IsArrayType)) << 24
+	case ast.KindImportSpecifier:
+		n := node.AsImportSpecifier()
+		return uint32(boolToByte(n.IsTypeOnly)) << 24
+	case ast.KindImportClause:
+		n := node.AsImportClause()
+		return uint32(boolToByte(n.IsTypeOnly)) << 24
+	case ast.KindExportSpecifier:
+		n := node.AsExportSpecifier()
+		return uint32(boolToByte(n.IsTypeOnly)) << 24
+	case ast.KindImportType:
+		n := node.AsImportTypeNode()
+		return uint32(boolToByte(n.IsTypeOf)) << 24
+	case ast.KindImportEqualsDeclaration:
+		n := node.AsImportEqualsDeclaration()
+		return uint32(boolToByte(n.IsTypeOnly)) << 24
+	case ast.KindExportAssignment:
+		n := node.AsExportAssignment()
+		return uint32(boolToByte(n.IsExportEquals)) << 24
+	case ast.KindExportDeclaration:
+		n := node.AsExportDeclaration()
+		return uint32(boolToByte(n.IsTypeOnly)) << 24
+	case ast.KindBlock:
+		n := node.AsBlock()
+		return uint32(boolToByte(n.Multiline)) << 24
+	case ast.KindArrayLiteralExpression:
+		n := node.AsArrayLiteralExpression()
+		return uint32(boolToByte(n.MultiLine)) << 24
+	case ast.KindObjectLiteralExpression:
+		n := node.AsObjectLiteralExpression()
+		return uint32(boolToByte(n.MultiLine)) << 24
+	case ast.KindJSDocPropertyTag:
+		n := node.AsJSDocPropertyTag()
+		return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
+	case ast.KindJSDocParameterTag:
+		n := node.AsJSDocParameterTag()
+		return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
+	case ast.KindJsxText:
+		n := node.AsJsxText()
+		return uint32(boolToByte(n.ContainsOnlyTriviaWhiteSpaces)) << 24
+	case ast.KindVariableDeclarationList:
+		n := node.AsVariableDeclarationList()
+		return uint32(n.Flags & (ast.NodeFlagsLet | ast.NodeFlagsConst) << 24)
+	case ast.KindImportAttributes:
+		n := node.AsImportAttributes()
+		return uint32(boolToByte(n.MultiLine))<<24 | uint32(boolToByte(n.Token == ast.KindAssertKeyword))<<25
+	}
+	return 0
+}
+
+func recordNodeStrings(node *ast.Node, strs *stringTable) uint32 {
+	switch node.Kind {
+	case ast.KindJsxText:
+		return strs.add(node.AsJsxText().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindIdentifier:
+		return strs.add(node.AsIdentifier().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindPrivateIdentifier:
+		return strs.add(node.AsPrivateIdentifier().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindStringLiteral:
+		return strs.add(node.AsStringLiteral().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindNumericLiteral:
+		return strs.add(node.AsNumericLiteral().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindBigIntLiteral:
+		return strs.add(node.AsBigIntLiteral().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindRegularExpressionLiteral:
+		return strs.add(node.AsRegularExpressionLiteral().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindNoSubstitutionTemplateLiteral:
+		return strs.add(node.AsNoSubstitutionTemplateLiteral().Text, node.Kind, node.Pos(), node.End())
+	case ast.KindJSDocText:
+		return strs.add(node.AsJSDocText().Text, node.Kind, node.Pos(), node.End())
+	default:
+		panic(fmt.Sprintf("Unexpected node kind %v", node.Kind))
+	}
+}
+
+func recordExtendedData(node *ast.Node, strs *stringTable, extendedData *[]byte) uint32 {
+	offset := uint32(len(*extendedData))
+	var text, rawText string
+	var templateFlags uint32
+	switch node.Kind {
+	case ast.KindTemplateTail:
+		n := node.AsTemplateTail()
+		text = n.Text
+		rawText = n.RawText
+		templateFlags = uint32(n.TemplateFlags)
+	case ast.KindTemplateMiddle:
+		n := node.AsTemplateMiddle()
+		text = n.Text
+		rawText = n.RawText
+		templateFlags = uint32(n.TemplateFlags)
+	case ast.KindTemplateHead:
+		n := node.AsTemplateHead()
+		text = n.Text
+		rawText = n.RawText
+		templateFlags = uint32(n.TemplateFlags)
+	}
+	textIndex := strs.add(text, node.Kind, node.Pos(), node.End())
+	rawTextIndex := strs.add(rawText, node.Kind, node.Pos(), node.End())
+	*extendedData = appendUint32s(*extendedData, textIndex, rawTextIndex, templateFlags)
+	return offset
+}
+
+func boolToByte(b bool) byte {
+	if b {
+		return 1
+	}
+	return 0
+}
diff --git a/internal/api/encoder/encoder_test.go b/internal/api/encoder/encoder_test.go
new file mode 100644
index 0000000000..ce616dfc26
--- /dev/null
+++ b/internal/api/encoder/encoder_test.go
@@ -0,0 +1,95 @@
+package encoder_test
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/microsoft/typescript-go/internal/api/encoder"
+	"github.com/microsoft/typescript-go/internal/ast"
+	"github.com/microsoft/typescript-go/internal/core"
+	"github.com/microsoft/typescript-go/internal/parser"
+	"github.com/microsoft/typescript-go/internal/repo"
+	"github.com/microsoft/typescript-go/internal/scanner"
+	"github.com/microsoft/typescript-go/internal/testutil/baseline"
+	"gotest.tools/v3/assert"
+)
+
+func TestEncodeSourceFile(t *testing.T) {
+	t.Parallel()
+	sourceFile := parser.ParseSourceFile("/test.ts", "/test.ts", "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptTargetESNext, scanner.JSDocParsingModeParseAll)
+	t.Run("baseline", func(t *testing.T) {
+		t.Parallel()
+		buf, err := encoder.EncodeSourceFile(sourceFile, "")
+		assert.NilError(t, err)
+
+		str := formatEncodedSourceFile(buf)
+		baseline.Run(t, "encodeSourceFile.txt", str, baseline.Options{
+			Subfolder: "api",
+		})
+	})
+}
+
+func BenchmarkEncodeSourceFile(b *testing.B) {
+	repo.SkipIfNoTypeScriptSubmodule(b)
+	filePath := filepath.Join(repo.TypeScriptSubmodulePath, "src/compiler/checker.ts")
+	fileContent, err := os.ReadFile(filePath)
+	assert.NilError(b, err)
+	sourceFile := parser.ParseSourceFile(
+		"/checker.ts",
+		"/checker.ts",
+		string(fileContent),
+		core.ScriptTargetESNext,
+		scanner.JSDocParsingModeParseAll,
+	)
+
+	for b.Loop() {
+		_, err := encoder.EncodeSourceFile(sourceFile, "")
+		assert.NilError(b, err)
+	}
+}
+
+func readUint32(buf []byte, offset int) uint32 {
+	return binary.LittleEndian.Uint32(buf[offset : offset+4])
+}
+
+func formatEncodedSourceFile(encoded []byte) string {
+	var result strings.Builder
+	var getIndent func(parentIndex uint32) string
+	offsetNodes := readUint32(encoded, encoder.HeaderOffsetNodes)
+	offsetStringOffsets := readUint32(encoded, encoder.HeaderOffsetStringOffsets)
+	offsetStrings := readUint32(encoded, encoder.HeaderOffsetStringData)
+	getIndent = func(parentIndex uint32) string {
+		if parentIndex == 0 {
+			return ""
+		}
+		return "  " + getIndent(readUint32(encoded, int(offsetNodes)+int(parentIndex)*encoder.NodeSize+encoder.NodeOffsetParent))
+	}
+	j := 1
+	for i := int(offsetNodes) + encoder.NodeSize; i < len(encoded); i += encoder.NodeSize {
+		kind := readUint32(encoded, i+encoder.NodeOffsetKind)
+		pos := readUint32(encoded, i+encoder.NodeOffsetPos)
+		end := readUint32(encoded, i+encoder.NodeOffsetEnd)
+		parentIndex := readUint32(encoded, i+encoder.NodeOffsetParent)
+		result.WriteString(getIndent(parentIndex))
+		if kind == encoder.SyntaxKindNodeList {
+			result.WriteString("NodeList")
+		} else {
+			result.WriteString(ast.Kind(kind).String())
+		}
+		if ast.Kind(kind) == ast.KindIdentifier || ast.Kind(kind) == ast.KindStringLiteral {
+			stringIndex := readUint32(encoded, i+encoder.NodeOffsetData) & encoder.NodeDataStringIndexMask
+			strStart := readUint32(encoded, int(offsetStringOffsets+stringIndex*4))
+			strEnd := readUint32(encoded, int(offsetStringOffsets+stringIndex*4)+4)
+			str := string(encoded[offsetStrings+strStart : offsetStrings+strEnd])
+			result.WriteString(fmt.Sprintf(" \"%s\"", str))
+		}
+		fmt.Fprintf(&result, " [%d, %d), i=%d, next=%d", pos, end, j, encoded[i+encoder.NodeOffsetNext])
+		result.WriteString("\n")
+		j++
+	}
+	return result.String()
+}
diff --git a/internal/api/encoder/stringtable.go b/internal/api/encoder/stringtable.go
new file mode 100644
index 0000000000..098015d87a
--- /dev/null
+++ b/internal/api/encoder/stringtable.go
@@ -0,0 +1,68 @@
+package encoder
+
+import (
+	"strings"
+
+	"github.com/microsoft/typescript-go/internal/ast"
+)
+
+type stringTable struct {
+	fileText     string
+	otherStrings *strings.Builder
+	// offsets are pos/end pairs
+	offsets []uint32
+}
+
+func newStringTable(fileText string, stringCount int) *stringTable {
+	builder := &strings.Builder{}
+	return &stringTable{
+		fileText:     fileText,
+		otherStrings: builder,
+		offsets:      make([]uint32, 0, stringCount*2),
+	}
+}
+
+func (t *stringTable) add(text string, kind ast.Kind, pos int, end int) uint32 {
+	index := uint32(len(t.offsets))
+	if kind == ast.KindSourceFile {
+		t.offsets = append(t.offsets, uint32(pos), uint32(end))
+		return index
+	}
+	length := len(text)
+	if end-pos > 0 {
+		// pos includes leading trivia, but we can usually infer the actual start of the
+		// string from the kind and end
+		endOffset := 0
+		if kind == ast.KindStringLiteral || kind == ast.KindTemplateTail || kind == ast.KindNoSubstitutionTemplateLiteral {
+			endOffset = 1
+		}
+		end = end - endOffset
+		start := end - length
+		fileSlice := t.fileText[start:end]
+		if fileSlice == text {
+			t.offsets = append(t.offsets, uint32(start), uint32(end))
+			return index
+		}
+	}
+	// no exact match, so we need to add it to the string table
+	offset := len(t.fileText) + t.otherStrings.Len()
+	t.otherStrings.WriteString(text)
+	t.offsets = append(t.offsets, uint32(offset), uint32(offset+length))
+	return index
+}
+
+func (t *stringTable) encode() []byte {
+	result := make([]byte, 0, t.encodedLength())
+	result = appendUint32s(result, t.offsets...)
+	result = append(result, t.fileText...)
+	result = append(result, t.otherStrings.String()...)
+	return result
+}
+
+func (t *stringTable) stringLength() int {
+	return len(t.fileText) + t.otherStrings.Len()
+}
+
+func (t *stringTable) encodedLength() int {
+	return len(t.offsets)*4 + len(t.fileText) + t.otherStrings.Len()
+}
diff --git a/internal/api/host.go b/internal/api/host.go
new file mode 100644
index 0000000000..83d45a8c60
--- /dev/null
+++ b/internal/api/host.go
@@ -0,0 +1,10 @@
+package api
+
+import "github.com/microsoft/typescript-go/internal/vfs"
+
+type APIHost interface {
+	FS() vfs.FS
+	DefaultLibraryPath() string
+	GetCurrentDirectory() string
+	NewLine() string
+}
diff --git a/internal/api/proto.go b/internal/api/proto.go
new file mode 100644
index 0000000000..e50684783f
--- /dev/null
+++ b/internal/api/proto.go
@@ -0,0 +1,217 @@
+package api
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/microsoft/typescript-go/internal/ast"
+	"github.com/microsoft/typescript-go/internal/checker"
+	"github.com/microsoft/typescript-go/internal/core"
+	"github.com/microsoft/typescript-go/internal/project"
+)
+
+var (
+	ErrInvalidRequest = errors.New("api: invalid request")
+	ErrClientError    = errors.New("api: client error")
+)
+
+type Method string
+
+type Handle[T any] string
+
+const (
+	handlePrefixProject = 'p'
+	handlePrefixSymbol  = 's'
+	handlePrefixType    = 't'
+	handlePrefixFile    = 'f'
+	handlePrefixNode    = 'n'
+)
+
+func ProjectHandle(p *project.Project) Handle[project.Project] {
+	return createHandle[project.Project](handlePrefixProject, p.Name())
+}
+
+func SymbolHandle(symbol *ast.Symbol) Handle[ast.Symbol] {
+	return createHandle[ast.Symbol](handlePrefixSymbol, ast.GetSymbolId(symbol))
+}
+
+func TypeHandle(t *checker.Type) Handle[checker.Type] {
+	return createHandle[checker.Type](handlePrefixType, t.Id())
+}
+
+func FileHandle(file *ast.SourceFile) Handle[ast.SourceFile] {
+	return createHandle[ast.SourceFile](handlePrefixFile, ast.GetNodeId(file.AsNode()))
+}
+
+func NodeHandle(node *ast.Node) Handle[ast.Node] {
+	fileHandle := FileHandle(ast.GetSourceFileOfNode(node))
+	return Handle[ast.Node](fmt.Sprintf("%s.%d.%d", fileHandle, node.Pos(), node.Kind))
+}
+
+func parseNodeHandle(handle Handle[ast.Node]) (Handle[ast.SourceFile], int, ast.Kind, error) {
+	parts := strings.SplitN(string(handle), ".", 3)
+	if len(parts) != 3 {
+		return "", 0, 0, fmt.Errorf("invalid node handle %q", handle)
+	}
+
+	fileHandle := Handle[ast.SourceFile](parts[0])
+	pos, err := strconv.ParseInt(parts[1], 10, 32)
+	if err != nil {
+		return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
+	}
+	kind, err := strconv.ParseInt(parts[2], 10, 16)
+	if err != nil {
+		return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
+	}
+	return fileHandle, int(pos), ast.Kind(kind), nil
+}
+
+func createHandle[T any](prefix rune, id any) Handle[T] {
+	return Handle[T](fmt.Sprintf("%c%016x", prefix, id))
+}
+
+const (
+	MethodConfigure Method = "configure"
+	MethodRelease   Method = "release"
+
+	MethodParseConfigFile       Method = "parseConfigFile"
+	MethodLoadProject           Method = "loadProject"
+	MethodGetSymbolAtPosition   Method = "getSymbolAtPosition"
+	MethodGetSymbolsAtPositions Method = "getSymbolsAtPositions"
+	MethodGetSymbolAtLocation   Method = "getSymbolAtLocation"
+	MethodGetSymbolsAtLocations Method = "getSymbolsAtLocations"
+	MethodGetTypeOfSymbol       Method = "getTypeOfSymbol"
+	MethodGetTypesOfSymbols     Method = "getTypesOfSymbols"
+	MethodGetSourceFile         Method = "getSourceFile"
+)
+
+var unmarshalers = map[Method]func([]byte) (any, error){
+	MethodRelease:               unmarshallerFor[string],
+	MethodParseConfigFile:       unmarshallerFor[ParseConfigFileParams],
+	MethodLoadProject:           unmarshallerFor[LoadProjectParams],
+	MethodGetSourceFile:         unmarshallerFor[GetSourceFileParams],
+	MethodGetSymbolAtPosition:   unmarshallerFor[GetSymbolAtPositionParams],
+	MethodGetSymbolsAtPositions: unmarshallerFor[GetSymbolsAtPositionsParams],
+	MethodGetSymbolAtLocation:   unmarshallerFor[GetSymbolAtLocationParams],
+	MethodGetSymbolsAtLocations: unmarshallerFor[GetSymbolsAtLocationsParams],
+	MethodGetTypeOfSymbol:       unmarshallerFor[GetTypeOfSymbolParams],
+	MethodGetTypesOfSymbols:     unmarshallerFor[GetTypesOfSymbolsParams],
+}
+
+type ConfigureParams struct {
+	Callbacks []string `json:"callbacks"`
+	LogFile   string   `json:"logFile"`
+}
+
+type ParseConfigFileParams struct {
+	FileName string `json:"fileName"`
+}
+
+type ConfigFileResponse struct {
+	FileNames []string              `json:"fileNames"`
+	Options   *core.CompilerOptions `json:"options"`
+}
+
+type LoadProjectParams struct {
+	ConfigFileName string `json:"configFileName"`
+}
+
+type ProjectResponse struct {
+	Id              Handle[project.Project] `json:"id"`
+	ConfigFileName  string                  `json:"configFileName"`
+	RootFiles       []string                `json:"rootFiles"`
+	CompilerOptions *core.CompilerOptions   `json:"compilerOptions"`
+}
+
+func NewProjectResponse(project *project.Project) *ProjectResponse {
+	return &ProjectResponse{
+		Id:              ProjectHandle(project),
+		ConfigFileName:  project.Name(),
+		RootFiles:       project.GetRootFileNames(),
+		CompilerOptions: project.GetCompilerOptions(),
+	}
+}
+
+type GetSymbolAtPositionParams struct {
+	Project  Handle[project.Project] `json:"project"`
+	FileName string                  `json:"fileName"`
+	Position uint32                  `json:"position"`
+}
+
+type GetSymbolsAtPositionsParams struct {
+	Project   Handle[project.Project] `json:"project"`
+	FileName  string                  `json:"fileName"`
+	Positions []uint32                `json:"positions"`
+}
+
+type GetSymbolAtLocationParams struct {
+	Project  Handle[project.Project] `json:"project"`
+	Location Handle[ast.Node]        `json:"location"`
+}
+
+type GetSymbolsAtLocationsParams struct {
+	Project   Handle[project.Project] `json:"project"`
+	Locations []Handle[ast.Node]      `json:"locations"`
+}
+
+type SymbolResponse struct {
+	Id         Handle[ast.Symbol] `json:"id"`
+	Name       string             `json:"name"`
+	Flags      uint32             `json:"flags"`
+	CheckFlags uint32             `json:"checkFlags"`
+}
+
+func NewSymbolResponse(symbol *ast.Symbol) *SymbolResponse {
+	return &SymbolResponse{
+		Id:         SymbolHandle(symbol),
+		Name:       symbol.Name,
+		Flags:      uint32(symbol.Flags),
+		CheckFlags: uint32(symbol.CheckFlags),
+	}
+}
+
+type GetTypeOfSymbolParams struct {
+	Project Handle[project.Project] `json:"project"`
+	Symbol  Handle[ast.Symbol]      `json:"symbol"`
+}
+
+type GetTypesOfSymbolsParams struct {
+	Project Handle[project.Project] `json:"project"`
+	Symbols []Handle[ast.Symbol]    `json:"symbols"`
+}
+
+type TypeResponse struct {
+	Id    Handle[checker.Type] `json:"id"`
+	Flags uint32               `json:"flags"`
+}
+
+func NewTypeData(t *checker.Type) *TypeResponse {
+	return &TypeResponse{
+		Id:    TypeHandle(t),
+		Flags: uint32(t.Flags()),
+	}
+}
+
+type GetSourceFileParams struct {
+	Project  Handle[project.Project] `json:"project"`
+	FileName string                  `json:"fileName"`
+}
+
+func unmarshalPayload(method string, payload json.RawMessage) (any, error) {
+	unmarshaler, ok := unmarshalers[Method(method)]
+	if !ok {
+		return nil, fmt.Errorf("unknown API method %q", method)
+	}
+	return unmarshaler(payload)
+}
+
+func unmarshallerFor[T any](data []byte) (any, error) {
+	var v T
+	if err := json.Unmarshal(data, &v); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal %T: %w", (*T)(nil), err)
+	}
+	return &v, nil
+}
diff --git a/internal/api/server.go b/internal/api/server.go
new file mode 100644
index 0000000000..2b60dd855a
--- /dev/null
+++ b/internal/api/server.go
@@ -0,0 +1,477 @@
+package api
+
+import (
+	"bufio"
+	"encoding/binary"
+	"encoding/json"
+	"fmt"
+	"io"
+	"sync"
+
+	"github.com/microsoft/typescript-go/internal/bundled"
+	"github.com/microsoft/typescript-go/internal/project"
+	"github.com/microsoft/typescript-go/internal/vfs"
+	"github.com/microsoft/typescript-go/internal/vfs/osvfs"
+)
+
+//go:generate go tool golang.org/x/tools/cmd/stringer -type=MessageType -output=stringer_generated.go
+
+type MessageType uint8
+
+const (
+	MessageTypeUnknown MessageType = iota
+	MessageTypeRequest
+	MessageTypeCallResponse
+	MessageTypeCallError
+	MessageTypeResponse
+	MessageTypeError
+	MessageTypeCall
+)
+
+func (m MessageType) IsValid() bool {
+	return m >= MessageTypeRequest && m <= MessageTypeCall
+}
+
+type MessagePackType uint8
+
+const (
+	MessagePackTypeFixedArray3 MessagePackType = 0x93
+	MessagePackTypeBin8        MessagePackType = 0xC4
+	MessagePackTypeBin16       MessagePackType = 0xC5
+	MessagePackTypeBin32       MessagePackType = 0xC6
+	MessagePackTypeU8          MessagePackType = 0xCC
+)
+
+type Callback int
+
+const (
+	CallbackDirectoryExists Callback = 1 << iota
+	CallbackFileExists
+	CallbackGetAccessibleEntries
+	CallbackReadFile
+	CallbackRealpath
+)
+
+type ServerOptions struct {
+	In                 io.Reader
+	Out                io.Writer
+	Err                io.Writer
+	Cwd                string
+	NewLine            string
+	DefaultLibraryPath string
+}
+
+var (
+	_ APIHost = (*Server)(nil)
+	_ vfs.FS  = (*Server)(nil)
+)
+
+type Server struct {
+	r      *bufio.Reader
+	w      *bufio.Writer
+	stderr io.Writer
+
+	cwd                string
+	newLine            string
+	fs                 vfs.FS
+	defaultLibraryPath string
+
+	callbackMu       sync.Mutex
+	enabledCallbacks Callback
+	logger           *project.Logger
+	api              *API
+
+	requestId int
+}
+
+func NewServer(options *ServerOptions) *Server {
+	if options.Cwd == "" {
+		panic("Cwd is required")
+	}
+
+	server := &Server{
+		r:                  bufio.NewReader(options.In),
+		w:                  bufio.NewWriter(options.Out),
+		stderr:             options.Err,
+		cwd:                options.Cwd,
+		newLine:            options.NewLine,
+		fs:                 bundled.WrapFS(osvfs.FS()),
+		defaultLibraryPath: options.DefaultLibraryPath,
+	}
+	logger := project.NewLogger([]io.Writer{options.Err}, "", project.LogLevelVerbose)
+	api := NewAPI(server, APIOptions{
+		Logger: logger,
+	})
+	server.logger = logger
+	server.api = api
+	return server
+}
+
+// DefaultLibraryPath implements APIHost.
+func (s *Server) DefaultLibraryPath() string {
+	return s.defaultLibraryPath
+}
+
+// FS implements APIHost.
+func (s *Server) FS() vfs.FS {
+	return s
+}
+
+// GetCurrentDirectory implements APIHost.
+func (s *Server) GetCurrentDirectory() string {
+	return s.cwd
+}
+
+// NewLine implements APIHost.
+func (s *Server) NewLine() string {
+	return s.newLine
+}
+
+func (s *Server) Run() error {
+	for {
+		messageType, method, payload, err := s.readRequest("")
+		if err != nil {
+			return err
+		}
+
+		switch messageType {
+		case MessageTypeRequest:
+			result, err := s.handleRequest(method, payload)
+
+			if err != nil {
+				if err := s.sendError(method, err); err != nil {
+					return err
+				}
+			} else {
+				if err := s.sendResponse(method, result); err != nil {
+					return err
+				}
+			}
+		default:
+			return fmt.Errorf("%w: expected request, received: %s", ErrInvalidRequest, messageType.String())
+		}
+	}
+}
+
+func (s *Server) readRequest(expectedMethod string) (messageType MessageType, method string, payload []byte, err error) {
+	t, err := s.r.ReadByte()
+	if err != nil {
+		return messageType, method, payload, err
+	}
+	if MessagePackType(t) != MessagePackTypeFixedArray3 {
+		return messageType, method, payload, fmt.Errorf("%w: expected message to be encoded as fixed 3-element array (0x93), received: 0x%2x", ErrInvalidRequest, t)
+	}
+	t, err = s.r.ReadByte()
+	if err != nil {
+		return messageType, method, payload, err
+	}
+	if MessagePackType(t) != MessagePackTypeU8 {
+		return messageType, method, payload, fmt.Errorf("%w: expected first element of message tuple to be encoded as unsigned 8-bit int (0xcc), received: 0x%2x", ErrInvalidRequest, t)
+	}
+	rawMessageType, err := s.r.ReadByte()
+	if err != nil {
+		return messageType, method, payload, err
+	}
+	messageType = MessageType(rawMessageType)
+	if !messageType.IsValid() {
+		return messageType, method, payload, fmt.Errorf("%w: unknown message type: %d", ErrInvalidRequest, messageType)
+	}
+	rawMethod, err := s.readBin()
+	if err != nil {
+		return messageType, method, payload, err
+	}
+	method = string(rawMethod)
+	if expectedMethod != "" && method != expectedMethod {
+		return messageType, method, payload, fmt.Errorf("%w: expected method %q, received %q", ErrInvalidRequest, expectedMethod, method)
+	}
+	payload, err = s.readBin()
+	return messageType, method, payload, err
+}
+
+func (s *Server) readBin() ([]byte, error) {
+	// https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family
+	t, err := s.r.ReadByte()
+	if err != nil {
+		return nil, err
+	}
+	var size uint
+	switch MessagePackType(t) {
+	case MessagePackTypeBin8:
+		var size8 uint8
+		if err = binary.Read(s.r, binary.BigEndian, &size8); err != nil {
+			return nil, err
+		}
+		size = uint(size8)
+	case MessagePackTypeBin16:
+		var size16 uint16
+		if err = binary.Read(s.r, binary.BigEndian, &size16); err != nil {
+			return nil, err
+		}
+		size = uint(size16)
+	case MessagePackTypeBin32:
+		var size32 uint32
+		if err = binary.Read(s.r, binary.BigEndian, &size32); err != nil {
+			return nil, err
+		}
+		size = uint(size32)
+	default:
+		return nil, fmt.Errorf("%w: expected binary data length (0xc4-0xc6), received: 0x%2x", ErrInvalidRequest, t)
+	}
+	payload := make([]byte, size)
+	bytesRead, err := io.ReadFull(s.r, payload)
+	if err != nil {
+		return nil, err
+	}
+	if bytesRead != int(size) {
+		return nil, fmt.Errorf("%w: expected %d bytes, read %d", ErrInvalidRequest, size, bytesRead)
+	}
+	return payload, nil
+}
+
+func (s *Server) enableCallback(callback string) error {
+	switch callback {
+	case "directoryExists":
+		s.enabledCallbacks |= CallbackDirectoryExists
+	case "fileExists":
+		s.enabledCallbacks |= CallbackFileExists
+	case "getAccessibleEntries":
+		s.enabledCallbacks |= CallbackGetAccessibleEntries
+	case "readFile":
+		s.enabledCallbacks |= CallbackReadFile
+	case "realpath":
+		s.enabledCallbacks |= CallbackRealpath
+	default:
+		return fmt.Errorf("unknown callback: %s", callback)
+	}
+	return nil
+}
+
+func (s *Server) handleRequest(method string, payload []byte) ([]byte, error) {
+	s.requestId++
+	switch method {
+	case "configure":
+		return nil, s.handleConfigure(payload)
+	case "echo":
+		return payload, nil
+	default:
+		return s.api.HandleRequest(s.requestId, method, payload)
+	}
+}
+
+func (s *Server) handleConfigure(payload []byte) error {
+	var params *ConfigureParams
+	if err := json.Unmarshal(payload, &params); err != nil {
+		return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
+	}
+	for _, callback := range params.Callbacks {
+		if err := s.enableCallback(callback); err != nil {
+			return err
+		}
+	}
+	if params.LogFile != "" {
+		s.logger.SetFile(params.LogFile)
+	} else {
+		s.logger.SetFile("")
+	}
+	return nil
+}
+
+func (s *Server) sendResponse(method string, result []byte) error {
+	return s.writeMessage(MessageTypeResponse, method, result)
+}
+
+func (s *Server) sendError(method string, err error) error {
+	return s.writeMessage(MessageTypeError, method, []byte(err.Error()))
+}
+
+func (s *Server) writeMessage(messageType MessageType, method string, payload []byte) error {
+	if err := s.w.WriteByte(byte(MessagePackTypeFixedArray3)); err != nil {
+		return err
+	}
+	if err := s.w.WriteByte(byte(MessagePackTypeU8)); err != nil {
+		return err
+	}
+	if err := s.w.WriteByte(byte(messageType)); err != nil {
+		return err
+	}
+	if err := s.writeBin([]byte(method)); err != nil {
+		return err
+	}
+	if err := s.writeBin(payload); err != nil {
+		return err
+	}
+	return s.w.Flush()
+}
+
+func (s *Server) writeBin(payload []byte) error {
+	length := len(payload)
+	if length < 256 {
+		if err := s.w.WriteByte(byte(MessagePackTypeBin8)); err != nil {
+			return err
+		}
+		if err := s.w.WriteByte(byte(length)); err != nil {
+			return err
+		}
+	} else if length < 1<<16 {
+		if err := s.w.WriteByte(byte(MessagePackTypeBin16)); err != nil {
+			return err
+		}
+		if err := binary.Write(s.w, binary.BigEndian, uint16(length)); err != nil {
+			return err
+		}
+	} else {
+		if err := s.w.WriteByte(byte(MessagePackTypeBin32)); err != nil {
+			return err
+		}
+		if err := binary.Write(s.w, binary.BigEndian, uint32(length)); err != nil {
+			return err
+		}
+	}
+	_, err := s.w.Write(payload)
+	return err
+}
+
+func (s *Server) call(method string, payload any) ([]byte, error) {
+	s.callbackMu.Lock()
+	defer s.callbackMu.Unlock()
+	jsonPayload, err := json.Marshal(payload)
+	if err != nil {
+		return nil, err
+	}
+	if err = s.writeMessage(MessageTypeCall, method, jsonPayload); err != nil {
+		return nil, err
+	}
+
+	messageType, _, responsePayload, err := s.readRequest(method)
+	if err != nil {
+		return nil, err
+	}
+
+	if messageType != MessageTypeCallResponse && messageType != MessageTypeCallError {
+		return nil, fmt.Errorf("%w: expected call-response or call-error, received: %s", ErrInvalidRequest, messageType.String())
+	}
+
+	if messageType == MessageTypeCallError {
+		return nil, fmt.Errorf("%w: %s", ErrClientError, responsePayload)
+	}
+
+	return responsePayload, nil
+}
+
+// DirectoryExists implements vfs.FS.
+func (s *Server) DirectoryExists(path string) bool {
+	if s.enabledCallbacks&CallbackDirectoryExists != 0 {
+		result, err := s.call("directoryExists", path)
+		if err != nil {
+			panic(err)
+		}
+		if len(result) > 0 {
+			return string(result) == "true"
+		}
+	}
+	return s.fs.DirectoryExists(path)
+}
+
+// FileExists implements vfs.FS.
+func (s *Server) FileExists(path string) bool {
+	if s.enabledCallbacks&CallbackFileExists != 0 {
+		result, err := s.call("fileExists", path)
+		if err != nil {
+			panic(err)
+		}
+		if len(result) > 0 {
+			return string(result) == "true"
+		}
+	}
+	return s.fs.FileExists(path)
+}
+
+// GetAccessibleEntries implements vfs.FS.
+func (s *Server) GetAccessibleEntries(path string) vfs.Entries {
+	if s.enabledCallbacks&CallbackGetAccessibleEntries != 0 {
+		result, err := s.call("getAccessibleEntries", path)
+		if err != nil {
+			panic(err)
+		}
+		if len(result) > 0 {
+			var rawEntries *struct {
+				Files       []string `json:"files"`
+				Directories []string `json:"directories"`
+			}
+			if err := json.Unmarshal(result, &rawEntries); err != nil {
+				panic(err)
+			}
+			if rawEntries != nil {
+				return vfs.Entries{
+					Files:       rawEntries.Files,
+					Directories: rawEntries.Directories,
+				}
+			}
+		}
+	}
+	return s.fs.GetAccessibleEntries(path)
+}
+
+// ReadFile implements vfs.FS.
+func (s *Server) ReadFile(path string) (contents string, ok bool) {
+	if s.enabledCallbacks&CallbackReadFile != 0 {
+		data, err := s.call("readFile", path)
+		if err != nil {
+			panic(err)
+		}
+		if string(data) == "null" {
+			return "", false
+		}
+		if len(data) > 0 {
+			var result string
+			if err := json.Unmarshal(data, &result); err != nil {
+				panic(err)
+			}
+			return result, true
+		}
+	}
+	return s.fs.ReadFile(path)
+}
+
+// Realpath implements vfs.FS.
+func (s *Server) Realpath(path string) string {
+	if s.enabledCallbacks&CallbackRealpath != 0 {
+		data, err := s.call("realpath", path)
+		if err != nil {
+			panic(err)
+		}
+		if len(data) > 0 {
+			var result string
+			if err := json.Unmarshal(data, &result); err != nil {
+				panic(err)
+			}
+			return result
+		}
+	}
+	return s.fs.Realpath(path)
+}
+
+// UseCaseSensitiveFileNames implements vfs.FS.
+func (s *Server) UseCaseSensitiveFileNames() bool {
+	return s.fs.UseCaseSensitiveFileNames()
+}
+
+// WriteFile implements vfs.FS.
+func (s *Server) WriteFile(path string, data string, writeByteOrderMark bool) error {
+	return s.fs.WriteFile(path, data, writeByteOrderMark)
+}
+
+// WalkDir implements vfs.FS.
+func (s *Server) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
+	panic("unimplemented")
+}
+
+// Stat implements vfs.FS.
+func (s *Server) Stat(path string) vfs.FileInfo {
+	panic("unimplemented")
+}
+
+// Remove implements vfs.FS.
+func (s *Server) Remove(path string) error {
+	panic("unimplemented")
+}
diff --git a/internal/api/stringer_generated.go b/internal/api/stringer_generated.go
new file mode 100644
index 0000000000..0a740d353b
--- /dev/null
+++ b/internal/api/stringer_generated.go
@@ -0,0 +1,29 @@
+// Code generated by "stringer -type=MessageType -output=stringer_generated.go"; DO NOT EDIT.
+
+package api
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[MessageTypeUnknown-0]
+	_ = x[MessageTypeRequest-1]
+	_ = x[MessageTypeCallResponse-2]
+	_ = x[MessageTypeCallError-3]
+	_ = x[MessageTypeResponse-4]
+	_ = x[MessageTypeError-5]
+	_ = x[MessageTypeCall-6]
+}
+
+const _MessageType_name = "MessageTypeUnknownMessageTypeRequestMessageTypeCallResponseMessageTypeCallErrorMessageTypeResponseMessageTypeErrorMessageTypeCall"
+
+var _MessageType_index = [...]uint8{0, 18, 36, 59, 79, 98, 114, 129}
+
+func (i MessageType) String() string {
+	if i >= MessageType(len(_MessageType_index)-1) {
+		return "MessageType(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]]
+}
diff --git a/internal/ast/ast.go b/internal/ast/ast.go
index 7d77dbff0b..7a4a04f01f 100644
--- a/internal/ast/ast.go
+++ b/internal/ast/ast.go
@@ -72,6 +72,9 @@ type NodeFactory struct {
 	variableDeclarationListPool      core.Pool[VariableDeclarationList]
 	variableDeclarationPool          core.Pool[VariableDeclaration]
 	variableStatementPool            core.Pool[VariableStatement]
+
+	nodeCount int
+	textCount int
 }
 
 type NodeFactoryHooks struct {
@@ -95,6 +98,19 @@ func newNode(kind Kind, data nodeData, hooks NodeFactoryHooks) *Node {
 	return n
 }
 
+func (f *NodeFactory) newNode(kind Kind, data nodeData) *Node {
+	f.nodeCount++
+	return newNode(kind, data, f.hooks)
+}
+
+func (f *NodeFactory) NodeCount() int {
+	return f.nodeCount
+}
+
+func (f *NodeFactory) TextCount() int {
+	return f.textCount
+}
+
 func updateNode(updated *Node, original *Node, hooks NodeFactoryHooks) *Node {
 	if updated != original {
 		updated.Loc = original.Loc
@@ -1843,7 +1859,7 @@ type Token struct {
 }
 
 func (f *NodeFactory) NewToken(kind Kind) *Node {
-	return newNode(kind, f.tokenPool.New(), f.hooks)
+	return f.newNode(kind, f.tokenPool.New())
 }
 
 func (node *Token) Clone(f *NodeFactory) *Node {
@@ -1902,7 +1918,8 @@ type Identifier struct {
 func (f *NodeFactory) NewIdentifier(text string) *Node {
 	data := f.identifierPool.New()
 	data.Text = text
-	return newNode(KindIdentifier, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindIdentifier, data)
 }
 
 func (node *Identifier) Clone(f *NodeFactory) *Node {
@@ -1927,7 +1944,8 @@ type PrivateIdentifier struct {
 func (f *NodeFactory) NewPrivateIdentifier(text string) *Node {
 	data := &PrivateIdentifier{}
 	data.Text = text
-	return newNode(KindPrivateIdentifier, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindPrivateIdentifier, data)
 }
 
 func (node *PrivateIdentifier) Clone(f *NodeFactory) *Node {
@@ -1956,7 +1974,7 @@ func (f *NodeFactory) NewQualifiedName(left *EntityName, right *IdentifierNode)
 	data := &QualifiedName{}
 	data.Left = left
 	data.Right = right
-	return newNode(KindQualifiedName, data, f.hooks)
+	return f.newNode(KindQualifiedName, data)
 }
 
 func (f *NodeFactory) UpdateQualifiedName(node *QualifiedName, left *EntityName, right *IdentifierNode) *Node {
@@ -2006,7 +2024,7 @@ func (f *NodeFactory) NewTypeParameterDeclaration(modifiers *ModifierList, name
 	data.name = name
 	data.Constraint = constraint
 	data.DefaultType = defaultType
-	return newNode(KindTypeParameter, data, f.hooks)
+	return f.newNode(KindTypeParameter, data)
 }
 
 func (f *NodeFactory) UpdateTypeParameterDeclaration(node *TypeParameterDeclaration, modifiers *ModifierList, name *IdentifierNode, constraint *TypeNode, defaultType *TypeNode) *Node {
@@ -2047,7 +2065,7 @@ type ComputedPropertyName struct {
 func (f *NodeFactory) NewComputedPropertyName(expression *Expression) *Node {
 	data := &ComputedPropertyName{}
 	data.Expression = expression
-	return newNode(KindComputedPropertyName, data, f.hooks)
+	return f.newNode(KindComputedPropertyName, data)
 }
 
 func (f *NodeFactory) UpdateComputedPropertyName(node *ComputedPropertyName, expression *Expression) *Node {
@@ -2094,7 +2112,7 @@ type Decorator struct {
 func (f *NodeFactory) NewDecorator(expression *LeftHandSideExpression) *Node {
 	data := &Decorator{}
 	data.Expression = expression
-	return newNode(KindDecorator, data, f.hooks)
+	return f.newNode(KindDecorator, data)
 }
 
 func (f *NodeFactory) UpdateDecorator(node *Decorator, expression *Expression) *Node {
@@ -2140,7 +2158,7 @@ type EmptyStatement struct {
 }
 
 func (f *NodeFactory) NewEmptyStatement() *Node {
-	return newNode(KindEmptyStatement, &EmptyStatement{}, f.hooks)
+	return f.newNode(KindEmptyStatement, &EmptyStatement{})
 }
 
 func (node *EmptyStatement) Clone(f *NodeFactory) *Node {
@@ -2166,7 +2184,7 @@ func (f *NodeFactory) NewIfStatement(expression *Expression, thenStatement *Stat
 	data.Expression = expression
 	data.ThenStatement = thenStatement
 	data.ElseStatement = elseStatement
-	return newNode(KindIfStatement, data, f.hooks)
+	return f.newNode(KindIfStatement, data)
 }
 
 func (f *NodeFactory) UpdateIfStatement(node *IfStatement, expression *Expression, thenStatement *Statement, elseStatement *Statement) *Node {
@@ -2211,7 +2229,7 @@ func (f *NodeFactory) NewDoStatement(statement *Statement, expression *Expressio
 	data := &DoStatement{}
 	data.Statement = statement
 	data.Expression = expression
-	return newNode(KindDoStatement, data, f.hooks)
+	return f.newNode(KindDoStatement, data)
 }
 
 func (f *NodeFactory) UpdateDoStatement(node *DoStatement, statement *Statement, expression *Expression) *Node {
@@ -2251,7 +2269,7 @@ func (f *NodeFactory) NewWhileStatement(expression *Expression, statement *State
 	data := &WhileStatement{}
 	data.Expression = expression
 	data.Statement = statement
-	return newNode(KindWhileStatement, data, f.hooks)
+	return f.newNode(KindWhileStatement, data)
 }
 
 func (f *NodeFactory) UpdateWhileStatement(node *WhileStatement, expression *Expression, statement *Statement) *Node {
@@ -2295,7 +2313,7 @@ func (f *NodeFactory) NewForStatement(initializer *ForInitializer, condition *Ex
 	data.Condition = condition
 	data.Incrementor = incrementor
 	data.Statement = statement
-	return newNode(KindForStatement, data, f.hooks)
+	return f.newNode(KindForStatement, data)
 }
 
 func (f *NodeFactory) UpdateForStatement(node *ForStatement, initializer *ForInitializer, condition *Expression, incrementor *Expression, statement *Statement) *Node {
@@ -2346,7 +2364,7 @@ func (f *NodeFactory) NewForInOrOfStatement(kind Kind, awaitModifier *TokenNode,
 	data.Initializer = initializer
 	data.Expression = expression
 	data.Statement = statement
-	return newNode(kind, data, f.hooks)
+	return f.newNode(kind, data)
 }
 
 func (f *NodeFactory) UpdateForInOrOfStatement(node *ForInOrOfStatement, awaitModifier *TokenNode, initializer *ForInitializer, expression *Expression, statement *Statement) *Node {
@@ -2397,7 +2415,7 @@ type BreakStatement struct {
 func (f *NodeFactory) NewBreakStatement(label *IdentifierNode) *Node {
 	data := &BreakStatement{}
 	data.Label = label
-	return newNode(KindBreakStatement, data, f.hooks)
+	return f.newNode(KindBreakStatement, data)
 }
 
 func (f *NodeFactory) UpdateBreakStatement(node *BreakStatement, label *IdentifierNode) *Node {
@@ -2429,7 +2447,7 @@ type ContinueStatement struct {
 func (f *NodeFactory) NewContinueStatement(label *IdentifierNode) *Node {
 	data := &ContinueStatement{}
 	data.Label = label
-	return newNode(KindContinueStatement, data, f.hooks)
+	return f.newNode(KindContinueStatement, data)
 }
 
 func (f *NodeFactory) UpdateContinueStatement(node *ContinueStatement, label *IdentifierNode) *Node {
@@ -2462,7 +2480,7 @@ type ReturnStatement struct {
 func (f *NodeFactory) NewReturnStatement(expression *Expression) *Node {
 	data := f.returnStatementPool.New()
 	data.Expression = expression
-	return newNode(KindReturnStatement, data, f.hooks)
+	return f.newNode(KindReturnStatement, data)
 }
 
 func (f *NodeFactory) UpdateReturnStatement(node *ReturnStatement, expression *Expression) *Node {
@@ -2505,7 +2523,7 @@ func (f *NodeFactory) NewWithStatement(expression *Expression, statement *Statem
 	data := &WithStatement{}
 	data.Expression = expression
 	data.Statement = statement
-	return newNode(KindWithStatement, data, f.hooks)
+	return f.newNode(KindWithStatement, data)
 }
 
 func (f *NodeFactory) UpdateWithStatement(node *WithStatement, expression *Expression, statement *Statement) *Node {
@@ -2544,7 +2562,7 @@ func (f *NodeFactory) NewSwitchStatement(expression *Expression, caseBlock *Case
 	data := &SwitchStatement{}
 	data.Expression = expression
 	data.CaseBlock = caseBlock
-	return newNode(KindSwitchStatement, data, f.hooks)
+	return f.newNode(KindSwitchStatement, data)
 }
 
 func (f *NodeFactory) UpdateSwitchStatement(node *SwitchStatement, expression *Expression, caseBlock *CaseBlockNode) *Node {
@@ -2583,7 +2601,7 @@ type CaseBlock struct {
 func (f *NodeFactory) NewCaseBlock(clauses *NodeList) *Node {
 	data := &CaseBlock{}
 	data.Clauses = clauses
-	return newNode(KindCaseBlock, data, f.hooks)
+	return f.newNode(KindCaseBlock, data)
 }
 
 func (f *NodeFactory) UpdateCaseBlock(node *CaseBlock, clauses *CaseClausesList) *Node {
@@ -2623,7 +2641,7 @@ func (f *NodeFactory) NewCaseOrDefaultClause(kind Kind, expression *Expression,
 	data := &CaseOrDefaultClause{}
 	data.Expression = expression
 	data.Statements = statements
-	return newNode(kind, data, f.hooks)
+	return f.newNode(kind, data)
 }
 
 func (f *NodeFactory) UpdateCaseOrDefaultClause(node *CaseOrDefaultClause, expression *Expression, statements *StatementList) *Node {
@@ -2668,7 +2686,7 @@ type ThrowStatement struct {
 func (f *NodeFactory) NewThrowStatement(expression *Expression) *Node {
 	data := &ThrowStatement{}
 	data.Expression = expression
-	return newNode(KindThrowStatement, data, f.hooks)
+	return f.newNode(KindThrowStatement, data)
 }
 
 func (f *NodeFactory) UpdateThrowStatement(node *ThrowStatement, expression *Expression) *Node {
@@ -2709,7 +2727,7 @@ func (f *NodeFactory) NewTryStatement(tryBlock *BlockNode, catchClause *CatchCla
 	data.TryBlock = tryBlock
 	data.CatchClause = catchClause
 	data.FinallyBlock = finallyBlock
-	return newNode(KindTryStatement, data, f.hooks)
+	return f.newNode(KindTryStatement, data)
 }
 
 func (f *NodeFactory) UpdateTryStatement(node *TryStatement, tryBlock *BlockNode, catchClause *CatchClauseNode, finallyBlock *BlockNode) *Node {
@@ -2755,7 +2773,7 @@ func (f *NodeFactory) NewCatchClause(variableDeclaration *VariableDeclarationNod
 	data := &CatchClause{}
 	data.VariableDeclaration = variableDeclaration
 	data.Block = block
-	return newNode(KindCatchClause, data, f.hooks)
+	return f.newNode(KindCatchClause, data)
 }
 
 func (f *NodeFactory) UpdateCatchClause(node *CatchClause, variableDeclaration *VariableDeclarationNode, block *BlockNode) *Node {
@@ -2797,7 +2815,7 @@ type DebuggerStatement struct {
 }
 
 func (f *NodeFactory) NewDebuggerStatement() *Node {
-	return newNode(KindDebuggerStatement, &DebuggerStatement{}, f.hooks)
+	return f.newNode(KindDebuggerStatement, &DebuggerStatement{})
 }
 
 func (node *DebuggerStatement) Clone(f *NodeFactory) *Node {
@@ -2816,7 +2834,7 @@ func (f *NodeFactory) NewLabeledStatement(label *IdentifierNode, statement *Stat
 	data := &LabeledStatement{}
 	data.Label = label
 	data.Statement = statement
-	return newNode(KindLabeledStatement, data, f.hooks)
+	return f.newNode(KindLabeledStatement, data)
 }
 
 func (f *NodeFactory) UpdateLabeledStatement(node *LabeledStatement, label *IdentifierNode, statement *Statement) *Node {
@@ -2856,7 +2874,7 @@ type ExpressionStatement struct {
 func (f *NodeFactory) NewExpressionStatement(expression *Expression) *Node {
 	data := f.expressionStatementPool.New()
 	data.Expression = expression
-	return newNode(KindExpressionStatement, data, f.hooks)
+	return f.newNode(KindExpressionStatement, data)
 }
 
 func (f *NodeFactory) UpdateExpressionStatement(node *ExpressionStatement, expression *Expression) *Node {
@@ -2900,7 +2918,7 @@ func (f *NodeFactory) NewBlock(statements *NodeList, multiline bool) *Node {
 	data := f.blockPool.New()
 	data.Statements = statements
 	data.Multiline = multiline
-	return newNode(KindBlock, data, f.hooks)
+	return f.newNode(KindBlock, data)
 }
 
 func (f *NodeFactory) UpdateBlock(node *Block, statements *StatementList) *Node {
@@ -2943,7 +2961,7 @@ func (f *NodeFactory) NewVariableStatement(modifiers *ModifierList, declarationL
 	data := f.variableStatementPool.New()
 	data.modifiers = modifiers
 	data.DeclarationList = declarationList
-	return newNode(KindVariableStatement, data, f.hooks)
+	return f.newNode(KindVariableStatement, data)
 }
 
 func (f *NodeFactory) UpdateVariableStatement(node *VariableStatement, modifiers *ModifierList, declarationList *VariableDeclarationListNode) *Node {
@@ -2997,7 +3015,7 @@ func (f *NodeFactory) NewVariableDeclaration(name *BindingName, exclamationToken
 	data.ExclamationToken = exclamationToken
 	data.Type = typeNode
 	data.Initializer = initializer
-	return newNode(KindVariableDeclaration, data, f.hooks)
+	return f.newNode(KindVariableDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateVariableDeclaration(node *VariableDeclaration, name *BindingName, exclamationToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
@@ -3045,8 +3063,8 @@ type VariableDeclarationList struct {
 func (f *NodeFactory) NewVariableDeclarationList(flags NodeFlags, declarations *NodeList) *Node {
 	data := f.variableDeclarationListPool.New()
 	data.Declarations = declarations
-	node := newNode(KindVariableDeclarationList, data, f.hooks)
-	node.Flags |= flags
+	node := f.newNode(KindVariableDeclarationList, data)
+	node.Flags = flags
 	return node
 }
 
@@ -3093,7 +3111,7 @@ type BindingPattern struct {
 func (f *NodeFactory) NewBindingPattern(kind Kind, elements *NodeList) *Node {
 	data := &BindingPattern{}
 	data.Elements = elements
-	return newNode(kind, data, f.hooks)
+	return f.newNode(kind, data)
 }
 
 func (f *NodeFactory) UpdateBindingPattern(node *BindingPattern, elements *BindingElementList) *Node {
@@ -3164,7 +3182,7 @@ func (f *NodeFactory) NewParameterDeclaration(modifiers *ModifierList, dotDotDot
 	data.QuestionToken = questionToken
 	data.Type = typeNode
 	data.Initializer = initializer
-	return newNode(KindParameter, data, f.hooks)
+	return f.newNode(KindParameter, data)
 }
 
 func (f *NodeFactory) UpdateParameterDeclaration(node *ParameterDeclaration, modifiers *ModifierList, dotDotDotToken *TokenNode, name *BindingName, questionToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
@@ -3231,7 +3249,7 @@ func (f *NodeFactory) NewBindingElement(dotDotDotToken *TokenNode, propertyName
 	data.PropertyName = propertyName
 	data.name = name
 	data.Initializer = initializer
-	return newNode(KindBindingElement, data, f.hooks)
+	return f.newNode(KindBindingElement, data)
 }
 
 func (f *NodeFactory) UpdateBindingElement(node *BindingElement, dotDotDotToken *TokenNode, propertyName *PropertyName, name *BindingName, initializer *Expression) *Node {
@@ -3279,7 +3297,7 @@ type MissingDeclaration struct {
 func (f *NodeFactory) NewMissingDeclaration(modifiers *ModifierList) *Node {
 	data := &MissingDeclaration{}
 	data.modifiers = modifiers
-	return newNode(KindMissingDeclaration, data, f.hooks)
+	return f.newNode(KindMissingDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateMissingDeclaration(node *MissingDeclaration, modifiers *ModifierList) *Node {
@@ -3323,7 +3341,7 @@ func (f *NodeFactory) NewFunctionDeclaration(modifiers *ModifierList, asteriskTo
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindFunctionDeclaration, data, f.hooks)
+	return f.newNode(KindFunctionDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateFunctionDeclaration(node *FunctionDeclaration, modifiers *ModifierList, asteriskToken *TokenNode, name *IdentifierNode, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -3424,7 +3442,7 @@ func (f *NodeFactory) NewClassDeclaration(modifiers *ModifierList, name *Identif
 	data.TypeParameters = typeParameters
 	data.HeritageClauses = heritageClauses
 	data.Members = members
-	return newNode(KindClassDeclaration, data, f.hooks)
+	return f.newNode(KindClassDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateClassDeclaration(node *ClassDeclaration, modifiers *ModifierList, name *IdentifierNode, typeParameters *TypeParameterList, heritageClauses *HeritageClauseList, members *ClassElementList) *Node {
@@ -3472,7 +3490,7 @@ func (f *NodeFactory) NewClassExpression(modifiers *ModifierList, name *Identifi
 	data.TypeParameters = typeParameters
 	data.HeritageClauses = heritageClauses
 	data.Members = members
-	return newNode(KindClassExpression, data, f.hooks)
+	return f.newNode(KindClassExpression, data)
 }
 
 func (f *NodeFactory) UpdateClassExpression(node *ClassExpression, modifiers *ModifierList, name *IdentifierNode, typeParameters *TypeParameterList, heritageClauses *HeritageClauseList, members *ClassElementList) *Node {
@@ -3519,7 +3537,7 @@ func (f *NodeFactory) NewHeritageClause(token Kind, types *NodeList) *Node {
 	data := &HeritageClause{}
 	data.Token = token
 	data.Types = types
-	return newNode(KindHeritageClause, data, f.hooks)
+	return f.newNode(KindHeritageClause, data)
 }
 
 func (f *NodeFactory) UpdateHeritageClause(node *HeritageClause, types *ExpressionWithTypeArgumentsList) *Node {
@@ -3577,7 +3595,7 @@ func (f *NodeFactory) NewInterfaceDeclaration(modifiers *ModifierList, name *Ide
 	data.TypeParameters = typeParameters
 	data.HeritageClauses = heritageClauses
 	data.Members = members
-	return newNode(KindInterfaceDeclaration, data, f.hooks)
+	return f.newNode(KindInterfaceDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateInterfaceDeclaration(node *InterfaceDeclaration, modifiers *ModifierList, name *IdentifierNode, typeParameters *TypeParameterList, heritageClauses *HeritageClauseList, members *TypeElementList) *Node {
@@ -3626,7 +3644,7 @@ func (f *NodeFactory) newTypeOrJSTypeAliasDeclaration(kind Kind, modifiers *Modi
 	data.name = name
 	data.TypeParameters = typeParameters
 	data.Type = typeNode
-	return newNode(kind, data, f.hooks)
+	return f.newNode(kind, data)
 }
 
 func (f *NodeFactory) NewTypeAliasDeclaration(modifiers *ModifierList, name *IdentifierNode, typeParameters *NodeList, typeNode *TypeNode) *Node {
@@ -3693,7 +3711,7 @@ func (f *NodeFactory) NewEnumMember(name *PropertyName, initializer *Expression)
 	data := &EnumMember{}
 	data.name = name
 	data.Initializer = initializer
-	return newNode(KindEnumMember, data, f.hooks)
+	return f.newNode(KindEnumMember, data)
 }
 
 func (f *NodeFactory) UpdateEnumMember(node *EnumMember, name *PropertyName, initializer *Expression) *Node {
@@ -3746,7 +3764,7 @@ func (f *NodeFactory) NewEnumDeclaration(modifiers *ModifierList, name *Identifi
 	data.modifiers = modifiers
 	data.name = name
 	data.Members = members
-	return newNode(KindEnumDeclaration, data, f.hooks)
+	return f.newNode(KindEnumDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateEnumDeclaration(node *EnumDeclaration, modifiers *ModifierList, name *IdentifierNode, members *EnumMemberList) *Node {
@@ -3798,7 +3816,7 @@ type ModuleBlock struct {
 func (f *NodeFactory) NewModuleBlock(statements *NodeList) *Node {
 	data := &ModuleBlock{}
 	data.Statements = statements
-	return newNode(KindModuleBlock, data, f.hooks)
+	return f.newNode(KindModuleBlock, data)
 }
 
 func (f *NodeFactory) UpdateModuleBlock(node *ModuleBlock, statements *StatementList) *Node {
@@ -3848,7 +3866,7 @@ func (f *NodeFactory) NewModuleDeclaration(modifiers *ModifierList, keyword Kind
 	data.Keyword = keyword
 	data.name = name
 	data.Body = body
-	node := newNode(KindModuleDeclaration, data, f.hooks)
+	node := f.newNode(KindModuleDeclaration, data)
 	return node
 }
 
@@ -3936,7 +3954,7 @@ func (f *NodeFactory) NewImportEqualsDeclaration(modifiers *ModifierList, isType
 	data.IsTypeOnly = isTypeOnly
 	data.name = name
 	data.ModuleReference = moduleReference
-	return newNode(KindImportEqualsDeclaration, data, f.hooks)
+	return f.newNode(KindImportEqualsDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateImportEqualsDeclaration(node *ImportEqualsDeclaration, modifiers *ModifierList, isTypeOnly bool, name *IdentifierNode, moduleReference *ModuleReference) *Node {
@@ -3993,7 +4011,7 @@ func (f *NodeFactory) NewImportDeclaration(modifiers *ModifierList, importClause
 	data.ImportClause = importClause
 	data.ModuleSpecifier = moduleSpecifier
 	data.Attributes = attributes
-	return newNode(KindImportDeclaration, data, f.hooks)
+	return f.newNode(KindImportDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateImportDeclaration(node *ImportDeclaration, modifiers *ModifierList, importClause *ImportClauseNode, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node {
@@ -4043,7 +4061,7 @@ func (f *NodeFactory) NewImportSpecifier(isTypeOnly bool, propertyName *ModuleEx
 	data.IsTypeOnly = isTypeOnly
 	data.PropertyName = propertyName
 	data.name = name
-	return newNode(KindImportSpecifier, data, f.hooks)
+	return f.newNode(KindImportSpecifier, data)
 }
 
 func (f *NodeFactory) UpdateImportSpecifier(node *ImportSpecifier, isTypeOnly bool, propertyName *ModuleExportName, name *IdentifierNode) *Node {
@@ -4092,7 +4110,7 @@ type ExternalModuleReference struct {
 func (f *NodeFactory) NewExternalModuleReference(expression *Expression) *Node {
 	data := &ExternalModuleReference{}
 	data.Expression = expression
-	return newNode(KindExternalModuleReference, data, f.hooks)
+	return f.newNode(KindExternalModuleReference, data)
 }
 
 func (f *NodeFactory) UpdateExternalModuleReference(node *ExternalModuleReference, expression *Expression) *Node {
@@ -4139,7 +4157,7 @@ func (f *NodeFactory) NewImportClause(isTypeOnly bool, name *IdentifierNode, nam
 	data.IsTypeOnly = isTypeOnly
 	data.name = name
 	data.NamedBindings = namedBindings
-	return newNode(KindImportClause, data, f.hooks)
+	return f.newNode(KindImportClause, data)
 }
 
 func (f *NodeFactory) UpdateImportClause(node *ImportClause, isTypeOnly bool, name *IdentifierNode, namedBindings *NamedImportBindings) *Node {
@@ -4190,7 +4208,7 @@ type NamespaceImport struct {
 func (f *NodeFactory) NewNamespaceImport(name *IdentifierNode) *Node {
 	data := &NamespaceImport{}
 	data.name = name
-	return newNode(KindNamespaceImport, data, f.hooks)
+	return f.newNode(KindNamespaceImport, data)
 }
 
 func (f *NodeFactory) UpdateNamespaceImport(node *NamespaceImport, name *IdentifierNode) *Node {
@@ -4235,7 +4253,7 @@ type NamedImports struct {
 func (f *NodeFactory) NewNamedImports(elements *ImportSpecifierList) *Node {
 	data := &NamedImports{}
 	data.Elements = elements
-	return newNode(KindNamedImports, data, f.hooks)
+	return f.newNode(KindNamedImports, data)
 }
 
 func (f *NodeFactory) UpdateNamedImports(node *NamedImports, elements *ImportSpecifierList) *Node {
@@ -4283,7 +4301,7 @@ func (f *NodeFactory) NewExportAssignment(modifiers *ModifierList, isExportEqual
 	data.modifiers = modifiers
 	data.IsExportEquals = isExportEquals
 	data.Expression = expression
-	return newNode(KindExportAssignment, data, f.hooks)
+	return f.newNode(KindExportAssignment, data)
 }
 
 func (f *NodeFactory) UpdateExportAssignment(node *ExportAssignment, modifiers *ModifierList, expression *Expression) *Node {
@@ -4327,7 +4345,7 @@ func (f *NodeFactory) NewNamespaceExportDeclaration(modifiers *ModifierList, nam
 	data := &NamespaceExportDeclaration{}
 	data.modifiers = modifiers
 	data.name = name
-	return newNode(KindNamespaceExportDeclaration, data, f.hooks)
+	return f.newNode(KindNamespaceExportDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateNamespaceExportDeclaration(node *NamespaceExportDeclaration, modifiers *ModifierList, name *IdentifierNode) *Node {
@@ -4377,7 +4395,7 @@ func (f *NodeFactory) NewExportDeclaration(modifiers *ModifierList, isTypeOnly b
 	data.ExportClause = exportClause
 	data.ModuleSpecifier = moduleSpecifier
 	data.Attributes = attributes
-	return newNode(KindExportDeclaration, data, f.hooks)
+	return f.newNode(KindExportDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateExportDeclaration(node *ExportDeclaration, modifiers *ModifierList, isTypeOnly bool, exportClause *NamedExportBindings, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node {
@@ -4422,7 +4440,7 @@ type NamespaceExport struct {
 func (f *NodeFactory) NewNamespaceExport(name *ModuleExportName) *Node {
 	data := &NamespaceExport{}
 	data.name = name
-	return newNode(KindNamespaceExport, data, f.hooks)
+	return f.newNode(KindNamespaceExport, data)
 }
 
 func (f *NodeFactory) UpdateNamespaceExport(node *NamespaceExport, name *ModuleExportName) *Node {
@@ -4467,7 +4485,7 @@ type NamedExports struct {
 func (f *NodeFactory) NewNamedExports(elements *NodeList) *Node {
 	data := &NamedExports{}
 	data.Elements = elements
-	return newNode(KindNamedExports, data, f.hooks)
+	return f.newNode(KindNamedExports, data)
 }
 
 func (f *NodeFactory) UpdateNamedExports(node *NamedExports, elements *ExportSpecifierList) *Node {
@@ -4514,7 +4532,7 @@ func (f *NodeFactory) NewExportSpecifier(isTypeOnly bool, propertyName *ModuleEx
 	data.IsTypeOnly = isTypeOnly
 	data.PropertyName = propertyName
 	data.name = name
-	return newNode(KindExportSpecifier, data, f.hooks)
+	return f.newNode(KindExportSpecifier, data)
 }
 
 func (f *NodeFactory) UpdateExportSpecifier(node *ExportSpecifier, isTypeOnly bool, propertyName *ModuleExportName, name *ModuleExportName) *Node {
@@ -4589,7 +4607,7 @@ func (f *NodeFactory) NewCallSignatureDeclaration(typeParameters *NodeList, para
 	data.TypeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindCallSignature, data, f.hooks)
+	return f.newNode(KindCallSignature, data)
 }
 
 func (f *NodeFactory) UpdateCallSignatureDeclaration(node *CallSignatureDeclaration, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -4630,7 +4648,7 @@ func (f *NodeFactory) NewConstructSignatureDeclaration(typeParameters *NodeList,
 	data.TypeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindConstructSignature, data, f.hooks)
+	return f.newNode(KindConstructSignature, data)
 }
 
 func (f *NodeFactory) UpdateConstructSignatureDeclaration(node *ConstructSignatureDeclaration, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -4675,7 +4693,7 @@ func (f *NodeFactory) NewConstructorDeclaration(modifiers *ModifierList, typePar
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindConstructor, data, f.hooks)
+	return f.newNode(KindConstructor, data)
 }
 
 func (f *NodeFactory) UpdateConstructorDeclaration(node *ConstructorDeclaration, modifiers *ModifierList, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -4769,7 +4787,7 @@ func (f *NodeFactory) NewGetAccessorDeclaration(modifiers *ModifierList, name *P
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindGetAccessor, data, f.hooks)
+	return f.newNode(KindGetAccessor, data)
 }
 
 func (f *NodeFactory) UpdateGetAccessorDeclaration(node *GetAccessorDeclaration, modifiers *ModifierList, name *PropertyName, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -4805,7 +4823,7 @@ func (f *NodeFactory) NewSetAccessorDeclaration(modifiers *ModifierList, name *P
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindSetAccessor, data, f.hooks)
+	return f.newNode(KindSetAccessor, data)
 }
 
 func (f *NodeFactory) UpdateSetAccessorDeclaration(node *SetAccessorDeclaration, modifiers *ModifierList, name *PropertyName, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -4844,7 +4862,7 @@ func (f *NodeFactory) NewIndexSignatureDeclaration(modifiers *ModifierList, para
 	data.modifiers = modifiers
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindIndexSignature, data, f.hooks)
+	return f.newNode(KindIndexSignature, data)
 }
 
 func (f *NodeFactory) UpdateIndexSignatureDeclaration(node *IndexSignatureDeclaration, modifiers *ModifierList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -4888,7 +4906,7 @@ func (f *NodeFactory) NewMethodSignatureDeclaration(modifiers *ModifierList, nam
 	data.TypeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindMethodSignature, data, f.hooks)
+	return f.newNode(KindMethodSignature, data)
 }
 
 func (f *NodeFactory) UpdateMethodSignatureDeclaration(node *MethodSignatureDeclaration, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -4937,7 +4955,7 @@ func (f *NodeFactory) NewMethodDeclaration(modifiers *ModifierList, asteriskToke
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindMethodDeclaration, data, f.hooks)
+	return f.newNode(KindMethodDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateMethodDeclaration(node *MethodDeclaration, modifiers *ModifierList, asteriskToken *TokenNode, name *PropertyName, postfixToken *TokenNode, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -5006,7 +5024,7 @@ func (f *NodeFactory) NewPropertySignatureDeclaration(modifiers *ModifierList, n
 	data.PostfixToken = postfixToken
 	data.Type = typeNode
 	data.Initializer = initializer
-	return newNode(KindPropertySignature, data, f.hooks)
+	return f.newNode(KindPropertySignature, data)
 }
 
 func (f *NodeFactory) UpdatePropertySignatureDeclaration(node *PropertySignatureDeclaration, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
@@ -5050,7 +5068,7 @@ func (f *NodeFactory) NewPropertyDeclaration(modifiers *ModifierList, name *Prop
 	data.PostfixToken = postfixToken
 	data.Type = typeNode
 	data.Initializer = initializer
-	return newNode(KindPropertyDeclaration, data, f.hooks)
+	return f.newNode(KindPropertyDeclaration, data)
 }
 
 func (f *NodeFactory) UpdatePropertyDeclaration(node *PropertyDeclaration, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
@@ -5099,7 +5117,7 @@ type SemicolonClassElement struct {
 }
 
 func (f *NodeFactory) NewSemicolonClassElement() *Node {
-	return newNode(KindSemicolonClassElement, &SemicolonClassElement{}, f.hooks)
+	return f.newNode(KindSemicolonClassElement, &SemicolonClassElement{})
 }
 
 func (node *SemicolonClassElement) Clone(f *NodeFactory) *Node {
@@ -5123,7 +5141,7 @@ func (f *NodeFactory) NewClassStaticBlockDeclaration(modifiers *ModifierList, bo
 	data := &ClassStaticBlockDeclaration{}
 	data.modifiers = modifiers
 	data.Body = body
-	return newNode(KindClassStaticBlockDeclaration, data, f.hooks)
+	return f.newNode(KindClassStaticBlockDeclaration, data)
 }
 
 func (f *NodeFactory) UpdateClassStaticBlockDeclaration(node *ClassStaticBlockDeclaration, modifiers *ModifierList, body *BlockNode) *Node {
@@ -5171,7 +5189,7 @@ type OmittedExpression struct {
 }
 
 func (f *NodeFactory) NewOmittedExpression() *Node {
-	return newNode(KindOmittedExpression, &OmittedExpression{}, f.hooks)
+	return f.newNode(KindOmittedExpression, &OmittedExpression{})
 }
 
 func (node *OmittedExpression) Clone(f *NodeFactory) *Node {
@@ -5190,7 +5208,7 @@ type KeywordExpression struct {
 }
 
 func (f *NodeFactory) NewKeywordExpression(kind Kind) *Node {
-	return newNode(kind, &KeywordExpression{}, f.hooks)
+	return f.newNode(kind, &KeywordExpression{})
 }
 
 func (node *KeywordExpression) Clone(f *NodeFactory) *Node {
@@ -5226,7 +5244,8 @@ type StringLiteral struct {
 func (f *NodeFactory) NewStringLiteral(text string) *Node {
 	data := f.stringLiteralPool.New()
 	data.Text = text
-	return newNode(KindStringLiteral, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindStringLiteral, data)
 }
 
 func (node *StringLiteral) Clone(f *NodeFactory) *Node {
@@ -5247,7 +5266,8 @@ type NumericLiteral struct {
 func (f *NodeFactory) NewNumericLiteral(text string) *Node {
 	data := &NumericLiteral{}
 	data.Text = text
-	return newNode(KindNumericLiteral, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindNumericLiteral, data)
 }
 
 func (node *NumericLiteral) Clone(f *NodeFactory) *Node {
@@ -5268,7 +5288,8 @@ type BigIntLiteral struct {
 func (f *NodeFactory) NewBigIntLiteral(text string) *Node {
 	data := &BigIntLiteral{}
 	data.Text = text
-	return newNode(KindBigIntLiteral, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindBigIntLiteral, data)
 }
 
 func (node *BigIntLiteral) Clone(f *NodeFactory) *Node {
@@ -5293,7 +5314,8 @@ type RegularExpressionLiteral struct {
 func (f *NodeFactory) NewRegularExpressionLiteral(text string) *Node {
 	data := &RegularExpressionLiteral{}
 	data.Text = text
-	return newNode(KindRegularExpressionLiteral, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindRegularExpressionLiteral, data)
 }
 
 func (node *RegularExpressionLiteral) Clone(f *NodeFactory) *Node {
@@ -5310,7 +5332,8 @@ type NoSubstitutionTemplateLiteral struct {
 func (f *NodeFactory) NewNoSubstitutionTemplateLiteral(text string) *Node {
 	data := &NoSubstitutionTemplateLiteral{}
 	data.Text = text
-	return newNode(KindNoSubstitutionTemplateLiteral, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindNoSubstitutionTemplateLiteral, data)
 }
 
 func (node *NoSubstitutionTemplateLiteral) Clone(f *NodeFactory) *Node {
@@ -5336,7 +5359,7 @@ func (f *NodeFactory) NewBinaryExpression(left *Expression, operatorToken *Token
 	data.Left = left
 	data.OperatorToken = operatorToken
 	data.Right = right
-	return newNode(KindBinaryExpression, data, f.hooks)
+	return f.newNode(KindBinaryExpression, data)
 }
 
 func (f *NodeFactory) UpdateBinaryExpression(node *BinaryExpression, left *Expression, operatorToken *TokenNode, right *Expression) *Node {
@@ -5381,7 +5404,7 @@ func (f *NodeFactory) NewPrefixUnaryExpression(operator Kind, operand *Expressio
 	data := &PrefixUnaryExpression{}
 	data.Operator = operator
 	data.Operand = operand
-	return newNode(KindPrefixUnaryExpression, data, f.hooks)
+	return f.newNode(KindPrefixUnaryExpression, data)
 }
 
 func (f *NodeFactory) UpdatePrefixUnaryExpression(node *PrefixUnaryExpression, operand *Expression) *Node {
@@ -5423,7 +5446,7 @@ func (f *NodeFactory) NewPostfixUnaryExpression(operand *Expression, operator Ki
 	data := &PostfixUnaryExpression{}
 	data.Operand = operand
 	data.Operator = operator
-	return newNode(KindPostfixUnaryExpression, data, f.hooks)
+	return f.newNode(KindPostfixUnaryExpression, data)
 }
 
 func (f *NodeFactory) UpdatePostfixUnaryExpression(node *PostfixUnaryExpression, operand *Expression) *Node {
@@ -5461,7 +5484,7 @@ func (f *NodeFactory) NewYieldExpression(asteriskToken *TokenNode, expression *E
 	data := &YieldExpression{}
 	data.AsteriskToken = asteriskToken
 	data.Expression = expression
-	return newNode(KindYieldExpression, data, f.hooks)
+	return f.newNode(KindYieldExpression, data)
 }
 
 func (f *NodeFactory) UpdateYieldExpression(node *YieldExpression, asteriskToken *TokenNode, expression *Expression) *Node {
@@ -5507,7 +5530,7 @@ func (f *NodeFactory) NewArrowFunction(modifiers *ModifierList, typeParameters *
 	data.Type = returnType
 	data.EqualsGreaterThanToken = equalsGreaterThanToken
 	data.Body = body
-	return newNode(KindArrowFunction, data, f.hooks)
+	return f.newNode(KindArrowFunction, data)
 }
 
 func (f *NodeFactory) UpdateArrowFunction(node *ArrowFunction, modifiers *ModifierList, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, equalsGreaterThanToken *TokenNode, body *BlockOrExpression) *Node {
@@ -5573,7 +5596,7 @@ func (f *NodeFactory) NewFunctionExpression(modifiers *ModifierList, asteriskTok
 	data.Parameters = parameters
 	data.Type = returnType
 	data.Body = body
-	return newNode(KindFunctionExpression, data, f.hooks)
+	return f.newNode(KindFunctionExpression, data)
 }
 
 func (f *NodeFactory) UpdateFunctionExpression(node *FunctionExpression, modifiers *ModifierList, asteriskToken *TokenNode, name *IdentifierNode, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode, body *BlockNode) *Node {
@@ -5634,7 +5657,7 @@ func (f *NodeFactory) NewAsExpression(expression *Expression, typeNode *TypeNode
 	data := &AsExpression{}
 	data.Expression = expression
 	data.Type = typeNode
-	return newNode(KindAsExpression, data, f.hooks)
+	return f.newNode(KindAsExpression, data)
 }
 
 func (f *NodeFactory) UpdateAsExpression(node *AsExpression, expression *Expression, typeNode *TypeNode) *Node {
@@ -5676,7 +5699,7 @@ func (f *NodeFactory) NewSatisfiesExpression(expression *Expression, typeNode *T
 	data := &SatisfiesExpression{}
 	data.Expression = expression
 	data.Type = typeNode
-	return newNode(KindSatisfiesExpression, data, f.hooks)
+	return f.newNode(KindSatisfiesExpression, data)
 }
 
 func (f *NodeFactory) UpdateSatisfiesExpression(node *SatisfiesExpression, expression *Expression, typeNode *TypeNode) *Node {
@@ -5729,7 +5752,7 @@ func (f *NodeFactory) NewConditionalExpression(condition *Expression, questionTo
 	data.WhenTrue = whenTrue
 	data.ColonToken = colonToken
 	data.WhenFalse = whenFalse
-	return newNode(KindConditionalExpression, data, f.hooks)
+	return f.newNode(KindConditionalExpression, data)
 }
 
 func (f *NodeFactory) UpdateConditionalExpression(node *ConditionalExpression, condition *Expression, questionToken *TokenNode, whenTrue *Expression, colonToken *TokenNode, whenFalse *Expression) *Node {
@@ -5774,7 +5797,7 @@ func (f *NodeFactory) NewPropertyAccessExpression(expression *Expression, questi
 	data.Expression = expression
 	data.QuestionDotToken = questionDotToken
 	data.name = name
-	node := newNode(KindPropertyAccessExpression, data, f.hooks)
+	node := f.newNode(KindPropertyAccessExpression, data)
 	node.Flags |= flags & NodeFlagsOptionalChain
 	return node
 }
@@ -5830,7 +5853,7 @@ func (f *NodeFactory) NewElementAccessExpression(expression *Expression, questio
 	data.Expression = expression
 	data.QuestionDotToken = questionDotToken
 	data.ArgumentExpression = argumentExpression
-	node := newNode(KindElementAccessExpression, data, f.hooks)
+	node := f.newNode(KindElementAccessExpression, data)
 	node.Flags |= flags & NodeFlagsOptionalChain
 	return node
 }
@@ -5885,7 +5908,7 @@ func (f *NodeFactory) NewCallExpression(expression *Expression, questionDotToken
 	data.QuestionDotToken = questionDotToken
 	data.TypeArguments = typeArguments
 	data.Arguments = arguments
-	node := newNode(KindCallExpression, data, f.hooks)
+	node := f.newNode(KindCallExpression, data)
 	node.Flags |= flags & NodeFlagsOptionalChain
 	return node
 }
@@ -5940,7 +5963,7 @@ func (f *NodeFactory) NewNewExpression(expression *Expression, typeArguments *No
 	data.Expression = expression
 	data.TypeArguments = typeArguments
 	data.Arguments = arguments
-	return newNode(KindNewExpression, data, f.hooks)
+	return f.newNode(KindNewExpression, data)
 }
 
 func (f *NodeFactory) UpdateNewExpression(node *NewExpression, expression *Expression, typeArguments *TypeArgumentList, arguments *ArgumentList) *Node {
@@ -5990,7 +6013,7 @@ func (f *NodeFactory) NewMetaProperty(keywordToken Kind, name *IdentifierNode) *
 	data := &MetaProperty{}
 	data.KeywordToken = keywordToken
 	data.name = name
-	return newNode(KindMetaProperty, data, f.hooks)
+	return f.newNode(KindMetaProperty, data)
 }
 
 func (f *NodeFactory) UpdateMetaProperty(node *MetaProperty, name *IdentifierNode) *Node {
@@ -6036,7 +6059,7 @@ func (f *NodeFactory) NewNonNullExpression(expression *Expression, flags NodeFla
 	data := &NonNullExpression{}
 	data.Expression = expression
 	data.Flags |= flags & NodeFlagsOptionalChain
-	return newNode(KindNonNullExpression, data, f.hooks)
+	return f.newNode(KindNonNullExpression, data)
 }
 
 func (f *NodeFactory) UpdateNonNullExpression(node *NonNullExpression, expression *Expression) *Node {
@@ -6076,7 +6099,7 @@ type SpreadElement struct {
 func (f *NodeFactory) NewSpreadElement(expression *Expression) *Node {
 	data := &SpreadElement{}
 	data.Expression = expression
-	return newNode(KindSpreadElement, data, f.hooks)
+	return f.newNode(KindSpreadElement, data)
 }
 
 func (f *NodeFactory) UpdateSpreadElement(node *SpreadElement, expression *Expression) *Node {
@@ -6119,7 +6142,7 @@ func (f *NodeFactory) NewTemplateExpression(head *TemplateHeadNode, templateSpan
 	data := &TemplateExpression{}
 	data.Head = head
 	data.TemplateSpans = templateSpans
-	return newNode(KindTemplateExpression, data, f.hooks)
+	return f.newNode(KindTemplateExpression, data)
 }
 
 func (f *NodeFactory) UpdateTemplateExpression(node *TemplateExpression, head *TemplateHeadNode, templateSpans *TemplateSpanList) *Node {
@@ -6162,7 +6185,7 @@ func (f *NodeFactory) NewTemplateSpan(expression *Expression, literal *TemplateM
 	data := &TemplateSpan{}
 	data.Expression = expression
 	data.Literal = literal
-	return newNode(KindTemplateSpan, data, f.hooks)
+	return f.newNode(KindTemplateSpan, data)
 }
 
 func (f *NodeFactory) UpdateTemplateSpan(node *TemplateSpan, expression *Expression, literal *TemplateMiddleOrTail) *Node {
@@ -6209,7 +6232,7 @@ func (f *NodeFactory) NewTaggedTemplateExpression(tag *Expression, questionDotTo
 	data.QuestionDotToken = questionDotToken
 	data.TypeArguments = typeArguments
 	data.Template = template
-	node := newNode(KindTaggedTemplateExpression, data, f.hooks)
+	node := f.newNode(KindTaggedTemplateExpression, data)
 	node.Flags |= flags & NodeFlagsOptionalChain
 	return node
 }
@@ -6254,7 +6277,7 @@ type ParenthesizedExpression struct {
 func (f *NodeFactory) NewParenthesizedExpression(expression *Expression) *Node {
 	data := f.parenthesizedExpressionPool.New()
 	data.Expression = expression
-	return newNode(KindParenthesizedExpression, data, f.hooks)
+	return f.newNode(KindParenthesizedExpression, data)
 }
 
 func (f *NodeFactory) UpdateParenthesizedExpression(node *ParenthesizedExpression, expression *Expression) *Node {
@@ -6301,7 +6324,7 @@ func (f *NodeFactory) NewArrayLiteralExpression(elements *NodeList, multiLine bo
 	data := &ArrayLiteralExpression{}
 	data.Elements = elements
 	data.MultiLine = multiLine
-	return newNode(KindArrayLiteralExpression, data, f.hooks)
+	return f.newNode(KindArrayLiteralExpression, data)
 }
 
 func (f *NodeFactory) UpdateArrayLiteralExpression(node *ArrayLiteralExpression, elements *ElementList) *Node {
@@ -6349,7 +6372,7 @@ func (f *NodeFactory) NewObjectLiteralExpression(properties *NodeList, multiLine
 	data := &ObjectLiteralExpression{}
 	data.Properties = properties
 	data.MultiLine = multiLine
-	return newNode(KindObjectLiteralExpression, data, f.hooks)
+	return f.newNode(KindObjectLiteralExpression, data)
 }
 
 func (f *NodeFactory) UpdateObjectLiteralExpression(node *ObjectLiteralExpression, properties *PropertyDefinitionList) *Node {
@@ -6399,7 +6422,7 @@ type SpreadAssignment struct {
 func (f *NodeFactory) NewSpreadAssignment(expression *Expression) *Node {
 	data := &SpreadAssignment{}
 	data.Expression = expression
-	return newNode(KindSpreadAssignment, data, f.hooks)
+	return f.newNode(KindSpreadAssignment, data)
 }
 
 func (f *NodeFactory) UpdateSpreadAssignment(node *SpreadAssignment, expression *Expression) *Node {
@@ -6445,7 +6468,7 @@ func (f *NodeFactory) NewPropertyAssignment(modifiers *ModifierList, name *Prope
 	data.name = name
 	data.PostfixToken = postfixToken
 	data.Initializer = initializer
-	return newNode(KindPropertyAssignment, data, f.hooks)
+	return f.newNode(KindPropertyAssignment, data)
 }
 
 func (f *NodeFactory) UpdatePropertyAssignment(node *PropertyAssignment, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, initializer *Expression) *Node {
@@ -6494,7 +6517,7 @@ func (f *NodeFactory) NewShorthandPropertyAssignment(modifiers *ModifierList, na
 	data.PostfixToken = postfixToken
 	data.EqualsToken = equalsToken
 	data.ObjectAssignmentInitializer = objectAssignmentInitializer
-	return newNode(KindShorthandPropertyAssignment, data, f.hooks)
+	return f.newNode(KindShorthandPropertyAssignment, data)
 }
 
 func (f *NodeFactory) UpdateShorthandPropertyAssignment(node *ShorthandPropertyAssignment, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, equalsToken *TokenNode, objectAssignmentInitializer *Expression) *Node {
@@ -6536,7 +6559,7 @@ type DeleteExpression struct {
 func (f *NodeFactory) NewDeleteExpression(expression *Expression) *Node {
 	data := &DeleteExpression{}
 	data.Expression = expression
-	return newNode(KindDeleteExpression, data, f.hooks)
+	return f.newNode(KindDeleteExpression, data)
 }
 
 func (f *NodeFactory) UpdateDeleteExpression(node *DeleteExpression, expression *Expression) *Node {
@@ -6572,7 +6595,7 @@ type TypeOfExpression struct {
 func (f *NodeFactory) NewTypeOfExpression(expression *Expression) *Node {
 	data := &TypeOfExpression{}
 	data.Expression = expression
-	return newNode(KindTypeOfExpression, data, f.hooks)
+	return f.newNode(KindTypeOfExpression, data)
 }
 
 func (f *NodeFactory) UpdateTypeOfExpression(node *TypeOfExpression, expression *Expression) *Node {
@@ -6612,7 +6635,7 @@ type VoidExpression struct {
 func (f *NodeFactory) NewVoidExpression(expression *Expression) *Node {
 	data := &VoidExpression{}
 	data.Expression = expression
-	return newNode(KindVoidExpression, data, f.hooks)
+	return f.newNode(KindVoidExpression, data)
 }
 
 func (f *NodeFactory) UpdateVoidExpression(node *VoidExpression, expression *Expression) *Node {
@@ -6648,7 +6671,7 @@ type AwaitExpression struct {
 func (f *NodeFactory) NewAwaitExpression(expression *Expression) *Node {
 	data := &AwaitExpression{}
 	data.Expression = expression
-	return newNode(KindAwaitExpression, data, f.hooks)
+	return f.newNode(KindAwaitExpression, data)
 }
 
 func (f *NodeFactory) UpdateAwaitExpression(node *AwaitExpression, expression *Expression) *Node {
@@ -6690,7 +6713,7 @@ func (f *NodeFactory) NewTypeAssertion(typeNode *TypeNode, expression *Expressio
 	data := &TypeAssertion{}
 	data.Type = typeNode
 	data.Expression = expression
-	return newNode(KindTypeAssertionExpression, data, f.hooks)
+	return f.newNode(KindTypeAssertionExpression, data)
 }
 
 func (f *NodeFactory) UpdateTypeAssertion(node *TypeAssertion, typeNode *TypeNode, expression *Expression) *Node {
@@ -6734,7 +6757,7 @@ type KeywordTypeNode struct {
 }
 
 func (f *NodeFactory) NewKeywordTypeNode(kind Kind) *Node {
-	return newNode(kind, f.keywordTypeNodePool.New(), f.hooks)
+	return f.newNode(kind, f.keywordTypeNodePool.New())
 }
 
 func (node *KeywordTypeNode) Clone(f *NodeFactory) *Node {
@@ -6768,7 +6791,7 @@ func (f *NodeFactory) UpdateUnionTypeNode(node *UnionTypeNode, types *TypeList)
 func (f *NodeFactory) NewUnionTypeNode(types *NodeList) *Node {
 	data := &UnionTypeNode{}
 	data.Types = types
-	return newNode(KindUnionType, data, f.hooks)
+	return f.newNode(KindUnionType, data)
 }
 
 func (node *UnionTypeNode) VisitEachChild(v *NodeVisitor) *Node {
@@ -6795,7 +6818,7 @@ func (f *NodeFactory) UpdateIntersectionTypeNode(node *IntersectionTypeNode, typ
 func (f *NodeFactory) NewIntersectionTypeNode(types *NodeList) *Node {
 	data := &IntersectionTypeNode{}
 	data.Types = types
-	return newNode(KindIntersectionType, data, f.hooks)
+	return f.newNode(KindIntersectionType, data)
 }
 
 func (node *IntersectionTypeNode) VisitEachChild(v *NodeVisitor) *Node {
@@ -6823,7 +6846,7 @@ func (f *NodeFactory) NewConditionalTypeNode(checkType *TypeNode, extendsType *T
 	data.ExtendsType = extendsType
 	data.TrueType = trueType
 	data.FalseType = falseType
-	return newNode(KindConditionalType, data, f.hooks)
+	return f.newNode(KindConditionalType, data)
 }
 
 func (f *NodeFactory) UpdateConditionalTypeNode(node *ConditionalTypeNode, checkType *TypeNode, extendsType *TypeNode, trueType *TypeNode, falseType *TypeNode) *Node {
@@ -6861,7 +6884,7 @@ func (f *NodeFactory) NewTypeOperatorNode(operator Kind, typeNode *TypeNode) *No
 	data := &TypeOperatorNode{}
 	data.Operator = operator
 	data.Type = typeNode
-	return newNode(KindTypeOperator, data, f.hooks)
+	return f.newNode(KindTypeOperator, data)
 }
 
 func (f *NodeFactory) UpdateTypeOperatorNode(node *TypeOperatorNode, typeNode *TypeNode) *Node {
@@ -6897,7 +6920,7 @@ type InferTypeNode struct {
 func (f *NodeFactory) NewInferTypeNode(typeParameter *TypeParameterDeclarationNode) *Node {
 	data := &InferTypeNode{}
 	data.TypeParameter = typeParameter
-	return newNode(KindInferType, data, f.hooks)
+	return f.newNode(KindInferType, data)
 }
 
 func (f *NodeFactory) UpdateInferTypeNode(node *InferTypeNode, typeParameter *TypeNode) *Node {
@@ -6933,7 +6956,7 @@ type ArrayTypeNode struct {
 func (f *NodeFactory) NewArrayTypeNode(elementType *TypeNode) *Node {
 	data := &ArrayTypeNode{}
 	data.ElementType = elementType
-	return newNode(KindArrayType, data, f.hooks)
+	return f.newNode(KindArrayType, data)
 }
 
 func (f *NodeFactory) UpdateArrayTypeNode(node *ArrayTypeNode, elementType *TypeNode) *Node {
@@ -6967,7 +6990,7 @@ func (f *NodeFactory) NewIndexedAccessTypeNode(objectType *TypeNode, indexType *
 	data := &IndexedAccessTypeNode{}
 	data.ObjectType = objectType
 	data.IndexType = indexType
-	return newNode(KindIndexedAccessType, data, f.hooks)
+	return f.newNode(KindIndexedAccessType, data)
 }
 
 func (f *NodeFactory) UpdateIndexedAccessTypeNode(node *IndexedAccessTypeNode, objectType *TypeNode, indexType *TypeNode) *Node {
@@ -7005,7 +7028,7 @@ func (f *NodeFactory) NewTypeReferenceNode(typeName *EntityName, typeArguments *
 	data := f.typeReferenceNodePool.New()
 	data.TypeName = typeName
 	data.TypeArguments = typeArguments
-	return newNode(KindTypeReference, data, f.hooks)
+	return f.newNode(KindTypeReference, data)
 }
 
 func (f *NodeFactory) UpdateTypeReferenceNode(node *TypeReferenceNode, typeName *EntityName, typeArguments *TypeArgumentList) *Node {
@@ -7044,7 +7067,7 @@ func (f *NodeFactory) NewExpressionWithTypeArguments(expression *Expression, typ
 	data := &ExpressionWithTypeArguments{}
 	data.Expression = expression
 	data.TypeArguments = typeArguments
-	return newNode(KindExpressionWithTypeArguments, data, f.hooks)
+	return f.newNode(KindExpressionWithTypeArguments, data)
 }
 
 func (f *NodeFactory) UpdateExpressionWithTypeArguments(node *ExpressionWithTypeArguments, expression *Expression, typeArguments *TypeArgumentList) *Node {
@@ -7085,7 +7108,7 @@ type LiteralTypeNode struct {
 func (f *NodeFactory) NewLiteralTypeNode(literal *Node) *Node {
 	data := f.literalTypeNodePool.New()
 	data.Literal = literal
-	return newNode(KindLiteralType, data, f.hooks)
+	return f.newNode(KindLiteralType, data)
 }
 
 func (f *NodeFactory) UpdateLiteralTypeNode(node *LiteralTypeNode, literal *Node) *Node {
@@ -7118,7 +7141,7 @@ type ThisTypeNode struct {
 }
 
 func (f *NodeFactory) NewThisTypeNode() *Node {
-	return newNode(KindThisType, &ThisTypeNode{}, f.hooks)
+	return f.newNode(KindThisType, &ThisTypeNode{})
 }
 
 func (node *ThisTypeNode) Clone(f *NodeFactory) *Node {
@@ -7143,7 +7166,7 @@ func (f *NodeFactory) NewTypePredicateNode(assertsModifier *TokenNode, parameter
 	data.AssertsModifier = assertsModifier
 	data.ParameterName = parameterName
 	data.Type = typeNode
-	return newNode(KindTypePredicate, data, f.hooks)
+	return f.newNode(KindTypePredicate, data)
 }
 
 func (f *NodeFactory) UpdateTypePredicateNode(node *TypePredicateNode, assertsModifier *TokenNode, parameterName *TypePredicateParameterName, typeNode *TypeNode) *Node {
@@ -7187,7 +7210,7 @@ func (f *NodeFactory) NewImportTypeNode(isTypeOf bool, argument *TypeNode, attri
 	data.Attributes = attributes
 	data.Qualifier = qualifier
 	data.TypeArguments = typeArguments
-	return newNode(KindImportType, data, f.hooks)
+	return f.newNode(KindImportType, data)
 }
 
 func (f *NodeFactory) UpdateImportTypeNode(node *ImportTypeNode, isTypeOf bool, argument *TypeNode, attributes *ImportAttributesNode, qualifier *EntityName, typeArguments *TypeArgumentList) *Node {
@@ -7226,7 +7249,7 @@ func (f *NodeFactory) NewImportAttribute(name *ImportAttributeName, value *Expre
 	data := &ImportAttribute{}
 	data.name = name
 	data.Value = value
-	return newNode(KindImportAttribute, data, f.hooks)
+	return f.newNode(KindImportAttribute, data)
 }
 
 func (f *NodeFactory) UpdateImportAttribute(node *ImportAttribute, name *ImportAttributeName, value *Expression) *Node {
@@ -7272,7 +7295,7 @@ func (f *NodeFactory) NewImportAttributes(token Kind, attributes *NodeList, mult
 	data.Token = token
 	data.Attributes = attributes
 	data.MultiLine = multiLine
-	return newNode(KindImportAttributes, data, f.hooks)
+	return f.newNode(KindImportAttributes, data)
 }
 
 func (f *NodeFactory) UpdateImportAttributes(node *ImportAttributes, attributes *ImportAttributeList) *Node {
@@ -7314,7 +7337,7 @@ func (f *NodeFactory) NewTypeQueryNode(exprName *EntityName, typeArguments *Node
 	data := &TypeQueryNode{}
 	data.ExprName = exprName
 	data.TypeArguments = typeArguments
-	return newNode(KindTypeQuery, data, f.hooks)
+	return f.newNode(KindTypeQuery, data)
 }
 
 func (f *NodeFactory) UpdateTypeQueryNode(node *TypeQueryNode, exprName *EntityName, typeArguments *TypeArgumentList) *Node {
@@ -7362,7 +7385,7 @@ func (f *NodeFactory) NewMappedTypeNode(readonlyToken *TokenNode, typeParameter
 	data.QuestionToken = questionToken
 	data.Type = typeNode
 	data.Members = members
-	return newNode(KindMappedType, data, f.hooks)
+	return f.newNode(KindMappedType, data)
 }
 
 func (f *NodeFactory) UpdateMappedTypeNode(node *MappedTypeNode, readonlyToken *TokenNode, typeParameter *TypeParameterDeclarationNode, nameType *TypeNode, questionToken *TokenNode, typeNode *TypeNode, members *TypeElementList) *Node {
@@ -7400,7 +7423,7 @@ type TypeLiteralNode struct {
 func (f *NodeFactory) NewTypeLiteralNode(members *NodeList) *Node {
 	data := &TypeLiteralNode{}
 	data.Members = members
-	return newNode(KindTypeLiteral, data, f.hooks)
+	return f.newNode(KindTypeLiteral, data)
 }
 
 func (f *NodeFactory) UpdateTypeLiteralNode(node *TypeLiteralNode, members *TypeElementList) *Node {
@@ -7436,7 +7459,7 @@ type TupleTypeNode struct {
 func (f *NodeFactory) NewTupleTypeNode(elements *NodeList) *Node {
 	data := &TupleTypeNode{}
 	data.Elements = elements
-	return newNode(KindTupleType, data, f.hooks)
+	return f.newNode(KindTupleType, data)
 }
 
 func (f *NodeFactory) UpdateTupleTypeNode(node *TupleTypeNode, elements *TypeList) *Node {
@@ -7479,7 +7502,7 @@ func (f *NodeFactory) NewNamedTupleMember(dotDotDotToken *TokenNode, name *Ident
 	data.name = name
 	data.QuestionToken = questionToken
 	data.Type = typeNode
-	return newNode(KindNamedTupleMember, data, f.hooks)
+	return f.newNode(KindNamedTupleMember, data)
 }
 
 func (f *NodeFactory) UpdateNamedTupleMember(node *NamedTupleMember, dotDotDotToken *TokenNode, name *IdentifierNode, questionToken *TokenNode, typeNode *TypeNode) *Node {
@@ -7519,7 +7542,7 @@ type OptionalTypeNode struct {
 func (f *NodeFactory) NewOptionalTypeNode(typeNode *TypeNode) *Node {
 	data := &OptionalTypeNode{}
 	data.Type = typeNode
-	return newNode(KindOptionalType, data, f.hooks)
+	return f.newNode(KindOptionalType, data)
 }
 
 func (f *NodeFactory) UpdateOptionalTypeNode(node *OptionalTypeNode, typeNode *TypeNode) *Node {
@@ -7555,7 +7578,7 @@ type RestTypeNode struct {
 func (f *NodeFactory) NewRestTypeNode(typeNode *TypeNode) *Node {
 	data := &RestTypeNode{}
 	data.Type = typeNode
-	return newNode(KindRestType, data, f.hooks)
+	return f.newNode(KindRestType, data)
 }
 
 func (f *NodeFactory) UpdateRestTypeNode(node *RestTypeNode, typeNode *TypeNode) *Node {
@@ -7591,7 +7614,7 @@ type ParenthesizedTypeNode struct {
 func (f *NodeFactory) NewParenthesizedTypeNode(typeNode *TypeNode) *Node {
 	data := &ParenthesizedTypeNode{}
 	data.Type = typeNode
-	return newNode(KindParenthesizedType, data, f.hooks)
+	return f.newNode(KindParenthesizedType, data)
 }
 
 func (f *NodeFactory) UpdateParenthesizedTypeNode(node *ParenthesizedTypeNode, typeNode *TypeNode) *Node {
@@ -7641,7 +7664,7 @@ func (f *NodeFactory) NewFunctionTypeNode(typeParameters *NodeList, parameters *
 	data.TypeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindFunctionType, data, f.hooks)
+	return f.newNode(KindFunctionType, data)
 }
 
 func (f *NodeFactory) UpdateFunctionTypeNode(node *FunctionTypeNode, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -7675,7 +7698,7 @@ func (f *NodeFactory) NewConstructorTypeNode(modifiers *ModifierList, typeParame
 	data.TypeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = returnType
-	return newNode(KindConstructorType, data, f.hooks)
+	return f.newNode(KindConstructorType, data)
 }
 
 func (f *NodeFactory) UpdateConstructorTypeNode(node *ConstructorTypeNode, modifiers *ModifierList, typeParameters *TypeParameterList, parameters *ParameterList, returnType *TypeNode) *Node {
@@ -7720,7 +7743,8 @@ func (f *NodeFactory) NewTemplateHead(text string, rawText string, templateFlags
 	data.Text = text
 	data.RawText = rawText
 	data.TemplateFlags = templateFlags
-	return newNode(KindTemplateHead, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindTemplateHead, data)
 }
 
 func (node *TemplateHead) Clone(f *NodeFactory) *Node {
@@ -7739,7 +7763,8 @@ func (f *NodeFactory) NewTemplateMiddle(text string, rawText string, templateFla
 	data.Text = text
 	data.RawText = rawText
 	data.TemplateFlags = templateFlags
-	return newNode(KindTemplateMiddle, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindTemplateMiddle, data)
 }
 
 func (node *TemplateMiddle) Clone(f *NodeFactory) *Node {
@@ -7758,7 +7783,8 @@ func (f *NodeFactory) NewTemplateTail(text string, rawText string, templateFlags
 	data.Text = text
 	data.RawText = rawText
 	data.TemplateFlags = templateFlags
-	return newNode(KindTemplateTail, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindTemplateTail, data)
 }
 
 func (node *TemplateTail) Clone(f *NodeFactory) *Node {
@@ -7777,7 +7803,7 @@ func (f *NodeFactory) NewTemplateLiteralTypeNode(head *TemplateHeadNode, templat
 	data := &TemplateLiteralTypeNode{}
 	data.Head = head
 	data.TemplateSpans = templateSpans
-	return newNode(KindTemplateLiteralType, data, f.hooks)
+	return f.newNode(KindTemplateLiteralType, data)
 }
 
 func (f *NodeFactory) UpdateTemplateLiteralTypeNode(node *TemplateLiteralTypeNode, head *TemplateHeadNode, templateSpans *TemplateLiteralTypeSpanList) *Node {
@@ -7812,7 +7838,7 @@ func (f *NodeFactory) NewTemplateLiteralTypeSpan(typeNode *TypeNode, literal *Te
 	data := &TemplateLiteralTypeSpan{}
 	data.Type = typeNode
 	data.Literal = literal
-	return newNode(KindTemplateLiteralTypeSpan, data, f.hooks)
+	return f.newNode(KindTemplateLiteralTypeSpan, data)
 }
 
 func (f *NodeFactory) UpdateTemplateLiteralTypeSpan(node *TemplateLiteralTypeSpan, typeNode *TypeNode, literal *TemplateMiddleOrTail) *Node {
@@ -7852,7 +7878,7 @@ func (f *NodeFactory) NewSyntheticExpression(t any, isSpread bool, tupleNameSour
 	data.Type = t
 	data.IsSpread = isSpread
 	data.TupleNameSource = tupleNameSource
-	return newNode(KindSyntheticExpression, data, f.hooks)
+	return f.newNode(KindSyntheticExpression, data)
 }
 
 func (node *SyntheticExpression) Clone(f *NodeFactory) *Node {
@@ -7922,7 +7948,7 @@ func (f *NodeFactory) NewJsxElement(openingElement *JsxOpeningElementNode, child
 	data.OpeningElement = openingElement
 	data.Children = children
 	data.ClosingElement = closingElement
-	return newNode(KindJsxElement, data, f.hooks)
+	return f.newNode(KindJsxElement, data)
 }
 
 func (f *NodeFactory) UpdateJsxElement(node *JsxElement, openingElement *JsxOpeningElementNode, children *JsxChildList, closingElement *JsxClosingElementNode) *Node {
@@ -7966,7 +7992,7 @@ type JsxAttributes struct {
 func (f *NodeFactory) NewJsxAttributes(properties *NodeList) *Node {
 	data := &JsxAttributes{}
 	data.Properties = properties
-	return newNode(KindJsxAttributes, data, f.hooks)
+	return f.newNode(KindJsxAttributes, data)
 }
 
 func (f *NodeFactory) UpdateJsxAttributes(node *JsxAttributes, properties *JsxAttributeList) *Node {
@@ -8010,7 +8036,7 @@ func (f *NodeFactory) NewJsxNamespacedName(namespace *IdentifierNode, name *Iden
 	data := &JsxNamespacedName{}
 	data.Namespace = namespace
 	data.name = name
-	return newNode(KindJsxNamespacedName, data, f.hooks)
+	return f.newNode(KindJsxNamespacedName, data)
 }
 
 func (f *NodeFactory) UpdateJsxNamespacedName(node *JsxNamespacedName, name *IdentifierNode, namespace *IdentifierNode) *Node {
@@ -8061,7 +8087,7 @@ func (f *NodeFactory) NewJsxOpeningElement(tagName *JsxTagNameExpression, typeAr
 	data.TagName = tagName
 	data.TypeArguments = typeArguments
 	data.Attributes = attributes
-	return newNode(KindJsxOpeningElement, data, f.hooks)
+	return f.newNode(KindJsxOpeningElement, data)
 }
 
 func (f *NodeFactory) UpdateJsxOpeningElement(node *JsxOpeningElement, tagName *JsxTagNameExpression, typeArguments *TypeArgumentList, attributes *JsxAttributesNode) *Node {
@@ -8109,7 +8135,7 @@ func (f *NodeFactory) NewJsxSelfClosingElement(tagName *JsxTagNameExpression, ty
 	data.TagName = tagName
 	data.TypeArguments = typeArguments
 	data.Attributes = attributes
-	return newNode(KindJsxSelfClosingElement, data, f.hooks)
+	return f.newNode(KindJsxSelfClosingElement, data)
 }
 
 func (f *NodeFactory) UpdateJsxSelfClosingElement(node *JsxSelfClosingElement, tagName *JsxTagNameExpression, typeArguments *TypeArgumentList, attributes *JsxAttributesNode) *Node {
@@ -8157,7 +8183,7 @@ func (f *NodeFactory) NewJsxFragment(openingFragment *JsxOpeningFragmentNode, ch
 	data.OpeningFragment = openingFragment
 	data.Children = children
 	data.ClosingFragment = closingFragment
-	return newNode(KindJsxFragment, data, f.hooks)
+	return f.newNode(KindJsxFragment, data)
 }
 
 func (f *NodeFactory) UpdateJsxFragment(node *JsxFragment, openingFragment *JsxOpeningFragmentNode, children *JsxChildList, closingFragment *JsxClosingFragmentNode) *Node {
@@ -8191,7 +8217,7 @@ type JsxOpeningFragment struct {
 }
 
 func (f *NodeFactory) NewJsxOpeningFragment() *Node {
-	return newNode(KindJsxOpeningFragment, &JsxOpeningFragment{}, f.hooks)
+	return f.newNode(KindJsxOpeningFragment, &JsxOpeningFragment{})
 }
 
 func (node *JsxOpeningFragment) Clone(f *NodeFactory) *Node {
@@ -8213,7 +8239,7 @@ type JsxClosingFragment struct {
 }
 
 func (f *NodeFactory) NewJsxClosingFragment() *Node {
-	return newNode(KindJsxClosingFragment, &JsxClosingFragment{}, f.hooks)
+	return f.newNode(KindJsxClosingFragment, &JsxClosingFragment{})
 }
 
 func (node *JsxClosingFragment) Clone(f *NodeFactory) *Node {
@@ -8238,7 +8264,7 @@ func (f *NodeFactory) NewJsxAttribute(name *JsxAttributeName, initializer *JsxAt
 	data := &JsxAttribute{}
 	data.name = name
 	data.Initializer = initializer
-	return newNode(KindJsxAttribute, data, f.hooks)
+	return f.newNode(KindJsxAttribute, data)
 }
 
 func (f *NodeFactory) UpdateJsxAttribute(node *JsxAttribute, name *JsxAttributeName, initializer *JsxAttributeValue) *Node {
@@ -8284,7 +8310,7 @@ type JsxSpreadAttribute struct {
 func (f *NodeFactory) NewJsxSpreadAttribute(expression *Expression) *Node {
 	data := &JsxSpreadAttribute{}
 	data.Expression = expression
-	return newNode(KindJsxSpreadAttribute, data, f.hooks)
+	return f.newNode(KindJsxSpreadAttribute, data)
 }
 
 func (f *NodeFactory) UpdateJsxSpreadAttribute(node *JsxSpreadAttribute, expression *Expression) *Node {
@@ -8324,7 +8350,7 @@ type JsxClosingElement struct {
 func (f *NodeFactory) NewJsxClosingElement(tagName *JsxTagNameExpression) *Node {
 	data := &JsxClosingElement{}
 	data.TagName = tagName
-	return newNode(KindJsxClosingElement, data, f.hooks)
+	return f.newNode(KindJsxClosingElement, data)
 }
 
 func (f *NodeFactory) UpdateJsxClosingElement(node *JsxClosingElement, tagName *JsxTagNameExpression) *Node {
@@ -8366,7 +8392,7 @@ func (f *NodeFactory) NewJsxExpression(dotDotDotToken *TokenNode, expression *Ex
 	data := &JsxExpression{}
 	data.DotDotDotToken = dotDotDotToken
 	data.Expression = expression
-	return newNode(KindJsxExpression, data, f.hooks)
+	return f.newNode(KindJsxExpression, data)
 }
 
 func (f *NodeFactory) UpdateJsxExpression(node *JsxExpression, dotDotDotToken *TokenNode, expression *Expression) *Node {
@@ -8408,7 +8434,8 @@ func (f *NodeFactory) NewJsxText(text string, containsOnlyTriviaWhiteSpace bool)
 	data := &JsxText{}
 	data.Text = text
 	data.ContainsOnlyTriviaWhiteSpaces = containsOnlyTriviaWhiteSpace
-	return newNode(KindJsxText, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindJsxText, data)
 }
 
 func (node *JsxText) Clone(f *NodeFactory) *Node {
@@ -8433,7 +8460,7 @@ type SyntaxList struct {
 func (f *NodeFactory) NewSyntaxList(children []*Node) *Node {
 	data := &SyntaxList{}
 	data.Children = children
-	return newNode(KindSyntaxList, data, f.hooks)
+	return f.newNode(KindSyntaxList, data)
 }
 
 func (node *SyntaxList) ForEachChild(v Visitor) bool {
@@ -8456,7 +8483,7 @@ func (f *NodeFactory) NewJSDoc(comment *NodeList, tags *NodeList) *Node {
 	data := f.jsdocPool.New()
 	data.Comment = comment
 	data.Tags = tags
-	return newNode(KindJSDoc, data, f.hooks)
+	return f.newNode(KindJSDoc, data)
 }
 
 func (f *NodeFactory) UpdateJSDoc(node *JSDoc, comment *NodeList, tags *NodeList) *Node {
@@ -8497,7 +8524,8 @@ type JSDocText struct {
 func (f *NodeFactory) NewJSDocText(text string) *Node {
 	data := f.jsdocTextPool.New()
 	data.Text = text
-	return newNode(KindJSDocText, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindJSDocText, data)
 }
 
 func (node *JSDocText) Clone(f *NodeFactory) *Node {
@@ -8513,7 +8541,8 @@ func (f *NodeFactory) NewJSDocLink(name *Node, text string) *Node {
 	data := &JSDocLink{}
 	data.name = name
 	data.Text = text
-	return newNode(KindJSDocLink, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindJSDocLink, data)
 }
 
 func (f *NodeFactory) UpdateJSDocLink(node *JSDocLink, name *Node, text string) *Node {
@@ -8548,7 +8577,8 @@ func (f *NodeFactory) NewJSDocLinkPlain(name *Node, text string) *Node {
 	data := &JSDocLinkPlain{}
 	data.name = name
 	data.Text = text
-	return newNode(KindJSDocLinkPlain, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindJSDocLinkPlain, data)
 }
 
 func (f *NodeFactory) UpdateJSDocLinkPlain(node *JSDocLinkPlain, name *Node, text string) *Node {
@@ -8583,7 +8613,8 @@ func (f *NodeFactory) NewJSDocLinkCode(name *Node, text string) *Node {
 	data := &JSDocLinkCode{}
 	data.name = name
 	data.Text = text
-	return newNode(KindJSDocLinkCode, data, f.hooks)
+	f.textCount++
+	return f.newNode(KindJSDocLinkCode, data)
 }
 
 func (f *NodeFactory) UpdateJSDocLinkCode(node *JSDocLinkCode, name *Node, text string) *Node {
@@ -8620,7 +8651,7 @@ type JSDocTypeExpression struct {
 func (f *NodeFactory) NewJSDocTypeExpression(typeNode *TypeNode) *Node {
 	data := &JSDocTypeExpression{}
 	data.Type = typeNode
-	return newNode(KindJSDocTypeExpression, data, f.hooks)
+	return f.newNode(KindJSDocTypeExpression, data)
 }
 
 func (f *NodeFactory) UpdateJSDocTypeExpression(node *JSDocTypeExpression, typeNode *TypeNode) *Node {
@@ -8652,7 +8683,7 @@ type JSDocNonNullableType struct {
 func (f *NodeFactory) NewJSDocNonNullableType(typeNode *TypeNode) *Node {
 	data := &JSDocNonNullableType{}
 	data.Type = typeNode
-	return newNode(KindJSDocNonNullableType, data, f.hooks)
+	return f.newNode(KindJSDocNonNullableType, data)
 }
 
 func (f *NodeFactory) UpdateJSDocNonNullableType(node *JSDocNonNullableType, typeNode *TypeNode) *Node {
@@ -8684,7 +8715,7 @@ type JSDocNullableType struct {
 func (f *NodeFactory) NewJSDocNullableType(typeNode *TypeNode) *Node {
 	data := &JSDocNullableType{}
 	data.Type = typeNode
-	return newNode(KindJSDocNullableType, data, f.hooks)
+	return f.newNode(KindJSDocNullableType, data)
 }
 
 func (f *NodeFactory) UpdateJSDocNullableType(node *JSDocNullableType, typeNode *TypeNode) *Node {
@@ -8714,7 +8745,7 @@ type JSDocAllType struct {
 
 func (f *NodeFactory) NewJSDocAllType() *Node {
 	data := &JSDocAllType{}
-	return newNode(KindJSDocAllType, data, f.hooks)
+	return f.newNode(KindJSDocAllType, data)
 }
 
 func (node *JSDocAllType) Clone(f *NodeFactory) *Node {
@@ -8731,7 +8762,7 @@ type JSDocVariadicType struct {
 func (f *NodeFactory) NewJSDocVariadicType(typeNode *TypeNode) *Node {
 	data := &JSDocVariadicType{}
 	data.Type = typeNode
-	return newNode(KindJSDocVariadicType, data, f.hooks)
+	return f.newNode(KindJSDocVariadicType, data)
 }
 
 func (f *NodeFactory) UpdateJSDocVariadicType(node *JSDocVariadicType, typeNode *TypeNode) *Node {
@@ -8763,7 +8794,7 @@ type JSDocOptionalType struct {
 func (f *NodeFactory) NewJSDocOptionalType(typeNode *TypeNode) *Node {
 	data := &JSDocOptionalType{}
 	data.Type = typeNode
-	return newNode(KindJSDocOptionalType, data, f.hooks)
+	return f.newNode(KindJSDocOptionalType, data)
 }
 
 func (f *NodeFactory) UpdateJSDocOptionalType(node *JSDocOptionalType, typeNode *TypeNode) *Node {
@@ -8797,7 +8828,7 @@ func (f *NodeFactory) NewJSDocTypeTag(tagName *IdentifierNode, typeExpression *N
 	data.TagName = tagName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocTypeTag, data, f.hooks)
+	return f.newNode(KindJSDocTypeTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocTypeTag(node *JSDocTypeTag, tagName *IdentifierNode, typeExpression *TypeNode, comment *NodeList) *Node {
@@ -8832,7 +8863,7 @@ func (f *NodeFactory) NewJSDocUnknownTag(tagName *IdentifierNode, comment *NodeL
 	data := &JSDocUnknownTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocTag, data, f.hooks)
+	return f.newNode(KindJSDocTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocUnknownTag(node *JSDocUnknownTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -8871,7 +8902,7 @@ func (f *NodeFactory) NewJSDocTemplateTag(tagName *IdentifierNode, constraint *N
 	data.Constraint = constraint
 	data.typeParameters = typeParameters
 	data.Comment = comment
-	return newNode(KindJSDocTemplateTag, data, f.hooks)
+	return f.newNode(KindJSDocTemplateTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocTemplateTag(node *JSDocTemplateTag, tagName *IdentifierNode, constraint *Node, typeParameters *TypeParameterList, comment *NodeList) *Node {
@@ -8912,7 +8943,7 @@ func (f *NodeFactory) NewJSDocPropertyTag(tagName *IdentifierNode, name *EntityN
 	data.TypeExpression = typeExpression
 	data.IsNameFirst = isNameFirst
 	data.Comment = comment
-	return newNode(KindJSDocPropertyTag, data, f.hooks)
+	return f.newNode(KindJSDocPropertyTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocPropertyTag(node *JSDocPropertyTag, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
@@ -8964,7 +8995,7 @@ func (f *NodeFactory) NewJSDocParameterTag(tagName *IdentifierNode, name *Entity
 	data.TypeExpression = typeExpression
 	data.IsNameFirst = isNameFirst
 	data.Comment = comment
-	return newNode(KindJSDocParameterTag, data, f.hooks)
+	return f.newNode(KindJSDocParameterTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocParameterTag(node *JSDocParameterTag, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
@@ -9013,7 +9044,7 @@ func (f *NodeFactory) NewJSDocReturnTag(tagName *IdentifierNode, typeExpression
 	data.TagName = tagName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocReturnTag, data, f.hooks)
+	return f.newNode(KindJSDocReturnTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocReturnTag(node *JSDocReturnTag, tagName *IdentifierNode, typeExpression *TypeNode, comment *NodeList) *Node {
@@ -9048,7 +9079,7 @@ func (f *NodeFactory) NewJSDocPublicTag(tagName *IdentifierNode, comment *NodeLi
 	data := &JSDocPublicTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocPublicTag, data, f.hooks)
+	return f.newNode(KindJSDocPublicTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocPublicTag(node *JSDocPublicTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9079,7 +9110,7 @@ func (f *NodeFactory) NewJSDocPrivateTag(tagName *IdentifierNode, comment *NodeL
 	data := &JSDocPrivateTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocPrivateTag, data, f.hooks)
+	return f.newNode(KindJSDocPrivateTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocPrivateTag(node *JSDocPrivateTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9110,7 +9141,7 @@ func (f *NodeFactory) NewJSDocProtectedTag(tagName *IdentifierNode, comment *Nod
 	data := &JSDocProtectedTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocProtectedTag, data, f.hooks)
+	return f.newNode(KindJSDocProtectedTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocProtectedTag(node *JSDocProtectedTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9141,7 +9172,7 @@ func (f *NodeFactory) NewJSDocReadonlyTag(tagName *IdentifierNode, comment *Node
 	data := &JSDocReadonlyTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocReadonlyTag, data, f.hooks)
+	return f.newNode(KindJSDocReadonlyTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocReadonlyTag(node *JSDocReadonlyTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9172,7 +9203,7 @@ func (f *NodeFactory) NewJSDocOverrideTag(tagName *IdentifierNode, comment *Node
 	data := &JSDocOverrideTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocOverrideTag, data, f.hooks)
+	return f.newNode(KindJSDocOverrideTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocOverrideTag(node *JSDocOverrideTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9203,7 +9234,7 @@ func (f *NodeFactory) NewJSDocDeprecatedTag(tagName *IdentifierNode, comment *No
 	data := &JSDocDeprecatedTag{}
 	data.TagName = tagName
 	data.Comment = comment
-	return newNode(KindJSDocDeprecatedTag, data, f.hooks)
+	return f.newNode(KindJSDocDeprecatedTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocDeprecatedTag(node *JSDocDeprecatedTag, tagName *IdentifierNode, comment *NodeList) *Node {
@@ -9240,7 +9271,7 @@ func (f *NodeFactory) NewJSDocSeeTag(tagName *IdentifierNode, nameExpression *Ty
 	data.TagName = tagName
 	data.NameExpression = nameExpression
 	data.Comment = comment
-	return newNode(KindJSDocSeeTag, data, f.hooks)
+	return f.newNode(KindJSDocSeeTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocSeeTag(node *JSDocSeeTag, tagName *IdentifierNode, nameExpression *TypeNode, comment *NodeList) *Node {
@@ -9273,7 +9304,7 @@ func (f *NodeFactory) NewJSDocImplementsTag(tagName *IdentifierNode, className *
 	data.TagName = tagName
 	data.ClassName = className
 	data.Comment = comment
-	return newNode(KindJSDocImplementsTag, data, f.hooks)
+	return f.newNode(KindJSDocImplementsTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocImplementsTag(node *JSDocImplementsTag, tagName *IdentifierNode, className *Expression, comment *NodeList) *Node {
@@ -9306,7 +9337,7 @@ func (f *NodeFactory) NewJSDocAugmentsTag(tagName *IdentifierNode, className *Ex
 	data.TagName = tagName
 	data.ClassName = className
 	data.Comment = comment
-	return newNode(KindJSDocAugmentsTag, data, f.hooks)
+	return f.newNode(KindJSDocAugmentsTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocAugmentsTag(node *JSDocAugmentsTag, tagName *IdentifierNode, className *Expression, comment *NodeList) *Node {
@@ -9339,7 +9370,7 @@ func (f *NodeFactory) NewJSDocSatisfiesTag(tagName *IdentifierNode, typeExpressi
 	data.TagName = tagName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocSatisfiesTag, data, f.hooks)
+	return f.newNode(KindJSDocSatisfiesTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocSatisfiesTag(node *JSDocSatisfiesTag, tagName *IdentifierNode, typeExpression *TypeNode, comment *NodeList) *Node {
@@ -9372,7 +9403,7 @@ func (f *NodeFactory) NewJSDocThisTag(tagName *IdentifierNode, typeExpression *T
 	data.TagName = tagName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocThisTag, data, f.hooks)
+	return f.newNode(KindJSDocThisTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocThisTag(node *JSDocThisTag, tagName *IdentifierNode, typeExpression *TypeNode, comment *NodeList) *Node {
@@ -9409,7 +9440,7 @@ func (f *NodeFactory) NewJSDocImportTag(tagName *IdentifierNode, importClause *D
 	data.ModuleSpecifier = moduleSpecifier
 	data.Attributes = attributes
 	data.Comment = comment
-	return newNode(KindJSDocImportTag, data, f.hooks)
+	return f.newNode(KindJSDocImportTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocImportTag(node *JSDocImportTag, tagName *IdentifierNode, importClause *Declaration, moduleSpecifier *Node, attributes *Node, comment *NodeList) *Node {
@@ -9444,7 +9475,7 @@ func (f *NodeFactory) NewJSDocCallbackTag(tagName *IdentifierNode, typeExpressio
 	data.FullName = fullName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocCallbackTag, data, f.hooks)
+	return f.newNode(KindJSDocCallbackTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocCallbackTag(node *JSDocCallbackTag, tagName *IdentifierNode, typeExpression *TypeNode, fullName *Node, comment *NodeList) *Node {
@@ -9477,7 +9508,7 @@ func (f *NodeFactory) NewJSDocOverloadTag(tagName *IdentifierNode, typeExpressio
 	data.TagName = tagName
 	data.TypeExpression = typeExpression
 	data.Comment = comment
-	return newNode(KindJSDocOverloadTag, data, f.hooks)
+	return f.newNode(KindJSDocOverloadTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocOverloadTag(node *JSDocOverloadTag, tagName *IdentifierNode, typeExpression *TypeNode, comment *NodeList) *Node {
@@ -9512,7 +9543,7 @@ func (f *NodeFactory) NewJSDocTypedefTag(tagName *IdentifierNode, typeExpression
 	data.TypeExpression = typeExpression
 	data.name = name
 	data.Comment = comment
-	return newNode(KindJSDocTypedefTag, data, f.hooks)
+	return f.newNode(KindJSDocTypedefTag, data)
 }
 
 func (f *NodeFactory) UpdateJSDocTypedefTag(node *JSDocTypedefTag, tagName *IdentifierNode, typeExpression *Node, fullName *IdentifierNode, comment *NodeList) *Node {
@@ -9551,7 +9582,7 @@ func (f *NodeFactory) NewJSDocTypeLiteral(jsdocPropertyTags []*Node, isArrayType
 	data := &JSDocTypeLiteral{}
 	data.JSDocPropertyTags = jsdocPropertyTags
 	data.IsArrayType = isArrayType
-	return newNode(KindJSDocTypeLiteral, data, f.hooks)
+	return f.newNode(KindJSDocTypeLiteral, data)
 }
 
 func (f *NodeFactory) UpdateJSDocTypeLiteral(node *JSDocTypeLiteral, jsdocPropertyTags []*Node, isArrayType bool) *Node {
@@ -9587,7 +9618,7 @@ func (f *NodeFactory) NewJSDocSignature(typeParameters *TypeParameterList, param
 	data.typeParameters = typeParameters
 	data.Parameters = parameters
 	data.Type = typeNode
-	return newNode(KindJSDocSignature, data, f.hooks)
+	return f.newNode(KindJSDocSignature, data)
 }
 
 func (f *NodeFactory) UpdateJSDocSignature(node *JSDocSignature, typeParameters *TypeParameterList, parameters *NodeList, typeNode *JSDocTag) *Node {
@@ -9620,7 +9651,7 @@ type JSDocNameReference struct {
 func (f *NodeFactory) NewJSDocNameReference(name *EntityName) *Node {
 	data := &JSDocNameReference{}
 	data.name = name
-	return newNode(KindJSDocNameReference, data, f.hooks)
+	return f.newNode(KindJSDocNameReference, data)
 }
 
 func (f *NodeFactory) UpdateJSDocNameReference(node *JSDocNameReference, name *EntityName) *Node {
@@ -9711,6 +9742,8 @@ type SourceFile struct {
 	TypeReferenceDirectives     []*FileReference
 	LibReferenceDirectives      []*FileReference
 	CheckJsDirective            *CheckJsDirective
+	NodeCount                   int
+	TextCount                   int
 
 	// Fields set by binder
 
@@ -9755,7 +9788,7 @@ func (f *NodeFactory) NewSourceFile(text string, fileName string, path tspath.Pa
 	data.path = path
 	data.Statements = statements
 	data.LanguageVersion = core.ScriptTargetLatest
-	return newNode(KindSourceFile, data, f.hooks)
+	return f.newNode(KindSourceFile, data)
 }
 
 func (node *SourceFile) Text() string {
diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go
index 30a4d350b2..4f690c9490 100644
--- a/internal/ast/utilities.go
+++ b/internal/ast/utilities.go
@@ -908,6 +908,17 @@ func FindAncestor(node *Node, callback func(*Node) bool) *Node {
 	return nil
 }
 
+// Walks up the parents of a node to find the ancestor that matches the kind
+func FindAncestorKind(node *Node, kind Kind) *Node {
+	for node != nil {
+		if node.Kind == kind {
+			return node
+		}
+		node = node.Parent
+	}
+	return nil
+}
+
 type FindAncestorResult int32
 
 const (
diff --git a/internal/checker/types.go b/internal/checker/types.go
index fa77c35cce..a99e7f7c43 100644
--- a/internal/checker/types.go
+++ b/internal/checker/types.go
@@ -538,6 +538,14 @@ type Type struct {
 	data        TypeData // Type specific data
 }
 
+func (t *Type) Id() TypeId {
+	return t.id
+}
+
+func (t *Type) Flags() TypeFlags {
+	return t.flags
+}
+
 // Casts for concrete struct types
 
 func (t *Type) AsIntrinsicType() *IntrinsicType             { return t.data.(*IntrinsicType) }
diff --git a/internal/core/core.go b/internal/core/core.go
index 53ce832db7..5456fb3077 100644
--- a/internal/core/core.go
+++ b/internal/core/core.go
@@ -58,6 +58,21 @@ func Map[T, U any](slice []T, f func(T) U) []U {
 	return result
 }
 
+func TryMap[T, U any](slice []T, f func(T) (U, error)) ([]U, error) {
+	if len(slice) == 0 {
+		return nil, nil
+	}
+	result := make([]U, len(slice))
+	for i, value := range slice {
+		mapped, err := f(value)
+		if err != nil {
+			return nil, err
+		}
+		result[i] = mapped
+	}
+	return result, nil
+}
+
 func MapIndex[T, U any](slice []T, f func(T, int) U) []U {
 	if slice == nil {
 		return nil
diff --git a/internal/ls/api.go b/internal/ls/api.go
new file mode 100644
index 0000000000..62fd233a6f
--- /dev/null
+++ b/internal/ls/api.go
@@ -0,0 +1,39 @@
+package ls
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/microsoft/typescript-go/internal/ast"
+	"github.com/microsoft/typescript-go/internal/astnav"
+	"github.com/microsoft/typescript-go/internal/checker"
+)
+
+var (
+	ErrNoSourceFile      = errors.New("source file not found")
+	ErrNoTokenAtPosition = errors.New("no token found at position")
+)
+
+func (l *LanguageService) GetSymbolAtPosition(fileName string, position int) (*ast.Symbol, error) {
+	program, file := l.tryGetProgramAndFile(fileName)
+	if file == nil {
+		return nil, fmt.Errorf("%w: %s", ErrNoSourceFile, fileName)
+	}
+	node := astnav.GetTokenAtPosition(file, position)
+	if node == nil {
+		return nil, fmt.Errorf("%w: %s:%d", ErrNoTokenAtPosition, fileName, position)
+	}
+	checker := program.GetTypeChecker()
+	return checker.GetSymbolAtLocation(node), nil
+}
+
+func (l *LanguageService) GetSymbolAtLocation(node *ast.Node) *ast.Symbol {
+	program := l.GetProgram()
+	checker := program.GetTypeChecker()
+	return checker.GetSymbolAtLocation(node)
+}
+
+func (l *LanguageService) GetTypeOfSymbol(symbol *ast.Symbol) *checker.Type {
+	checker := l.GetProgram().GetTypeChecker()
+	return checker.GetTypeOfSymbolAtLocation(symbol, nil)
+}
diff --git a/internal/ls/languageservice.go b/internal/ls/languageservice.go
index c3401c5f96..60f0b830a1 100644
--- a/internal/ls/languageservice.go
+++ b/internal/ls/languageservice.go
@@ -57,9 +57,14 @@ func (l *LanguageService) GetProgram() *compiler.Program {
 	return l.host.GetProgram()
 }
 
-func (l *LanguageService) getProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
+func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
 	program := l.GetProgram()
 	file := program.GetSourceFile(fileName)
+	return program, file
+}
+
+func (l *LanguageService) getProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
+	program, file := l.tryGetProgramAndFile(fileName)
 	if file == nil {
 		panic("file not found")
 	}
diff --git a/internal/lsp/lsproto/jsonrpc.go b/internal/lsp/lsproto/jsonrpc.go
index 952a3635b9..0af3ac6cf1 100644
--- a/internal/lsp/lsproto/jsonrpc.go
+++ b/internal/lsp/lsproto/jsonrpc.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strings"
 )
 
 type JSONRPCVersion struct{}
@@ -43,6 +44,13 @@ func (id *ID) UnmarshalJSON(data []byte) error {
 	return json.Unmarshal(data, &id.int)
 }
 
+func (id *ID) MustInt() int32 {
+	if id.str != "" {
+		panic("ID is not an integer")
+	}
+	return id.int
+}
+
 // TODO(jakebailey): NotificationMessage? Use RequestMessage without ID?
 
 type RequestMessage struct {
@@ -70,6 +78,11 @@ func (r *RequestMessage) UnmarshalJSON(data []byte) error {
 		return nil
 	}
 
+	if strings.HasPrefix(string(r.Method), "@ts/") {
+		r.Params = raw.Params
+		return nil
+	}
+
 	var params any
 	var err error
 
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 7118c8b208..dedd8862ed 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -255,11 +255,10 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) error {
 }
 
 func (s *Server) handleInitialized(req *lsproto.RequestMessage) error {
-	s.logger = project.NewLogger([]io.Writer{s.stderr}, project.LogLevelVerbose)
+	s.logger = project.NewLogger([]io.Writer{s.stderr}, "" /*file*/, project.LogLevelVerbose)
 	s.projectService = project.NewService(s, project.ServiceOptions{
-		DefaultLibraryPath: s.defaultLibraryPath,
-		Logger:             s.logger,
-		PositionEncoding:   s.positionEncoding,
+		Logger:           s.logger,
+		PositionEncoding: s.positionEncoding,
 	})
 
 	s.converters = ls.NewConverters(s.positionEncoding, func(fileName string) ls.ScriptInfo {
diff --git a/internal/parser/parser.go b/internal/parser/parser.go
index 8950bbdf01..5c095b739f 100644
--- a/internal/parser/parser.go
+++ b/internal/parser/parser.go
@@ -352,6 +352,8 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool
 	result.ScriptKind = p.scriptKind
 	result.Flags |= p.sourceFlags
 	result.Identifiers = p.identifiers
+	result.NodeCount = p.factory.NodeCount()
+	result.TextCount = p.factory.TextCount()
 	result.IdentifierCount = p.identifierCount
 	result.SetJSDocCache(p.jsdocCache)
 	p.jsdocCache = nil
diff --git a/internal/project/documentregistry.go b/internal/project/documentregistry.go
index f8d86693d8..f4a651e48c 100644
--- a/internal/project/documentregistry.go
+++ b/internal/project/documentregistry.go
@@ -31,20 +31,19 @@ type registryEntry struct {
 	mu         sync.Mutex
 }
 
+type DocumentRegistryHooks struct {
+	OnReleaseDocument func(file *ast.SourceFile)
+}
+
 // The document registry represents a store of SourceFile objects that can be shared between
 // multiple LanguageService instances.
-type documentRegistry struct {
-	options   tspath.ComparePathsOptions
+type DocumentRegistry struct {
+	Options   tspath.ComparePathsOptions
+	Hooks     DocumentRegistryHooks
 	documents collections.SyncMap[registryKey, *registryEntry]
 }
 
-func newDocumentRegistry(options tspath.ComparePathsOptions) *documentRegistry {
-	return &documentRegistry{
-		options: options,
-	}
-}
-
-// acquireDocument gets a SourceFile from the registry if it exists as the same version tracked
+// AcquireDocument gets a SourceFile from the registry if it exists as the same version tracked
 // by the ScriptInfo. If it does not exist, or is out of date, it creates a new SourceFile and
 // stores it, tracking that the caller has referenced it. If an oldSourceFile is passed, the registry
 // will decrement its reference count and remove it from the registry if the count reaches 0.
@@ -55,7 +54,7 @@ func newDocumentRegistry(options tspath.ComparePathsOptions) *documentRegistry {
 // LanguageService instance over time, as well as across multiple instances. Here, we still
 // reuse files across multiple LanguageServices, but we only reuse them across Program updates
 // when the files haven't changed.
-func (r *documentRegistry) acquireDocument(scriptInfo *ScriptInfo, compilerOptions *core.CompilerOptions, oldSourceFile *ast.SourceFile, oldCompilerOptions *core.CompilerOptions) *ast.SourceFile {
+func (r *DocumentRegistry) AcquireDocument(scriptInfo *ScriptInfo, compilerOptions *core.CompilerOptions, oldSourceFile *ast.SourceFile, oldCompilerOptions *core.CompilerOptions) *ast.SourceFile {
 	key := newRegistryKey(compilerOptions, scriptInfo.path, scriptInfo.scriptKind)
 	document := r.getDocumentWorker(scriptInfo, compilerOptions, key)
 	if oldSourceFile != nil && oldCompilerOptions != nil {
@@ -65,23 +64,26 @@ func (r *documentRegistry) acquireDocument(scriptInfo *ScriptInfo, compilerOptio
 	return document
 }
 
-func (r *documentRegistry) releaseDocument(file *ast.SourceFile, compilerOptions *core.CompilerOptions) {
+func (r *DocumentRegistry) ReleaseDocument(file *ast.SourceFile, compilerOptions *core.CompilerOptions) {
 	key := newRegistryKey(compilerOptions, file.Path(), file.ScriptKind)
 	r.releaseDocumentWithKey(key)
 }
 
-func (r *documentRegistry) releaseDocumentWithKey(key registryKey) {
+func (r *DocumentRegistry) releaseDocumentWithKey(key registryKey) {
 	if entry, ok := r.documents.Load(key); ok {
 		entry.mu.Lock()
 		defer entry.mu.Unlock()
 		entry.refCount--
 		if entry.refCount == 0 {
 			r.documents.Delete(key)
+			if r.Hooks.OnReleaseDocument != nil {
+				r.Hooks.OnReleaseDocument(entry.sourceFile)
+			}
 		}
 	}
 }
 
-func (r *documentRegistry) getDocumentWorker(
+func (r *DocumentRegistry) getDocumentWorker(
 	scriptInfo *ScriptInfo,
 	compilerOptions *core.CompilerOptions,
 	key registryKey,
@@ -115,6 +117,6 @@ func (r *documentRegistry) getDocumentWorker(
 }
 
 // size should only be used for testing.
-func (r *documentRegistry) size() int {
+func (r *DocumentRegistry) size() int {
 	return r.documents.Size()
 }
diff --git a/internal/project/logger.go b/internal/project/logger.go
index 2269586926..cc2db1c8bd 100644
--- a/internal/project/logger.go
+++ b/internal/project/logger.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"os"
 	"strings"
 	"time"
 )
@@ -18,18 +19,38 @@ const (
 )
 
 type Logger struct {
-	outputs []*bufio.Writer
-	level   LogLevel
-	inGroup bool
-	seq     int
+	outputs    []*bufio.Writer
+	fileHandle *os.File
+	level      LogLevel
+	inGroup    bool
+	seq        int
 }
 
-func NewLogger(outputs []io.Writer, level LogLevel) *Logger {
+func NewLogger(outputs []io.Writer, file string, level LogLevel) *Logger {
 	var o []*bufio.Writer
 	for _, w := range outputs {
 		o = append(o, bufio.NewWriter(w))
 	}
-	return &Logger{outputs: o, level: level}
+	logger := &Logger{outputs: o, level: level}
+	logger.SetFile(file)
+	return logger
+}
+
+func (l *Logger) SetFile(file string) {
+	if l.fileHandle != nil {
+		oldWriter := l.outputs[len(l.outputs)-1]
+		l.outputs = l.outputs[:len(l.outputs)-1]
+		_ = oldWriter.Flush()
+		l.fileHandle.Close()
+	}
+	if file != "" {
+		f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
+		if err != nil {
+			panic(err)
+		}
+		l.fileHandle = f
+		l.outputs = append(l.outputs, bufio.NewWriter(f))
+	}
 }
 
 func (l *Logger) PerfTrace(s string) {
@@ -45,22 +66,43 @@ func (l *Logger) Error(s string) {
 }
 
 func (l *Logger) StartGroup() {
+	if l == nil {
+		return
+	}
 	l.inGroup = true
 }
 
 func (l *Logger) EndGroup() {
+	if l == nil {
+		return
+	}
 	l.inGroup = false
 }
 
 func (l *Logger) LoggingEnabled() bool {
-	return len(l.outputs) > 0
+	return l != nil && len(l.outputs) > 0
 }
 
 func (l *Logger) HasLevel(level LogLevel) bool {
-	return l.LoggingEnabled() && l.level >= level
+	return l != nil && l.LoggingEnabled() && l.level >= level
+}
+
+func (l *Logger) Close() {
+	if l == nil {
+		return
+	}
+	for _, output := range l.outputs {
+		_ = output.Flush()
+	}
+	if l.fileHandle != nil {
+		_ = l.fileHandle.Close()
+	}
 }
 
 func (l *Logger) msg(s string, messageType string) {
+	if l == nil {
+		return
+	}
 	for _, output := range l.outputs {
 		header := fmt.Sprintf("%s %d", messageType, l.seq)
 		output.WriteString(header)                                      //nolint: errcheck
diff --git a/internal/project/project.go b/internal/project/project.go
index 14bd41d354..3aa4e4751e 100644
--- a/internal/project/project.go
+++ b/internal/project/project.go
@@ -11,6 +11,7 @@ import (
 	"github.com/microsoft/typescript-go/internal/core"
 	"github.com/microsoft/typescript-go/internal/ls"
 	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
+	"github.com/microsoft/typescript-go/internal/tsoptions"
 	"github.com/microsoft/typescript-go/internal/tspath"
 	"github.com/microsoft/typescript-go/internal/vfs"
 )
@@ -30,9 +31,21 @@ const (
 	KindAuxiliary
 )
 
+type ProjectHost interface {
+	tsoptions.ParseConfigHost
+	NewLine() string
+	DefaultLibraryPath() string
+	DocumentRegistry() *DocumentRegistry
+	GetScriptInfoByPath(path tspath.Path) *ScriptInfo
+	GetOrCreateScriptInfoForFile(fileName string, path tspath.Path, scriptKind core.ScriptKind) *ScriptInfo
+	OnDiscoveredSymlink(info *ScriptInfo)
+	Log(s string)
+	PositionEncoding() lsproto.PositionEncodingKind
+}
+
 type Project struct {
-	projectService *Service
-	mu             sync.Mutex
+	host ProjectHost
+	mu   sync.Mutex
 
 	name string
 	kind Kind
@@ -59,25 +72,25 @@ type Project struct {
 	program         *compiler.Program
 }
 
-func NewConfiguredProject(configFileName string, configFilePath tspath.Path, projectService *Service) *Project {
-	project := NewProject(configFileName, KindConfigured, tspath.GetDirectoryPath(configFileName), projectService)
+func NewConfiguredProject(configFileName string, configFilePath tspath.Path, host ProjectHost) *Project {
+	project := NewProject(configFileName, KindConfigured, tspath.GetDirectoryPath(configFileName), host)
 	project.configFileName = configFileName
 	project.configFilePath = configFilePath
 	project.initialLoadPending = true
 	return project
 }
 
-func NewInferredProject(compilerOptions *core.CompilerOptions, currentDirectory string, projectRootPath tspath.Path, projectService *Service) *Project {
-	project := NewProject(projectNamer.next("/dev/null/inferredProject"), KindInferred, currentDirectory, projectService)
+func NewInferredProject(compilerOptions *core.CompilerOptions, currentDirectory string, projectRootPath tspath.Path, host ProjectHost) *Project {
+	project := NewProject(projectNamer.next("/dev/null/inferredProject"), KindInferred, currentDirectory, host)
 	project.rootPath = projectRootPath
 	project.compilerOptions = compilerOptions
 	return project
 }
 
-func NewProject(name string, kind Kind, currentDirectory string, projectService *Service) *Project {
-	projectService.log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory))
+func NewProject(name string, kind Kind, currentDirectory string, host ProjectHost) *Project {
+	host.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory))
 	project := &Project{
-		projectService:   projectService,
+		host:             host,
 		name:             name,
 		kind:             kind,
 		currentDirectory: currentDirectory,
@@ -90,12 +103,12 @@ func NewProject(name string, kind Kind, currentDirectory string, projectService
 
 // FS implements LanguageServiceHost.
 func (p *Project) FS() vfs.FS {
-	return p.projectService.host.FS()
+	return p.host.FS()
 }
 
 // DefaultLibraryPath implements LanguageServiceHost.
 func (p *Project) DefaultLibraryPath() string {
-	return p.projectService.host.DefaultLibraryPath()
+	return p.host.DefaultLibraryPath()
 }
 
 // GetCompilerOptions implements LanguageServiceHost.
@@ -117,7 +130,7 @@ func (p *Project) GetProjectVersion() int {
 func (p *Project) GetRootFileNames() []string {
 	fileNames := make([]string, 0, p.rootFileNames.Size())
 	for path, fileName := range p.rootFileNames.Entries() {
-		if p.projectService.getScriptInfo(path) != nil {
+		if p.host.GetScriptInfoByPath(path) != nil {
 			fileNames = append(fileNames, fileName)
 		}
 	}
@@ -136,7 +149,7 @@ func (p *Project) GetSourceFile(fileName string, path tspath.Path, languageVersi
 			oldSourceFile = p.program.GetSourceFileByPath(scriptInfo.path)
 			oldCompilerOptions = p.program.GetCompilerOptions()
 		}
-		return p.projectService.documentRegistry.acquireDocument(scriptInfo, p.GetCompilerOptions(), oldSourceFile, oldCompilerOptions)
+		return p.host.DocumentRegistry().AcquireDocument(scriptInfo, p.GetCompilerOptions(), oldSourceFile, oldCompilerOptions)
 	}
 	return nil
 }
@@ -149,7 +162,7 @@ func (p *Project) GetProgram() *compiler.Program {
 
 // NewLine implements LanguageServiceHost.
 func (p *Project) NewLine() string {
-	return p.projectService.host.NewLine()
+	return p.host.NewLine()
 }
 
 // Trace implements LanguageServiceHost.
@@ -159,17 +172,17 @@ func (p *Project) Trace(msg string) {
 
 // GetDefaultLibraryPath implements ls.Host.
 func (p *Project) GetDefaultLibraryPath() string {
-	return p.projectService.options.DefaultLibraryPath
+	return p.host.DefaultLibraryPath()
 }
 
 // GetScriptInfo implements ls.Host.
 func (p *Project) GetScriptInfo(fileName string) ls.ScriptInfo {
-	return p.projectService.GetScriptInfo(fileName)
+	return p.host.GetScriptInfoByPath(p.toPath(fileName))
 }
 
 // GetPositionEncoding implements ls.Host.
 func (p *Project) GetPositionEncoding() lsproto.PositionEncodingKind {
-	return p.projectService.options.PositionEncoding
+	return p.host.PositionEncoding()
 }
 
 func (p *Project) Name() string {
@@ -180,6 +193,10 @@ func (p *Project) Kind() Kind {
 	return p.kind
 }
 
+func (p *Project) Version() int {
+	return p.version
+}
+
 func (p *Project) CurrentProgram() *compiler.Program {
 	return p.program
 }
@@ -189,7 +206,7 @@ func (p *Project) LanguageService() *ls.LanguageService {
 }
 
 func (p *Project) getOrCreateScriptInfoAndAttachToProject(fileName string, scriptKind core.ScriptKind) *ScriptInfo {
-	if scriptInfo := p.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, p.projectService.toPath(fileName), scriptKind); scriptInfo != nil {
+	if scriptInfo := p.host.GetOrCreateScriptInfoForFile(fileName, p.toPath(fileName), scriptKind); scriptInfo != nil {
 		scriptInfo.attachToProject(p)
 		return scriptInfo
 	}
@@ -241,7 +258,9 @@ func (p *Project) updateGraph() bool {
 	p.initialLoadPending = false
 
 	if p.kind == KindConfigured && p.reloadConfig {
-		p.projectService.loadConfiguredProject(p)
+		if err := p.LoadConfig(); err != nil {
+			panic(fmt.Sprintf("failed to reload config: %v", err))
+		}
 		p.reloadConfig = false
 	}
 
@@ -259,7 +278,7 @@ func (p *Project) updateGraph() bool {
 	if p.program != oldProgram && oldProgram != nil {
 		for _, oldSourceFile := range oldProgram.GetSourceFiles() {
 			if p.program.GetSourceFileByPath(oldSourceFile.Path()) == nil {
-				p.projectService.documentRegistry.releaseDocument(oldSourceFile, oldProgram.GetCompilerOptions())
+				p.host.DocumentRegistry().ReleaseDocument(oldSourceFile, oldProgram.GetCompilerOptions())
 			}
 		}
 	}
@@ -326,7 +345,7 @@ func (p *Project) removeFile(info *ScriptInfo, fileExists bool, detachFromProjec
 func (p *Project) addRoot(info *ScriptInfo) {
 	// !!!
 	// if p.kind == KindInferred {
-	// 	p.projectService.startWatchingConfigFilesForInferredProjectRoot(info.path);
+	// 	p.host.startWatchingConfigFilesForInferredProjectRoot(info.path);
 	//  // handle JS toggling
 	// }
 	if p.isRoot(info) {
@@ -337,6 +356,75 @@ func (p *Project) addRoot(info *ScriptInfo) {
 	p.markAsDirty()
 }
 
+func (p *Project) LoadConfig() error {
+	if p.kind != KindConfigured {
+		panic("loadConfig called on non-configured project")
+	}
+
+	if configFileContent, ok := p.host.FS().ReadFile(p.configFileName); ok {
+		configDir := tspath.GetDirectoryPath(p.configFileName)
+		tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(p.configFileName, p.configFilePath, configFileContent)
+		parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
+			tsConfigSourceFile,
+			p.host,
+			configDir,
+			nil, /*existingOptions*/
+			p.configFileName,
+			nil, /*resolutionStack*/
+			nil, /*extraFileExtensions*/
+			nil, /*extendedConfigCache*/
+		)
+
+		p.logf("Config: %s : %s",
+			p.configFileName,
+			core.Must(core.StringifyJson(map[string]any{
+				"rootNames":         parsedCommandLine.FileNames(),
+				"options":           parsedCommandLine.CompilerOptions(),
+				"projectReferences": parsedCommandLine.ProjectReferences(),
+			}, "    ", "  ")),
+		)
+
+		p.compilerOptions = parsedCommandLine.CompilerOptions()
+		p.setRootFiles(parsedCommandLine.FileNames())
+	} else {
+		p.compilerOptions = &core.CompilerOptions{}
+		return fmt.Errorf("could not read file %q", p.configFileName)
+	}
+
+	p.markAsDirty()
+	return nil
+}
+
+func (p *Project) setRootFiles(rootFileNames []string) {
+	newRootScriptInfos := make(map[tspath.Path]struct{}, len(rootFileNames))
+	for _, file := range rootFileNames {
+		scriptKind := p.getScriptKind(file)
+		scriptInfo := p.host.GetOrCreateScriptInfoForFile(file, p.toPath(file), scriptKind)
+		newRootScriptInfos[scriptInfo.path] = struct{}{}
+		if _, isRoot := p.rootFileNames.Get(scriptInfo.path); !isRoot {
+			p.addRoot(scriptInfo)
+			if scriptInfo.isOpen {
+				// !!!
+				// s.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo)
+			}
+		} else {
+			p.rootFileNames.Set(scriptInfo.path, file)
+		}
+	}
+
+	if p.rootFileNames.Size() > len(rootFileNames) {
+		for root := range p.rootFileNames.Keys() {
+			if _, ok := newRootScriptInfos[root]; !ok {
+				if info := p.host.GetScriptInfoByPath(root); info != nil {
+					p.removeFile(info, true /*fileExists*/, true /*detachFromProject*/)
+				} else {
+					p.rootFileNames.Delete(root)
+				}
+			}
+		}
+	}
+}
+
 func (p *Project) clearSourceMapperCache() {
 	// !!!
 }
@@ -368,5 +456,13 @@ func (p *Project) print(writeFileNames bool, writeFileExplanation bool, writeFil
 }
 
 func (p *Project) log(s string) {
-	p.projectService.log(s)
+	p.host.Log(s)
+}
+
+func (p *Project) logf(format string, args ...interface{}) {
+	p.log(fmt.Sprintf(format, args...))
+}
+
+func (p *Project) Close() {
+	// !!!
 }
diff --git a/internal/project/scriptinfo.go b/internal/project/scriptinfo.go
index 2ad81034c1..f97f15ad13 100644
--- a/internal/project/scriptinfo.go
+++ b/internal/project/scriptinfo.go
@@ -29,7 +29,7 @@ type ScriptInfo struct {
 	containingProjects []*Project
 }
 
-func newScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind) *ScriptInfo {
+func NewScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind) *ScriptInfo {
 	isDynamic := isDynamicFileName(fileName)
 	realpath := core.IfElse(isDynamic, path, "")
 	return &ScriptInfo{
@@ -70,7 +70,7 @@ func (s *ScriptInfo) open(newText string) {
 	}
 }
 
-func (s *ScriptInfo) setTextFromDisk(newText string) {
+func (s *ScriptInfo) SetTextFromDisk(newText string) {
 	if newText != s.text {
 		s.setText(newText)
 		s.matchesDiskText = true
@@ -146,7 +146,7 @@ func (s *ScriptInfo) ensureRealpath(fs vfs.FS) {
 		project := s.containingProjects[0]
 		s.realpath = project.toPath(realpath)
 		if s.realpath != s.path {
-			project.projectService.recordSymlink(s)
+			project.host.OnDiscoveredSymlink(s)
 		}
 	}
 }
@@ -184,64 +184,3 @@ func (s *ScriptInfo) delayReloadNonMixedContentFile() {
 	s.pendingReloadFromDisk = true
 	s.markContainingProjectsAsDirty()
 }
-
-func (s *ScriptInfo) getDefaultProject() *Project {
-	switch len(s.containingProjects) {
-	case 0:
-		panic("scriptInfo must be attached to a project before calling getDefaultProject")
-	case 1:
-		project := s.containingProjects[0]
-		if project.deferredClose || project.kind == KindAutoImportProvider || project.kind == KindAuxiliary {
-			panic("scriptInfo must be attached to a non-background project before calling getDefaultProject")
-		}
-		return project
-	default:
-		// If this file belongs to multiple projects, below is the order in which default project is used
-		// - first external project
-		// - for open script info, its default configured project during opening is default if info is part of it
-		// - first configured project of which script info is not a source of project reference redirect
-		// - first configured project
-		// - first inferred project
-		var firstConfiguredProject *Project
-		var firstInferredProject *Project
-		var firstNonSourceOfProjectReferenceRedirect *Project
-		var defaultConfiguredProject *Project
-
-		for index, project := range s.containingProjects {
-			if project.kind == KindConfigured {
-				if project.deferredClose {
-					continue
-				}
-				// !!! if !project.isSourceOfProjectReferenceRedirect(s.fileName) {
-				if defaultConfiguredProject == nil && index != len(s.containingProjects)-1 {
-					defaultConfiguredProject = project.projectService.findDefaultConfiguredProject(s)
-				}
-				if defaultConfiguredProject == project {
-					return project
-				}
-				if firstNonSourceOfProjectReferenceRedirect == nil {
-					firstNonSourceOfProjectReferenceRedirect = project
-				}
-				// }
-				if firstConfiguredProject == nil {
-					firstConfiguredProject = project
-				}
-			} else if firstInferredProject == nil && project.kind == KindInferred {
-				firstInferredProject = project
-			}
-		}
-		if defaultConfiguredProject != nil {
-			return defaultConfiguredProject
-		}
-		if firstNonSourceOfProjectReferenceRedirect != nil {
-			return firstNonSourceOfProjectReferenceRedirect
-		}
-		if firstConfiguredProject != nil {
-			return firstConfiguredProject
-		}
-		if firstInferredProject != nil {
-			return firstInferredProject
-		}
-		panic("no project found")
-	}
-}
diff --git a/internal/project/service.go b/internal/project/service.go
index d2ecaa2617..6fe70c8a95 100644
--- a/internal/project/service.go
+++ b/internal/project/service.go
@@ -8,8 +8,8 @@ import (
 	"github.com/microsoft/typescript-go/internal/core"
 	"github.com/microsoft/typescript-go/internal/ls"
 	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
-	"github.com/microsoft/typescript-go/internal/tsoptions"
 	"github.com/microsoft/typescript-go/internal/tspath"
+	"github.com/microsoft/typescript-go/internal/vfs"
 )
 
 type projectLoadKind int
@@ -28,11 +28,12 @@ type assignProjectResult struct {
 }
 
 type ServiceOptions struct {
-	DefaultLibraryPath string
-	Logger             *Logger
-	PositionEncoding   lsproto.PositionEncodingKind
+	Logger           *Logger
+	PositionEncoding lsproto.PositionEncodingKind
 }
 
+var _ ProjectHost = (*Service)(nil)
+
 type Service struct {
 	host                ServiceHost
 	options             ServiceOptions
@@ -46,7 +47,7 @@ type Service struct {
 	// if it exists
 	inferredProjects []*Project
 
-	documentRegistry *documentRegistry
+	documentRegistry *DocumentRegistry
 	scriptInfosMu    sync.RWMutex
 	scriptInfos      map[tspath.Path]*ScriptInfo
 	openFiles        map[tspath.Path]string // values are projectRootPath, if provided
@@ -59,7 +60,7 @@ type Service struct {
 
 func NewService(host ServiceHost, options ServiceOptions) *Service {
 	options.Logger.Info(fmt.Sprintf("currentDirectory:: %s useCaseSensitiveFileNames:: %t", host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames()))
-	options.Logger.Info("libs Location:: " + options.DefaultLibraryPath)
+	options.Logger.Info("libs Location:: " + host.DefaultLibraryPath())
 	return &Service{
 		host:    host,
 		options: options,
@@ -70,10 +71,12 @@ func NewService(host ServiceHost, options ServiceOptions) *Service {
 
 		configuredProjects: make(map[tspath.Path]*Project),
 
-		documentRegistry: newDocumentRegistry(tspath.ComparePathsOptions{
-			UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
-			CurrentDirectory:          host.GetCurrentDirectory(),
-		}),
+		documentRegistry: &DocumentRegistry{
+			Options: tspath.ComparePathsOptions{
+				UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
+				CurrentDirectory:          host.GetCurrentDirectory(),
+			},
+		},
 		scriptInfos:                 make(map[tspath.Path]*ScriptInfo),
 		openFiles:                   make(map[tspath.Path]string),
 		filenameToScriptInfoVersion: make(map[tspath.Path]int),
@@ -81,6 +84,46 @@ func NewService(host ServiceHost, options ServiceOptions) *Service {
 	}
 }
 
+// GetCurrentDirectory implements ProjectHost.
+func (s *Service) GetCurrentDirectory() string {
+	return s.host.GetCurrentDirectory()
+}
+
+// Log implements ProjectHost.
+func (s *Service) Log(msg string) {
+	s.options.Logger.Info(msg)
+}
+
+// NewLine implements ProjectHost.
+func (s *Service) NewLine() string {
+	return s.host.NewLine()
+}
+
+// DefaultLibraryPath implements ProjectHost.
+func (s *Service) DefaultLibraryPath() string {
+	return s.host.DefaultLibraryPath()
+}
+
+// DocumentRegistry implements ProjectHost.
+func (s *Service) DocumentRegistry() *DocumentRegistry {
+	return s.documentRegistry
+}
+
+// FS implements ProjectHost.
+func (s *Service) FS() vfs.FS {
+	return s.host.FS()
+}
+
+// GetOrCreateScriptInfoForFile implements ProjectHost.
+func (s *Service) GetOrCreateScriptInfoForFile(fileName string, path tspath.Path, scriptKind core.ScriptKind) *ScriptInfo {
+	return s.getOrCreateScriptInfoNotOpenedByClient(fileName, path, scriptKind)
+}
+
+// PositionEncoding implements ProjectHost.
+func (s *Service) PositionEncoding() lsproto.PositionEncodingKind {
+	return s.options.PositionEncoding
+}
+
 func (s *Service) Projects() []*Project {
 	projects := make([]*Project, 0, len(s.configuredProjects)+len(s.inferredProjects))
 	for _, project := range s.configuredProjects {
@@ -91,10 +134,10 @@ func (s *Service) Projects() []*Project {
 }
 
 func (s *Service) GetScriptInfo(fileName string) *ScriptInfo {
-	return s.getScriptInfo(s.toPath(fileName))
+	return s.GetScriptInfoByPath(s.toPath(fileName))
 }
 
-func (s *Service) getScriptInfo(path tspath.Path) *ScriptInfo {
+func (s *Service) GetScriptInfoByPath(path tspath.Path) *ScriptInfo {
 	s.scriptInfosMu.RLock()
 	defer s.scriptInfosMu.RUnlock()
 	if info, ok := s.scriptInfos[path]; ok && !info.deferredDelete {
@@ -105,7 +148,7 @@ func (s *Service) getScriptInfo(path tspath.Path) *ScriptInfo {
 
 func (s *Service) OpenFile(fileName string, fileContent string, scriptKind core.ScriptKind, projectRootPath string) {
 	path := s.toPath(fileName)
-	existing := s.getScriptInfo(path)
+	existing := s.GetScriptInfoByPath(path)
 	info := s.getOrCreateOpenScriptInfo(fileName, path, fileContent, scriptKind, projectRootPath)
 	if existing == nil && info != nil && !info.isDynamic {
 		// !!!
@@ -118,7 +161,7 @@ func (s *Service) OpenFile(fileName string, fileContent string, scriptKind core.
 
 func (s *Service) ChangeFile(fileName string, changes []ls.TextChange) {
 	path := s.toPath(fileName)
-	info := s.getScriptInfo(path)
+	info := s.GetScriptInfoByPath(path)
 	if info == nil {
 		panic("scriptInfo not found")
 	}
@@ -126,7 +169,7 @@ func (s *Service) ChangeFile(fileName string, changes []ls.TextChange) {
 }
 
 func (s *Service) CloseFile(fileName string) {
-	if info := s.getScriptInfo(s.toPath(fileName)); info != nil {
+	if info := s.GetScriptInfoByPath(s.toPath(fileName)); info != nil {
 		fileExists := !info.isDynamic && s.host.FS().FileExists(info.fileName)
 		info.close(fileExists)
 		for _, project := range info.containingProjects {
@@ -142,21 +185,21 @@ func (s *Service) CloseFile(fileName string) {
 }
 
 func (s *Service) MarkFileSaved(fileName string, text string) {
-	if info := s.getScriptInfo(s.toPath(fileName)); info != nil {
-		info.setTextFromDisk(text)
+	if info := s.GetScriptInfoByPath(s.toPath(fileName)); info != nil {
+		info.SetTextFromDisk(text)
 	}
 }
 
 func (s *Service) EnsureDefaultProjectForFile(fileName string) (*ScriptInfo, *Project) {
 	path := s.toPath(fileName)
-	if info := s.getScriptInfo(path); info != nil && !info.isOrphan() {
-		if project := info.getDefaultProject(); project != nil {
+	if info := s.GetScriptInfoByPath(path); info != nil && !info.isOrphan() {
+		if project := s.getDefaultProjectForScript(info); project != nil {
 			return info, project
 		}
 	}
 	s.ensureProjectStructureUpToDate()
-	if info := s.getScriptInfo(path); info != nil {
-		if project := info.getDefaultProject(); project != nil {
+	if info := s.GetScriptInfoByPath(path); info != nil {
+		if project := s.getDefaultProjectForScript(info); project != nil {
 			return info, project
 		}
 	}
@@ -164,7 +207,7 @@ func (s *Service) EnsureDefaultProjectForFile(fileName string) (*ScriptInfo, *Pr
 }
 
 func (s *Service) Close() {
-	// !!!
+	s.options.Logger.Close()
 }
 
 // SourceFileCount should only be used for testing.
@@ -186,11 +229,11 @@ func (s *Service) ensureProjectStructureUpToDate() {
 }
 
 func (s *Service) ensureProjectForOpenFiles() {
-	s.log("Before ensureProjectForOpenFiles:")
+	s.Log("Before ensureProjectForOpenFiles:")
 	s.printProjects()
 
 	for filePath, projectRootPath := range s.openFiles {
-		info := s.getScriptInfo(filePath)
+		info := s.GetScriptInfoByPath(filePath)
 		if info == nil {
 			panic("scriptInfo not found for open file")
 		}
@@ -204,7 +247,7 @@ func (s *Service) ensureProjectForOpenFiles() {
 		project.updateIfDirty()
 	}
 
-	s.log("After ensureProjectForOpenFiles:")
+	s.Log("After ensureProjectForOpenFiles:")
 	s.printProjects()
 }
 
@@ -249,7 +292,7 @@ func (s *Service) deleteScriptInfo(info *ScriptInfo) {
 	// !!! closeSourceMapFileWatcher
 }
 
-func (s *Service) recordSymlink(info *ScriptInfo) {
+func (s *Service) OnDiscoveredSymlink(info *ScriptInfo) {
 	s.realpathToScriptInfosMu.Lock()
 	defer s.realpathToScriptInfosMu.Unlock()
 	if scriptInfos, ok := s.realpathToScriptInfos[info.realpath]; ok {
@@ -308,9 +351,9 @@ func (s *Service) getOrCreateScriptInfoWorker(fileName string, path tspath.Path,
 			}
 		}
 
-		info = newScriptInfo(fileName, path, scriptKind)
+		info = NewScriptInfo(fileName, path, scriptKind)
 		if fromDisk {
-			info.setTextFromDisk(fileContent)
+			info.SetTextFromDisk(fileContent)
 		}
 
 		s.scriptInfosMu.Lock()
@@ -513,6 +556,67 @@ func (s *Service) getOrCreateInferredProjectForProjectRootPath(info *ScriptInfo,
 	return bestMatch
 }
 
+func (s *Service) getDefaultProjectForScript(scriptInfo *ScriptInfo) *Project {
+	switch len(scriptInfo.containingProjects) {
+	case 0:
+		panic("scriptInfo must be attached to a project before calling getDefaultProject")
+	case 1:
+		project := scriptInfo.containingProjects[0]
+		if project.deferredClose || project.kind == KindAutoImportProvider || project.kind == KindAuxiliary {
+			panic("scriptInfo must be attached to a non-background project before calling getDefaultProject")
+		}
+		return project
+	default:
+		// If this file belongs to multiple projects, below is the order in which default project is used
+		// - first external project
+		// - for open script info, its default configured project during opening is default if info is part of it
+		// - first configured project of which script info is not a source of project reference redirect
+		// - first configured project
+		// - first inferred project
+		var firstConfiguredProject *Project
+		var firstInferredProject *Project
+		var firstNonSourceOfProjectReferenceRedirect *Project
+		var defaultConfiguredProject *Project
+
+		for index, project := range scriptInfo.containingProjects {
+			if project.kind == KindConfigured {
+				if project.deferredClose {
+					continue
+				}
+				// !!! if !project.isSourceOfProjectReferenceRedirect(scriptInfo.fileName) {
+				if defaultConfiguredProject == nil && index != len(scriptInfo.containingProjects)-1 {
+					defaultConfiguredProject = s.findDefaultConfiguredProject(scriptInfo)
+				}
+				if defaultConfiguredProject == project {
+					return project
+				}
+				if firstNonSourceOfProjectReferenceRedirect == nil {
+					firstNonSourceOfProjectReferenceRedirect = project
+				}
+				// }
+				if firstConfiguredProject == nil {
+					firstConfiguredProject = project
+				}
+			} else if firstInferredProject == nil && project.kind == KindInferred {
+				firstInferredProject = project
+			}
+		}
+		if defaultConfiguredProject != nil {
+			return defaultConfiguredProject
+		}
+		if firstNonSourceOfProjectReferenceRedirect != nil {
+			return firstNonSourceOfProjectReferenceRedirect
+		}
+		if firstConfiguredProject != nil {
+			return firstConfiguredProject
+		}
+		if firstInferredProject != nil {
+			return firstInferredProject
+		}
+		panic("no project found")
+	}
+}
+
 func (s *Service) createInferredProject(currentDirectory string, projectRootPath tspath.Path) *Project {
 	// !!!
 	compilerOptions := core.CompilerOptions{}
@@ -526,62 +630,9 @@ func (s *Service) toPath(fileName string) tspath.Path {
 }
 
 func (s *Service) loadConfiguredProject(project *Project) {
-	if configFileContent, ok := s.host.FS().ReadFile(project.configFileName); ok {
-		configDir := tspath.GetDirectoryPath(project.configFileName)
-		tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(project.configFileName, s.toPath(project.configFileName), configFileContent)
-		parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
-			tsConfigSourceFile,
-			s.host,
-			configDir,
-			nil, /*existingOptions*/
-			project.configFileName,
-			nil, /*resolutionStack*/
-			nil, /*extraFileExtensions*/
-			nil, /*extendedConfigCache*/
-		)
-
-		s.logf("Config: %s : %s",
-			project.configFileName,
-			core.Must(core.StringifyJson(map[string]any{
-				"rootNames":         parsedCommandLine.FileNames(),
-				"options":           parsedCommandLine.CompilerOptions(),
-				"projectReferences": parsedCommandLine.ProjectReferences(),
-			}, "    ", "  ")),
-		)
-
-		newRootScriptInfos := make(map[tspath.Path]struct{}, len(parsedCommandLine.FileNames()))
-		project.compilerOptions = parsedCommandLine.CompilerOptions()
-		for _, file := range parsedCommandLine.FileNames() {
-			scriptKind := project.getScriptKind(file)
-			scriptInfo := s.getOrCreateScriptInfoNotOpenedByClient(file, s.toPath(file), scriptKind)
-			newRootScriptInfos[scriptInfo.path] = struct{}{}
-			if _, isRoot := project.rootFileNames.Get(scriptInfo.path); !isRoot {
-				project.addRoot(scriptInfo)
-				if scriptInfo.isOpen {
-					// !!!
-					// s.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo)
-				}
-			} else {
-				project.rootFileNames.Set(scriptInfo.path, file)
-			}
-		}
-
-		if project.rootFileNames.Size() > len(parsedCommandLine.FileNames()) {
-			for root := range project.rootFileNames.Keys() {
-				if _, ok := newRootScriptInfos[root]; !ok {
-					if info := s.getScriptInfo(root); info != nil {
-						project.removeFile(info, true /*fileExists*/, true /*detachFromProject*/)
-					} else {
-						project.rootFileNames.Delete(root)
-					}
-				}
-			}
-		}
-	} else {
-		project.compilerOptions = &core.CompilerOptions{}
+	if err := project.LoadConfig(); err != nil {
+		panic(fmt.Errorf("failed to load project %q: %w", project.configFileName, err))
 	}
-
-	project.markAsDirty()
 }
 
 func (s *Service) printProjects() {
@@ -591,25 +642,21 @@ func (s *Service) printProjects() {
 
 	s.options.Logger.StartGroup()
 	for _, project := range s.configuredProjects {
-		s.log(project.print(false /*writeFileNames*/, false /*writeFileExpanation*/, false /*writeFileVersionAndText*/))
+		s.Log(project.print(false /*writeFileNames*/, false /*writeFileExpanation*/, false /*writeFileVersionAndText*/))
 	}
 	for _, project := range s.inferredProjects {
-		s.log(project.print(false /*writeFileNames*/, false /*writeFileExpanation*/, false /*writeFileVersionAndText*/))
+		s.Log(project.print(false /*writeFileNames*/, false /*writeFileExpanation*/, false /*writeFileVersionAndText*/))
 	}
 
-	s.log("Open files: ")
+	s.Log("Open files: ")
 	for path, projectRootPath := range s.openFiles {
-		info := s.getScriptInfo(path)
+		info := s.GetScriptInfoByPath(path)
 		s.logf("\tFileName: %s ProjectRootPath: %s", info.fileName, projectRootPath)
-		s.log("\t\tProjects: " + strings.Join(core.Map(info.containingProjects, func(project *Project) string { return project.name }), ", "))
+		s.Log("\t\tProjects: " + strings.Join(core.Map(info.containingProjects, func(project *Project) string { return project.name }), ", "))
 	}
 	s.options.Logger.EndGroup()
 }
 
-func (s *Service) log(msg string) {
-	s.options.Logger.Info(msg)
-}
-
 func (s *Service) logf(format string, args ...any) {
-	s.log(fmt.Sprintf(format, args...))
+	s.Log(fmt.Sprintf(format, args...))
 }
diff --git a/internal/project/service_test.go b/internal/project/service_test.go
index 25fa07f83d..90952bb5a7 100644
--- a/internal/project/service_test.go
+++ b/internal/project/service_test.go
@@ -245,7 +245,7 @@ func newProjectServiceHost(files map[string]string) *projectServiceHost {
 		fs:                 fs,
 		defaultLibraryPath: bundled.LibPath(),
 	}
-	host.logger = project.NewLogger([]io.Writer{&host.output}, project.LogLevelVerbose)
+	host.logger = project.NewLogger([]io.Writer{&host.output}, "", project.LogLevelVerbose)
 	return host
 }
 
diff --git a/package-lock.json b/package-lock.json
index ae2a6f9a5b..cbb54a532b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
             "hasInstallScript": true,
             "license": "Apache-2.0",
             "workspaces": [
-                "./_extension"
+                "./_extension",
+                "./_packages/*"
             ],
             "devDependencies": {
                 "@types/node": "latest",
@@ -39,6 +40,21 @@
                 "vscode": "^1.91.0"
             }
         },
+        "_packages/api": {
+            "name": "@typescript/api",
+            "version": "1.0.0",
+            "dependencies": {
+                "@typescript/ast": "1.0.0",
+                "libsyncrpc": "github:microsoft/libsyncrpc#bb02d84"
+            },
+            "devDependencies": {
+                "tinybench": "^3.1.1"
+            }
+        },
+        "_packages/ast": {
+            "name": "@typescript/ast",
+            "version": "1.0.0"
+        },
         "node_modules/@apidevtools/json-schema-ref-parser": {
             "version": "11.9.0",
             "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.0.tgz",
@@ -251,6 +267,14 @@
             "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==",
             "dev": true
         },
+        "node_modules/@typescript/api": {
+            "resolved": "_packages/api",
+            "link": true
+        },
+        "node_modules/@typescript/ast": {
+            "resolved": "_packages/ast",
+            "link": true
+        },
         "node_modules/ansi-regex": {
             "version": "6.1.0",
             "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@@ -705,6 +729,14 @@
                 "@types/json-schema": "^7.0.15"
             }
         },
+        "node_modules/libsyncrpc": {
+            "version": "0.0.0",
+            "resolved": "git+ssh://git@github.com/microsoft/libsyncrpc.git#bb02d8458da5b6dda1507b3390ea7cca80b99ca3",
+            "license": "MIT",
+            "engines": {
+                "node": ">= 10.16.0 < 11 || >= 11.8.0 < 12 || >= 12.0.0"
+            }
+        },
         "node_modules/lru-cache": {
             "version": "10.4.3",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -1037,6 +1069,16 @@
                 "node": ">=8.0.0"
             }
         },
+        "node_modules/tinybench": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-3.1.1.tgz",
+            "integrity": "sha512-74pmf47HY/bHqamcCMGris+1AtGGsqTZ3Hc/UK4QvSmRuf/9PIF9753+c8XBh7JfX2r9KeZtVjOYjd6vFpc0qQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
         "node_modules/typescript": {
             "version": "5.7.3",
             "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
@@ -1430,6 +1472,17 @@
             "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==",
             "dev": true
         },
+        "@typescript/api": {
+            "version": "file:_packages/api",
+            "requires": {
+                "@typescript/ast": "1.0.0",
+                "libsyncrpc": "github:microsoft/libsyncrpc#bb02d84",
+                "tinybench": "^3.1.1"
+            }
+        },
+        "@typescript/ast": {
+            "version": "file:_packages/ast"
+        },
         "ansi-regex": {
             "version": "6.1.0",
             "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@@ -1756,6 +1809,10 @@
                 "@types/json-schema": "^7.0.15"
             }
         },
+        "libsyncrpc": {
+            "version": "git+ssh://git@github.com/microsoft/libsyncrpc.git#bb02d8458da5b6dda1507b3390ea7cca80b99ca3",
+            "from": "libsyncrpc@github:microsoft/libsyncrpc#bb02d84"
+        },
         "lru-cache": {
             "version": "10.4.3",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -1980,6 +2037,12 @@
                 "wordwrapjs": "^4.0.0"
             }
         },
+        "tinybench": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-3.1.1.tgz",
+            "integrity": "sha512-74pmf47HY/bHqamcCMGris+1AtGGsqTZ3Hc/UK4QvSmRuf/9PIF9753+c8XBh7JfX2r9KeZtVjOYjd6vFpc0qQ==",
+            "dev": true
+        },
         "typescript": {
             "version": "5.7.3",
             "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
diff --git a/package.json b/package.json
index 7130090b01..5cd212c942 100644
--- a/package.json
+++ b/package.json
@@ -37,11 +37,14 @@
         "build": "hereby build",
         "build:watch": "hereby build:watch",
         "test": "hereby test",
+        "api:build": "npm run -w @typescript/api build",
         "extension:build": "npm run -w typescript-lsp build",
-        "extension:watch": "npm run -w typescript-lsp watch"
+        "extension:watch": "npm run -w typescript-lsp watch",
+        "node": "node --no-warnings --conditions @typescript/source"
     },
     "workspaces": [
-        "./_extension"
+        "./_extension",
+        "./_packages/*"
     ],
     "packageManager": "npm@8.19.4",
     "volta": {
diff --git a/testdata/baselines/reference/api/encodeSourceFile.txt b/testdata/baselines/reference/api/encodeSourceFile.txt
new file mode 100644
index 0000000000..88eb7adb80
--- /dev/null
+++ b/testdata/baselines/reference/api/encodeSourceFile.txt
@@ -0,0 +1,30 @@
+KindSourceFile [0, 89), i=1, next=0
+  NodeList [0, 89), i=2, next=0
+    KindImportDeclaration [0, 26), i=3, next=10
+      KindImportClause [6, 14), i=4, next=9
+        KindNamedImports [6, 14), i=5, next=0
+          NodeList [8, 12), i=6, next=0
+            KindImportSpecifier [8, 12), i=7, next=0
+              KindIdentifier "bar" [8, 12), i=8, next=0
+      KindStringLiteral "bar" [19, 25), i=9, next=0
+    KindFunctionDeclaration [26, 82), i=10, next=28
+      NodeList [26, 33), i=11, next=13
+        KindExportKeyword [26, 33), i=12, next=0
+      KindIdentifier "foo" [42, 46), i=13, next=14
+      NodeList [47, 51), i=14, next=19
+        KindTypeParameter [47, 48), i=15, next=17
+          KindIdentifier "T" [47, 48), i=16, next=0
+        KindTypeParameter [49, 51), i=17, next=0
+          KindIdentifier "U" [49, 51), i=18, next=0
+      NodeList [53, 73), i=19, next=26
+        KindParameter [53, 62), i=20, next=23
+          KindIdentifier "a" [53, 54), i=21, next=22
+          KindStringKeyword [55, 62), i=22, next=0
+        KindParameter [63, 73), i=23, next=0
+          KindIdentifier "b" [63, 65), i=24, next=25
+          KindStringKeyword [66, 73), i=25, next=0
+      KindAnyKeyword [75, 79), i=26, next=27
+      KindBlock [79, 82), i=27, next=0
+    KindExpressionStatement [82, 89), i=28, next=0
+      KindCallExpression [82, 88), i=29, next=0
+        KindIdentifier "foo" [82, 86), i=30, next=0