From 004583d59871912a543adc5258e559b6f90bddd8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:24:11 -0500 Subject: [PATCH] refactor(codemode): remove TypeScript stripping (#49870) Co-authored-by: rekram1-node Co-authored-by: Aiden Cline --- bun.lock | 1 - packages/codemode/interpreter-support.md | 10 +++----- packages/codemode/package.json | 9 +------ packages/codemode/script/publish.ts | 16 ++++++------ packages/codemode/src/interpreter/execute.ts | 15 +---------- .../src/interpreter/transpile.node.ts | 25 ------------------- .../src/interpreter/transpile.workerd.ts | 11 -------- packages/codemode/test/errors.test.ts | 20 +++++++++------ 8 files changed, 27 insertions(+), 80 deletions(-) delete mode 100644 packages/codemode/src/interpreter/transpile.node.ts delete mode 100644 packages/codemode/src/interpreter/transpile.workerd.ts diff --git a/bun.lock b/bun.lock index 8f88d5f76d6..ff1836f2ac4 100644 --- a/bun.lock +++ b/bun.lock @@ -205,7 +205,6 @@ "dependencies": { "acorn": "8.15.0", "effect": "catalog:", - "typescript": "catalog:", }, "devDependencies": { "@tsconfig/bun": "catalog:", diff --git a/packages/codemode/interpreter-support.md b/packages/codemode/interpreter-support.md index 260b892cb1b..2b02c502a61 100644 --- a/packages/codemode/interpreter-support.md +++ b/packages/codemode/interpreter-support.md @@ -15,8 +15,7 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262 ## Source and execution model - [x] JavaScript parsed with the latest syntax accepted by Acorn, then restricted by the interpreter allowlist. -- [x] Erasable TypeScript syntax, including type annotations, type declarations, assertions, and non-null assertions. - TypeScript is transpiled first; the emitted JavaScript must still use the supported subset. + TypeScript-only syntax is rejected rather than stripped before execution. - [x] Top-level `await` and `return` through the program's implicit async-function scope. - [x] Explicit `return`, final top-level expression as a REPL-style result, and `null` when no value is produced. - [x] The host boundary is `JSON.stringify` plus a short table. The program result and tool arguments cross as @@ -41,8 +40,6 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262 expression match can still run long on a pathological pattern; the host regex engine has no interrupt hook. - [ ] Strict-mode early errors: duplicate parameter names, `yield` as an identifier, and a trailing comma after a rest parameter are accepted unless the program itself begins with `"use strict"`. -- [ ] Valid JavaScript rejected by TypeScript transpilation before interpretation, such as `in` inside a destructuring - default in a `for...of` head and Unicode-escaped keywords. ## Values and literals @@ -494,9 +491,8 @@ Nothing is exposed unless a host provides it; extension calls are not tool calls - [x] Catchable user throws, runtime failures raised during interpreted evaluation, awaited tool failures, and awaited tool-call-limit failures; parse/compile failures, cooperative timeout, and output bounding remain outside program `catch`. -- [x] Source locations on unsupported-syntax diagnostics for JavaScript-shaped input; TypeScript transpilation may - shift them. The diagnostic names the rejected node type and attaches a short orientation to the supported - subset; this matrix is the full reference. +- [x] Source locations on unsupported-syntax diagnostics. The diagnostic names the rejected node type and attaches a + short orientation to the supported subset; this matrix is the full reference. - [x] Model-visible host failure messages and underlying causes, including output-validation errors. - [x] Caught errors do not distinguish user throws, interpreter failures, and tool failures; a program sees one Error-shaped value with `name` and `message` in `catch`, rejection handlers, and `Promise.allSettled` reasons. diff --git a/packages/codemode/package.json b/packages/codemode/package.json index 0590738fa85..6e4af0f30f0 100644 --- a/packages/codemode/package.json +++ b/packages/codemode/package.json @@ -19,12 +19,6 @@ "exports": { ".": "./src/index.ts" }, - "imports": { - "#transpile": { - "workerd": "./src/interpreter/transpile.workerd.ts", - "default": "./src/interpreter/transpile.node.ts" - } - }, "scripts": { "build": "bun run script/build.ts", "typecheck": "tsgo --noEmit", @@ -32,8 +26,7 @@ }, "dependencies": { "acorn": "8.15.0", - "effect": "catalog:", - "typescript": "catalog:" + "effect": "catalog:" }, "devDependencies": { "@tsconfig/bun": "catalog:", diff --git a/packages/codemode/script/publish.ts b/packages/codemode/script/publish.ts index 346185c5804..0e989827518 100644 --- a/packages/codemode/script/publish.ts +++ b/packages/codemode/script/publish.ts @@ -15,7 +15,7 @@ const pkg = JSON.parse(originalText) as { name: string version: string exports: Record - imports: Record> + imports?: Record> } const tarball = `${pkg.name.replace("@", "").replace("/", "-")}-${pkg.version}.tgz` const output = (value: string, types = false) => @@ -41,12 +41,14 @@ try { ] }), ) - pkg.imports = Object.fromEntries( - Object.entries(pkg.imports).map(([key, conditions]) => [ - key, - Object.fromEntries(Object.entries(conditions).map(([condition, value]) => [condition, output(value)])), - ]), - ) + if (pkg.imports) { + pkg.imports = Object.fromEntries( + Object.entries(pkg.imports).map(([key, conditions]) => [ + key, + Object.fromEntries(Object.entries(conditions).map(([condition, value]) => [condition, output(value)])), + ]), + ) + } await Bun.write("package.json", JSON.stringify(pkg, null, 2) + "\n") await rm(tarball, { force: true }) await $`bun pm pack` diff --git a/packages/codemode/src/interpreter/execute.ts b/packages/codemode/src/interpreter/execute.ts index b21c649b190..d08739cf664 100644 --- a/packages/codemode/src/interpreter/execute.ts +++ b/packages/codemode/src/interpreter/execute.ts @@ -1,14 +1,10 @@ import { parse, type Program } from "acorn" import { Cause, Effect, Scope } from "effect" -// #transpile: conditional import — full typescript on node/bun, an identity -// pass-through on workerd (the compiler is ~11 MiB and can't init there). -import { transpile } from "#transpile" import type { DataValue, Diagnostic, ResolvedExecutionLimits, Result } from "../codemode.js" import { toBoundary } from "../data.js" import { ToolRuntime } from "../tool-runtime.js" import { normalizeError } from "./errors.js" import { createBuiltins } from "./intrinsics.js" -import { PendingThrow } from "./model.js" import { Pending } from "./promises.js" import { Interpreter } from "./interpreter.js" @@ -110,16 +106,7 @@ export const executeProgram = ( } const parseProgram = (code: string): Program => { - const transpiled = transpile(`async function __codemode__() {\n${code}\n}`) - - if (transpiled.error !== undefined) { - throw new PendingThrow("SyntaxError", `Failed to parse TypeScript: ${transpiled.error}`, undefined, "ParseError") - } - - const bodyStart = transpiled.outputText.indexOf("{") + 1 - const bodyEnd = transpiled.outputText.lastIndexOf("}") - const executableCode = transpiled.outputText.slice(bodyStart, bodyEnd) - return parse(executableCode, { + return parse(code, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, diff --git a/packages/codemode/src/interpreter/transpile.node.ts b/packages/codemode/src/interpreter/transpile.node.ts deleted file mode 100644 index 95d0989af20..00000000000 --- a/packages/codemode/src/interpreter/transpile.node.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DiagnosticCategory, ModuleKind, ScriptTarget, flattenDiagnosticMessageText, transpileModule } from "typescript" - -export interface TranspileResult { - readonly outputText: string - readonly error?: string -} - -// Full TypeScript transpilation on node/bun runtimes. -export const transpile = (source: string): TranspileResult => { - const transpiled = transpileModule(source, { - reportDiagnostics: true, - compilerOptions: { - target: ScriptTarget.ESNext, - module: ModuleKind.ESNext, - }, - }) - const diagnostic = transpiled.diagnostics?.find((item) => item.category === DiagnosticCategory.Error) - if (diagnostic) { - return { - outputText: transpiled.outputText, - error: flattenDiagnosticMessageText(diagnostic.messageText, "\n"), - } - } - return { outputText: transpiled.outputText } -} diff --git a/packages/codemode/src/interpreter/transpile.workerd.ts b/packages/codemode/src/interpreter/transpile.workerd.ts deleted file mode 100644 index 6574a5bd348..00000000000 --- a/packages/codemode/src/interpreter/transpile.workerd.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface TranspileResult { - readonly outputText: string - readonly error?: string -} - -// workerd profile: the typescript compiler is ~11 MiB and probes node -// internals at module init, so codemode programs are passed through -// untranspiled. Plain-JS programs (the overwhelmingly common case) parse -// fine downstream via acorn; TypeScript-only syntax surfaces as a parse -// error from the interpreter instead of a transpile diagnostic. -export const transpile = (source: string): TranspileResult => ({ outputText: source }) diff --git a/packages/codemode/test/errors.test.ts b/packages/codemode/test/errors.test.ts index 0998a1bb24a..a89bef5dea9 100644 --- a/packages/codemode/test/errors.test.ts +++ b/packages/codemode/test/errors.test.ts @@ -15,6 +15,12 @@ const error = async (code: string) => { return result.error } +describe("source syntax", () => { + test("rejects TypeScript-only syntax", async () => { + expect((await error(`const value: number = 1; return value`)).kind).toBe("ParseError") + }) +}) + describe("error identity", () => { test("awaiting the same rejected promise twice yields the same error object", async () => { expect( @@ -101,7 +107,7 @@ describe("host errors escaping built-ins", () => { test("report the location of the call that raised them", async () => { const failure = await error(`return [1].map((n) => n.toFixed(200))`) expect(failure.kind).toBe("ExecutionFailure") - expect(failure.message).toBe("RangeError: toFixed() argument must be between 0 and 100 (line 1, col 23)") + expect(failure.message).toBe("RangeError: toFixed() argument must be between 0 and 100 (line 1, col 19)") }) test("a built-in that rejects its arguments before doing any work is located at the call", async () => { @@ -109,13 +115,13 @@ describe("host errors escaping built-ins", () => { }) test("a rejection born inside a promise the built-in created is located at the creating call", async () => { - expect((await error(`return await Promise.all(1)`)).message).toEndWith("(line 1, col 14)") - expect((await error(`return await Promise.race([])`)).message).toEndWith("(line 1, col 14)") + expect((await error(`return await Promise.all(1)`)).message).toEndWith("(line 1, col 10)") + expect((await error(`return await Promise.race([])`)).message).toEndWith("(line 1, col 10)") expect((await error(`return await Promise.all({ [Symbol.iterator]: () => ({ next: 1 }) })`)).message).toEndWith( - "(line 1, col 14)", + "(line 1, col 10)", ) expect((await error(`let p; p = Promise.resolve().then(() => p); return await p`)).message).toEndWith( - "(line 2, col 5)", + "(line 1, col 8)", ) }) @@ -128,7 +134,7 @@ describe("host errors escaping built-ins", () => { test("a failure inside a built-in called by another built-in is located at the outer call", async () => { const failure = await error(`return Array.from({ [Symbol.iterator]: () => ({ next: 1 }) })`) - expect(failure.message).toBe("TypeError: Iterator next must be a function. (line 1, col 8)") + expect(failure.message).toBe("TypeError: Iterator next must be a function. (line 1, col 4)") }) }) @@ -147,7 +153,7 @@ describe("call depth", () => { test("uncaught overflow reports the call that overflowed", async () => { const failure = await error(`const f = (n) => f(n + 1); return f(0)`) expect(failure.kind).toBe("ExecutionFailure") - expect(failure.message).toBe("RangeError: Maximum call stack size exceeded (line 1, col 18)") + expect(failure.message).toBe("RangeError: Maximum call stack size exceeded (line 1, col 14)") }) test("the limit is 10000 nested calls", async () => {