Files
opencode/packages/codemode/test/new-expression.test.ts
T

83 lines
3.7 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "../src/index.js"
// `new` is supported syntax; only the callee decides whether construction succeeds. A callee without
// construction support is a TypeError naming it, like JS, rather than an unsupported-syntax diagnostic
// that would suggest `new` itself is unavailable.
const tools = {
echo: Tool.make({
description: "Echo",
input: Schema.Struct({}),
output: Schema.Struct({}),
execute: () => Effect.succeed({}),
}),
}
const run = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools }))
const value = async (code: string) => {
const result = await run(code)
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const error = async (code: string) => {
const result = await run(code)
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
return result.error
}
describe("new on a non-constructible callee", () => {
test("built-in functions without construction point at the plain call", async () => {
// Number is a real constructor in JS, so the message must not claim otherwise.
const failure = await error(`return new Number(42)`)
expect(failure.kind).toBe("ExecutionFailure")
expect(failure.message).toStartWith(
"TypeError: new Number(...) is not supported; call Number(...) without new instead.",
)
expect(failure.suggestions).toBeUndefined()
expect((await error(`return new String("a")`)).message).toStartWith("TypeError: new String(...) is not supported")
expect((await error(`return new Math.abs(1)`)).message).toStartWith(
"TypeError: new Math.abs(...) is not supported; call Math.abs(...) without new instead.",
)
})
test("non-callable values are not constructors", async () => {
expect((await error(`return new tools.echo()`)).message).toStartWith("TypeError: tools.echo is not a constructor.")
expect((await error(`return new (1)()`)).message).toStartWith("TypeError: The called value is not a constructor.")
expect((await error(`const Date = 5; return new Date()`)).message).toStartWith(
"TypeError: Date is not a constructor.",
)
})
test("user-defined functions explain the documented gap", async () => {
const failure = await error(`function Point(x) { return { x } }; return new Point(1)`)
expect(failure.message).toStartWith(
"TypeError: Point cannot be constructed: user-defined constructors and classes are not supported. Call it as a function that returns a plain object instead.",
)
expect((await error(`const make = () => ({}); return new make()`)).message).toStartWith(
"TypeError: make cannot be constructed",
)
})
test("the failure is a catchable TypeError", async () => {
expect(
await value(`
try { new Number(1) } catch (error) { return [error.name, error instanceof TypeError] }
`),
).toEqual(["TypeError", true])
})
test("an undeclared callee still fails as an unknown identifier", async () => {
expect((await error(`return new Function("return 1")`)).message).toContain("Function")
expect((await error(`return new Function("return 1")`)).message).not.toContain("not a constructor")
})
test("classes remain unsupported syntax", async () => {
const failure = await error(`class A {}; return new A()`)
expect(failure.kind).toBe("UnsupportedSyntax")
expect(failure.message).toStartWith(
"SyntaxError: Syntax 'ClassDeclaration' is not supported. This is a restricted JavaScript-like language. Supported: ",
)
expect(failure.message).toContain("Unsupported: classes, getters/setters, BigInt, and custom Symbols.")
})
})