mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
refactor(codemode): remove TypeScript stripping (#49870)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
committed by
GitHub
parent
cbd911368e
commit
004583d598
@@ -205,7 +205,6 @@
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -15,7 +15,7 @@ const pkg = JSON.parse(originalText) as {
|
||||
name: string
|
||||
version: string
|
||||
exports: Record<string, string | { import: string; types: string }>
|
||||
imports: Record<string, Record<string, string>>
|
||||
imports?: Record<string, Record<string, string>>
|
||||
}
|
||||
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`
|
||||
|
||||
@@ -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 = <R>(
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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 })
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user