mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-12 05:04:43 +00:00
Replace record-keyed tools and the separate namespace-description map with an OpenAI-style array of named tools and namespaces. Namespace descriptions are optional, namespaces may nest, and duplicate or dotted names are rejected.
534 lines
16 KiB
TypeScript
534 lines
16 KiB
TypeScript
/*
|
|
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
|
|
* - test/language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js
|
|
* - test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup.js
|
|
* - test/language/statements/for-await-of/async-func-dstr-let-ary-ptrn-elem-id-iter-val.js
|
|
* - test/language/statements/for-await-of/async-func-decl-dstr-array-rest-after-element.js
|
|
* - test/language/statements/for-await-of/iterator-close-non-throw-get-method-is-null.js
|
|
* - test/language/statements/for-await-of/iterator-close-non-throw-get-method-non-callable.js
|
|
* - test/language/statements/for-await-of/iterator-close-throw-get-method-non-callable.js
|
|
*
|
|
* Copyright (C) 2019 André Bargull. All rights reserved.
|
|
* Copyright (C) 2020 Alexey Shvayka. All rights reserved.
|
|
* Test262 portions are governed by the BSD license in LICENSE.test262.
|
|
*/
|
|
import { describe, expect, test } from "bun:test"
|
|
import { Effect } from "effect"
|
|
import { CodeMode } from "../src/index.js"
|
|
|
|
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code }))
|
|
|
|
const value = async (code: string) => {
|
|
const result = await execute(code)
|
|
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
|
|
return result.value
|
|
}
|
|
|
|
describe("Test262 for-await-of adaptations", () => {
|
|
test("awaits promise and plain values from a synchronous array", async () => {
|
|
expect(
|
|
await value(`
|
|
const values = []
|
|
for await (const item of [Promise.resolve(1), 2, new Promise((resolve) => resolve(3))]) {
|
|
values.push(item)
|
|
}
|
|
return values
|
|
`),
|
|
).toEqual([1, 2, 3])
|
|
})
|
|
|
|
test("defers the body even for an already-resolved or plain value", async () => {
|
|
expect(
|
|
await value(`
|
|
const events = []
|
|
const before = Promise.resolve().then(() => events.push("before"))
|
|
for await (const item of [Promise.resolve(1), 2]) events.push("body " + item)
|
|
await before
|
|
return events
|
|
`),
|
|
).toEqual(["before", "body 1", "body 2"])
|
|
})
|
|
|
|
test("an awaited rejection exits through normal try/catch", async () => {
|
|
const result = await execute(`
|
|
const values = []
|
|
try {
|
|
for await (const item of [Promise.resolve(1), Promise.reject("stop"), Promise.resolve(3)]) {
|
|
values.push(item)
|
|
}
|
|
} catch (error) {
|
|
return [values, error]
|
|
}
|
|
return "missed"
|
|
`)
|
|
expect(result.ok).toBe(true)
|
|
if (!result.ok) return
|
|
expect(result.value).toEqual([[1], "stop"])
|
|
expect(result.warnings ?? []).toEqual([])
|
|
})
|
|
|
|
test("destructures after resolving each yielded value", async () => {
|
|
expect(
|
|
await value(`
|
|
const values = []
|
|
for await (const [first, ...rest] of [Promise.resolve([1, 2, 3])]) {
|
|
values.push(first, rest)
|
|
}
|
|
return values
|
|
`),
|
|
).toEqual([1, [2, 3]])
|
|
})
|
|
|
|
test("supports assignment targets", async () => {
|
|
expect(
|
|
await value(`
|
|
let first
|
|
let rest
|
|
for await ([first, ...rest] of [Promise.resolve([1, 2, 3])]) {}
|
|
return [first, rest]
|
|
`),
|
|
).toEqual([1, [2, 3]])
|
|
})
|
|
|
|
test("preserves fresh lexical bindings per iteration", async () => {
|
|
expect(
|
|
await value(`
|
|
const reads = []
|
|
for await (const item of [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]) {
|
|
reads.push(() => item)
|
|
}
|
|
return reads.map((read) => read())
|
|
`),
|
|
).toEqual([1, 2, 3])
|
|
})
|
|
|
|
test("supports every existing collection iterable", async () => {
|
|
expect(
|
|
await value(`
|
|
const string = []
|
|
for await (const item of "ab") string.push(item)
|
|
|
|
const set = []
|
|
for await (const item of new Set([Promise.resolve(1), 2])) set.push(item)
|
|
|
|
const map = []
|
|
for await (const [key, item] of new Map([["a", 1], ["b", 2]])) map.push(key, item)
|
|
|
|
const params = []
|
|
for await (const [key, item] of new URLSearchParams("a=1&b=2")) params.push(key, item)
|
|
return { string, set, map, params }
|
|
`),
|
|
).toEqual({ string: ["a", "b"], set: [1, 2], map: ["a", 1, "b", 2], params: ["a", "1", "b", "2"] })
|
|
})
|
|
|
|
test("preserves labeled break and continue behavior", async () => {
|
|
expect(
|
|
await value(`
|
|
const values = []
|
|
outer: for await (const item of [1, 2, 3, 4]) {
|
|
if (item === 2) continue outer
|
|
if (item === 4) break outer
|
|
values.push(item)
|
|
}
|
|
return values
|
|
`),
|
|
).toEqual([1, 3])
|
|
})
|
|
|
|
test("drives a custom async iterator sequentially", async () => {
|
|
expect(
|
|
await value(`
|
|
let index = 0
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
async next() {
|
|
index += 1
|
|
if (index > 3) return { done: true }
|
|
return { done: false, value: index }
|
|
},
|
|
}
|
|
const values = []
|
|
for await (const item of iterator) values.push(item)
|
|
return values
|
|
`),
|
|
).toEqual([1, 2, 3])
|
|
})
|
|
|
|
test("leaves async iterator values under the iterator's control", async () => {
|
|
expect(
|
|
await value(`
|
|
let done = false
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => done ? { done: true } : (done = true, { done: false, value: Promise.resolve(1) }),
|
|
}
|
|
for await (const item of iterator) return [item instanceof Promise, await item]
|
|
`),
|
|
).toEqual([true, 1])
|
|
})
|
|
|
|
test("awaits synchronous results from an async iterator before the body", async () => {
|
|
expect(
|
|
await value(`
|
|
const events = ["pre"]
|
|
const ticks = Promise.resolve()
|
|
.then(() => events.push("tick 1"))
|
|
.then(() => events.push("tick 2"))
|
|
let done = false
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: () => done ? { done: true } : (done = true, { done: false, value: Promise.resolve(1) }),
|
|
}
|
|
for await (const item of iterator) events.push(item instanceof Promise ? "loop" : "adopted")
|
|
events.push("post")
|
|
await ticks
|
|
return events
|
|
`),
|
|
).toEqual(["pre", "tick 1", "loop", "tick 2", "post"])
|
|
})
|
|
|
|
test("falls back to a custom synchronous iterator", async () => {
|
|
expect(
|
|
await value(`
|
|
let index = 0
|
|
const iterator = {
|
|
[Symbol.iterator]: () => iterator,
|
|
next() {
|
|
index += 1
|
|
return index > 2 ? { done: true } : { done: false, value: Promise.resolve(index) }
|
|
},
|
|
}
|
|
const values = []
|
|
for await (const item of iterator) values.push(item)
|
|
return values
|
|
`),
|
|
).toEqual([1, 2])
|
|
})
|
|
|
|
test("adopts terminal and close values from synchronous iterators", async () => {
|
|
expect(
|
|
await value(`
|
|
const terminal = {
|
|
[Symbol.iterator]: () => terminal,
|
|
next: () => ({ done: true, value: Promise.reject("terminal") }),
|
|
}
|
|
let terminalError
|
|
try {
|
|
for await (const item of terminal) {}
|
|
} catch (error) {
|
|
terminalError = error
|
|
}
|
|
|
|
const closing = {
|
|
[Symbol.iterator]: () => closing,
|
|
next: () => ({ done: false, value: 1 }),
|
|
return: () => ({ done: true, value: Promise.reject("close") }),
|
|
}
|
|
let closeError
|
|
try {
|
|
for await (const item of closing) break
|
|
} catch (error) {
|
|
closeError = error
|
|
}
|
|
return [terminalError, closeError]
|
|
`),
|
|
).toEqual(["terminal", "close"])
|
|
})
|
|
|
|
test("captures the next method when acquiring the iterator", async () => {
|
|
expect(
|
|
await value(`
|
|
let count = 0
|
|
const next = () => {
|
|
count += 1
|
|
if (count === 1) iterator.next = () => ({ done: true })
|
|
return count > 2 ? { done: true } : { done: false, value: count }
|
|
}
|
|
const iterator = { [Symbol.iterator]: () => iterator, next }
|
|
const values = []
|
|
for await (const item of iterator) values.push(item)
|
|
return values
|
|
`),
|
|
).toEqual([1, 2])
|
|
})
|
|
|
|
test("captures synchronous iterator result fields before suspending", async () => {
|
|
expect(
|
|
await value(`
|
|
let count = 0
|
|
const result = { done: false, value: 1 }
|
|
const iterator = {
|
|
[Symbol.iterator]: () => iterator,
|
|
next() {
|
|
count += 1
|
|
if (count > 1) return { done: true }
|
|
Promise.resolve().then(() => {
|
|
result.done = true
|
|
result.value = 2
|
|
})
|
|
return result
|
|
},
|
|
}
|
|
const values = []
|
|
for await (const item of iterator) values.push(item)
|
|
return values
|
|
`),
|
|
).toEqual([1])
|
|
})
|
|
|
|
test("preserves the async close turn for a sync iterator without return", async () => {
|
|
expect(
|
|
await value(`
|
|
const events = []
|
|
const iterator = {
|
|
[Symbol.iterator]: () => iterator,
|
|
next: () => ({ done: false, value: 1 }),
|
|
}
|
|
for await (const item of iterator) {
|
|
Promise.resolve().then(() => events.push("reaction"))
|
|
break
|
|
}
|
|
events.push("after")
|
|
return events
|
|
`),
|
|
).toEqual(["reaction", "after"])
|
|
})
|
|
|
|
test("defers synchronous iterator protocol errors", async () => {
|
|
expect(
|
|
await value(`
|
|
const events = []
|
|
const throwing = {
|
|
[Symbol.iterator]: () => throwing,
|
|
next() {
|
|
Promise.resolve().then(() => events.push("next reaction"))
|
|
throw "next"
|
|
},
|
|
}
|
|
try {
|
|
for await (const item of throwing) {}
|
|
} catch (error) {
|
|
events.push("next catch")
|
|
}
|
|
|
|
const malformed = {
|
|
[Symbol.iterator]: () => malformed,
|
|
next() {
|
|
Promise.resolve().then(() => events.push("result reaction"))
|
|
return 1
|
|
},
|
|
}
|
|
try {
|
|
for await (const item of malformed) {}
|
|
} catch (error) {
|
|
events.push("result catch")
|
|
}
|
|
|
|
const closing = {
|
|
[Symbol.iterator]: () => closing,
|
|
next: () => ({ done: false, value: 1 }),
|
|
return() {
|
|
Promise.resolve().then(() => events.push("return reaction"))
|
|
throw "return"
|
|
},
|
|
}
|
|
try {
|
|
for await (const item of closing) break
|
|
} catch (error) {
|
|
events.push("return catch")
|
|
}
|
|
return events
|
|
`),
|
|
).toEqual(["next reaction", "next catch", "result reaction", "result catch", "return reaction", "return catch"])
|
|
})
|
|
|
|
test("prefers Symbol.asyncIterator over Symbol.iterator", async () => {
|
|
expect(
|
|
await value(`
|
|
let done = false
|
|
const asyncIterator = {
|
|
next: async () => done ? { done: true } : (done = true, { done: false, value: "async" }),
|
|
}
|
|
const syncIterator = { next: () => ({ done: true }) }
|
|
const iterable = {
|
|
[Symbol.asyncIterator]: () => asyncIterator,
|
|
[Symbol.iterator]: () => syncIterator,
|
|
}
|
|
const values = []
|
|
for await (const item of iterable) values.push(item)
|
|
return values
|
|
`),
|
|
).toEqual(["async"])
|
|
})
|
|
|
|
test("closes a custom iterator on abrupt loop completion", async () => {
|
|
expect(
|
|
await value(`
|
|
let closed = 0
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => ({ done: false, value: 1 }),
|
|
return: async () => (closed += 1, { done: true }),
|
|
}
|
|
for await (const item of iterator) break
|
|
try {
|
|
for await (const item of iterator) throw "stop"
|
|
} catch (error) {}
|
|
return closed
|
|
`),
|
|
).toBe(2)
|
|
})
|
|
|
|
test("treats a null iterator return method as absent", async () => {
|
|
expect(
|
|
await value(`
|
|
let count = 0
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => ({ done: false, value: 1 }),
|
|
return: null,
|
|
}
|
|
for await (const item of iterator) {
|
|
count += 1
|
|
break
|
|
}
|
|
return count
|
|
`),
|
|
).toBe(1)
|
|
})
|
|
|
|
test("a non-callable return method replaces break with TypeError", async () => {
|
|
expect(
|
|
await value(`
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => ({ done: false, value: 1 }),
|
|
return: true,
|
|
}
|
|
try {
|
|
for await (const item of iterator) break
|
|
} catch (error) {
|
|
return error.name
|
|
}
|
|
return "missed"
|
|
`),
|
|
).toBe("TypeError")
|
|
})
|
|
|
|
test("a body throw wins over a non-callable return method", async () => {
|
|
expect(
|
|
await value(`
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => ({ done: false, value: 1 }),
|
|
return: true,
|
|
}
|
|
try {
|
|
for await (const item of iterator) throw "body"
|
|
} catch (error) {
|
|
return error
|
|
}
|
|
return "missed"
|
|
`),
|
|
).toBe("body")
|
|
})
|
|
|
|
test("rejects a primitive iterator return result", async () => {
|
|
expect(
|
|
await value(`
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => ({ done: false, value: 1 }),
|
|
return: async () => null,
|
|
}
|
|
try {
|
|
for await (const item of iterator) break
|
|
} catch (error) {
|
|
return error.name
|
|
}
|
|
return "missed"
|
|
`),
|
|
).toBe("TypeError")
|
|
})
|
|
|
|
test("propagates iterator acquisition failures", async () => {
|
|
expect(
|
|
await value(`
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => { throw "acquire" },
|
|
}
|
|
try {
|
|
for await (const item of iterator) {}
|
|
} catch (error) {
|
|
return error
|
|
}
|
|
return "missed"
|
|
`),
|
|
).toBe("acquire")
|
|
})
|
|
|
|
test("rejects malformed iterator acquisition methods and results", async () => {
|
|
expect(
|
|
await value(`
|
|
const names = []
|
|
const invalid = [
|
|
{ [Symbol.asyncIterator]: true },
|
|
{ [Symbol.asyncIterator]: () => 1 },
|
|
{ [Symbol.asyncIterator]: () => ({ next: true }) },
|
|
]
|
|
for (const iterator of invalid) {
|
|
try {
|
|
for await (const item of iterator) {}
|
|
} catch (error) {
|
|
names.push(error.name)
|
|
}
|
|
}
|
|
return names
|
|
`),
|
|
).toEqual(["TypeError", "TypeError", "TypeError"])
|
|
})
|
|
|
|
test("preserves iterator protocol keys through object copies", async () => {
|
|
expect(
|
|
await value(`
|
|
let done = false
|
|
const iterable = {
|
|
plain: true,
|
|
[Symbol.asyncIterator]: () => iterable,
|
|
next: async () => done ? { done: true } : (done = true, { done: false, value: 1 }),
|
|
}
|
|
const spread = { ...iterable }
|
|
const { plain, ...rest } = iterable
|
|
const assigned = Object.assign({}, iterable)
|
|
const values = []
|
|
for await (const item of spread) values.push(item)
|
|
return [
|
|
values,
|
|
Object.hasOwn(spread, Symbol.asyncIterator),
|
|
Object.hasOwn(rest, Symbol.asyncIterator),
|
|
Object.hasOwn(assigned, Symbol.asyncIterator),
|
|
]
|
|
`),
|
|
).toEqual([[1], true, true, true])
|
|
})
|
|
|
|
test("rejects malformed iterator protocol results", async () => {
|
|
const result = await execute(`
|
|
const iterator = {
|
|
[Symbol.asyncIterator]: () => iterator,
|
|
next: async () => 1,
|
|
}
|
|
for await (const item of iterator) {}
|
|
`)
|
|
expect(result.ok).toBe(false)
|
|
if (result.ok) return
|
|
expect(result.error.message).toContain("Iterator next() result must be an object")
|
|
})
|
|
|
|
test("rejects objects without an iterator protocol method", async () => {
|
|
const result = await execute(`for await (const item of { values: [1, 2] }) {}`)
|
|
expect(result.ok).toBe(false)
|
|
if (result.ok) return
|
|
expect(result.error.message).toContain("or custom iterator value")
|
|
})
|
|
})
|