mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 19:09:49 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7711a4b543 | |||
| f55a0c904f |
@@ -1403,6 +1403,84 @@ class Interpreter<R> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private assignPattern(pattern: AstNode, value: unknown, node: AstNode): Effect.Effect<void, unknown, R> {
|
||||||
|
const self = this
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
if (pattern.type === "Identifier") {
|
||||||
|
self.setIdentifierValue(getString(pattern, "name"), value, pattern)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.type === "MemberExpression") {
|
||||||
|
yield* self.writeMember(pattern, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.type === "AssignmentPattern") {
|
||||||
|
const resolved = value === undefined ? yield* self.evaluateExpression(getNode(pattern, "right")) : value
|
||||||
|
yield* self.assignPattern(getNode(pattern, "left"), resolved, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.type === "ObjectPattern") {
|
||||||
|
if (value === null || typeof value !== "object" || Array.isArray(value) || isRuntimeReference(value)) {
|
||||||
|
throw new InterpreterRuntimeError(
|
||||||
|
"Object destructuring requires a data object value.",
|
||||||
|
pattern,
|
||||||
|
"InvalidDataValue",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = value as SafeObject
|
||||||
|
const consumed = new Set<string>()
|
||||||
|
for (const propertyValue of getArray(pattern, "properties")) {
|
||||||
|
const property = asNode(propertyValue, "properties")
|
||||||
|
if (property.type === "RestElement") {
|
||||||
|
const rest: SafeObject = Object.create(null) as SafeObject
|
||||||
|
for (const [key, item] of Object.entries(source)) {
|
||||||
|
if (!consumed.has(key) && !isBlockedMember(key)) rest[key] = item
|
||||||
|
}
|
||||||
|
yield* self.assignPattern(getNode(property, "argument"), rest, property)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
property.type !== "Property" ||
|
||||||
|
getBoolean(property, "computed") ||
|
||||||
|
getString(property, "kind") !== "init"
|
||||||
|
) {
|
||||||
|
throw new InterpreterRuntimeError("Only named object destructuring properties are supported.", property)
|
||||||
|
}
|
||||||
|
const keyNode = getNode(property, "key")
|
||||||
|
const key = keyNode.type === "Identifier" ? getString(keyNode, "name") : String(keyNode.value)
|
||||||
|
if (isBlockedMember(key)) {
|
||||||
|
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, keyNode)
|
||||||
|
}
|
||||||
|
consumed.add(key)
|
||||||
|
yield* self.assignPattern(getNode(property, "value"), source[key], property)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.type === "ArrayPattern") {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new InterpreterRuntimeError("Array destructuring requires an array value.", pattern)
|
||||||
|
}
|
||||||
|
for (const [index, item] of getArray(pattern, "elements").entries()) {
|
||||||
|
if (item === null) continue
|
||||||
|
const element = asNode(item, `elements[${index}]`)
|
||||||
|
if (element.type === "RestElement") {
|
||||||
|
yield* self.assignPattern(getNode(element, "argument"), value.slice(index), element)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
yield* self.assignPattern(element, value[index], pattern)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InterpreterRuntimeError(`Unsupported assignment pattern '${pattern.type}'.`, node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private evaluateExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
|
private evaluateExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case "Literal": {
|
case "Literal": {
|
||||||
@@ -1804,25 +1882,36 @@ class Interpreter<R> {
|
|||||||
if (operator === "??=" || operator === "||=" || operator === "&&=") {
|
if (operator === "??=" || operator === "||=" || operator === "&&=") {
|
||||||
return yield* self.evaluateLogicalAssignment(node, left, operator)
|
return yield* self.evaluateLogicalAssignment(node, left, operator)
|
||||||
}
|
}
|
||||||
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
|
if (operator === "=" && (left.type === "ObjectPattern" || left.type === "ArrayPattern")) {
|
||||||
|
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
|
||||||
|
yield* self.assignPattern(left, rightValue, node)
|
||||||
|
return rightValue
|
||||||
|
}
|
||||||
if (left.type === "Identifier") {
|
if (left.type === "Identifier") {
|
||||||
const name = getString(left, "name")
|
const name = getString(left, "name")
|
||||||
if (operator === "=") return self.setIdentifierValue(name, rightValue, left)
|
if (operator !== "=") {
|
||||||
const next = boundedData(
|
const current = self.getIdentifierValue(name, left)
|
||||||
self.applyCompoundAssignment(operator, self.getIdentifierValue(name, left), rightValue, node),
|
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
|
||||||
"Assignment result",
|
|
||||||
)
|
|
||||||
return self.setIdentifierValue(name, next, left)
|
|
||||||
}
|
|
||||||
if (left.type === "MemberExpression") {
|
|
||||||
if (operator === "=") return yield* self.writeMember(left, rightValue)
|
|
||||||
return yield* self.modifyMember(left, (current) => {
|
|
||||||
const next = boundedData(
|
const next = boundedData(
|
||||||
self.applyCompoundAssignment(operator, current, rightValue, node),
|
self.applyCompoundAssignment(operator, current, rightValue, node),
|
||||||
"Assignment result",
|
"Assignment result",
|
||||||
)
|
)
|
||||||
return Effect.succeed({ write: true, next, result: next })
|
return self.setIdentifierValue(name, next, left)
|
||||||
})
|
}
|
||||||
|
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
|
||||||
|
if (operator === "=") return self.setIdentifierValue(name, rightValue, left)
|
||||||
|
}
|
||||||
|
if (left.type === "MemberExpression") {
|
||||||
|
return yield* self.modifyMember(left, (current) =>
|
||||||
|
Effect.map(self.evaluateExpression(getNode(node, "right")), (rightValue) => {
|
||||||
|
if (operator === "=") return { write: true, next: rightValue, result: rightValue }
|
||||||
|
const next = boundedData(
|
||||||
|
self.applyCompoundAssignment(operator, current, rightValue, node),
|
||||||
|
"Assignment result",
|
||||||
|
)
|
||||||
|
return { write: true, next, result: next }
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
throw new InterpreterRuntimeError("Assignment target must be an Identifier or MemberExpression.", left)
|
throw new InterpreterRuntimeError("Assignment target must be an Identifier or MemberExpression.", left)
|
||||||
})
|
})
|
||||||
@@ -1923,6 +2012,9 @@ class Interpreter<R> {
|
|||||||
if (callable.namespace === "Object" && args[0] instanceof ToolReference) {
|
if (callable.namespace === "Object" && args[0] instanceof ToolReference) {
|
||||||
return self.invokeObjectMethodOnTools(callable.name, args[0] as ToolReference, node)
|
return self.invokeObjectMethodOnTools(callable.name, args[0] as ToolReference, node)
|
||||||
}
|
}
|
||||||
|
if (callable.namespace === "Object" && callable.name === "assign") {
|
||||||
|
return invokeGlobalMethod(callable, args, node)
|
||||||
|
}
|
||||||
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
|
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
|
||||||
}
|
}
|
||||||
if (callable instanceof CoercionFunction) {
|
if (callable instanceof CoercionFunction) {
|
||||||
@@ -2575,8 +2667,12 @@ class Interpreter<R> {
|
|||||||
case "flat":
|
case "flat":
|
||||||
return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1))
|
return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1))
|
||||||
case "reverse":
|
case "reverse":
|
||||||
return Effect.succeed([...target].reverse())
|
return Effect.succeed(target.reverse())
|
||||||
case "sort":
|
case "sort":
|
||||||
|
return Effect.map(this.sortArray(target, args[0], node), (sorted) => {
|
||||||
|
target.splice(0, target.length, ...sorted)
|
||||||
|
return target
|
||||||
|
})
|
||||||
case "toSorted":
|
case "toSorted":
|
||||||
return this.sortArray(target, args[0], node)
|
return this.sortArray(target, args[0], node)
|
||||||
case "toReversed":
|
case "toReversed":
|
||||||
@@ -2659,19 +2755,24 @@ class Interpreter<R> {
|
|||||||
? Effect.succeed(invokeUriFunction(callback, callbackArgs, node))
|
? Effect.succeed(invokeUriFunction(callback, callbackArgs, node))
|
||||||
: self.invokeFunction(callback, callbackArgs)
|
: self.invokeFunction(callback, callbackArgs)
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
// Iterate a snapshot taken at call time so a callback that mutates the array can't
|
// Capture the initial length, but read the receiver live so callbacks observe mutations
|
||||||
// self-extend the loop - matching JS, where elements appended during iteration are not visited.
|
// without visiting elements appended after iteration begins.
|
||||||
const items = target.slice()
|
const length = target.length
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "map": {
|
case "map": {
|
||||||
const values: Array<unknown> = []
|
const values: Array<unknown> = []
|
||||||
for (const [index, item] of items.entries()) values.push(yield* apply([item, index, items]))
|
values.length = length
|
||||||
|
for (let index = 0; index < length; index += 1) {
|
||||||
|
if (!(index in target)) continue
|
||||||
|
values[index] = yield* apply([target[index], index, target])
|
||||||
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
case "flatMap": {
|
case "flatMap": {
|
||||||
const values: Array<unknown> = []
|
const values: Array<unknown> = []
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
const mapped = yield* apply([item, index, items])
|
if (!(index in target)) continue
|
||||||
|
const mapped = yield* apply([target[index], index, target])
|
||||||
if (Array.isArray(mapped)) values.push(...mapped)
|
if (Array.isArray(mapped)) values.push(...mapped)
|
||||||
else values.push(mapped)
|
else values.push(mapped)
|
||||||
}
|
}
|
||||||
@@ -2679,33 +2780,40 @@ class Interpreter<R> {
|
|||||||
}
|
}
|
||||||
case "filter": {
|
case "filter": {
|
||||||
const values: Array<unknown> = []
|
const values: Array<unknown> = []
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
if (yield* apply([item, index, items])) values.push(item)
|
if (!(index in target)) continue
|
||||||
|
const item = target[index]
|
||||||
|
if (yield* apply([item, index, target])) values.push(item)
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
case "find":
|
case "find":
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
if (yield* apply([item, index, items])) return item
|
const item = target[index]
|
||||||
|
if (yield* apply([item, index, target])) return item
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
case "findIndex":
|
case "findIndex":
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
if (yield* apply([item, index, items])) return index
|
if (yield* apply([target[index], index, target])) return index
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
case "some":
|
case "some":
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
if (yield* apply([item, index, items])) return true
|
if (!(index in target)) continue
|
||||||
|
if (yield* apply([target[index], index, target])) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
case "every":
|
case "every":
|
||||||
for (const [index, item] of items.entries()) {
|
for (let index = 0; index < length; index += 1) {
|
||||||
if (!(yield* apply([item, index, items]))) return false
|
if (!(index in target)) continue
|
||||||
|
if (!(yield* apply([target[index], index, target]))) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case "forEach":
|
case "forEach":
|
||||||
for (const [index, item] of items.entries()) yield* apply([item, index, items])
|
for (let index = 0; index < length; index += 1) {
|
||||||
|
if (index in target) yield* apply([target[index], index, target])
|
||||||
|
}
|
||||||
return undefined
|
return undefined
|
||||||
case "reduce": {
|
case "reduce": {
|
||||||
let accumulator: unknown
|
let accumulator: unknown
|
||||||
@@ -2714,13 +2822,14 @@ class Interpreter<R> {
|
|||||||
accumulator = args[1]
|
accumulator = args[1]
|
||||||
start = 0
|
start = 0
|
||||||
} else {
|
} else {
|
||||||
if (items.length === 0)
|
if (length === 0)
|
||||||
throw new InterpreterRuntimeError("Array.reduce of an empty array with no initial value.", node)
|
throw new InterpreterRuntimeError("Array.reduce of an empty array with no initial value.", node)
|
||||||
accumulator = items[0]
|
accumulator = target[0]
|
||||||
start = 1
|
start = 1
|
||||||
}
|
}
|
||||||
for (let index = start; index < items.length; index += 1) {
|
for (let index = start; index < length; index += 1) {
|
||||||
accumulator = yield* apply([accumulator, items[index], index, items])
|
if (!(index in target)) continue
|
||||||
|
accumulator = yield* apply([accumulator, target[index], index, target])
|
||||||
}
|
}
|
||||||
return accumulator
|
return accumulator
|
||||||
}
|
}
|
||||||
@@ -2729,26 +2838,27 @@ class Interpreter<R> {
|
|||||||
let start: number
|
let start: number
|
||||||
if (args.length >= 2) {
|
if (args.length >= 2) {
|
||||||
accumulator = args[1]
|
accumulator = args[1]
|
||||||
start = items.length - 1
|
start = length - 1
|
||||||
} else {
|
} else {
|
||||||
if (items.length === 0)
|
if (length === 0)
|
||||||
throw new InterpreterRuntimeError("Array.reduceRight of an empty array with no initial value.", node)
|
throw new InterpreterRuntimeError("Array.reduceRight of an empty array with no initial value.", node)
|
||||||
accumulator = items[items.length - 1]
|
accumulator = target[length - 1]
|
||||||
start = items.length - 2
|
start = length - 2
|
||||||
}
|
}
|
||||||
for (let index = start; index >= 0; index -= 1) {
|
for (let index = start; index >= 0; index -= 1) {
|
||||||
accumulator = yield* apply([accumulator, items[index], index, items])
|
if (!(index in target)) continue
|
||||||
|
accumulator = yield* apply([accumulator, target[index], index, target])
|
||||||
}
|
}
|
||||||
return accumulator
|
return accumulator
|
||||||
}
|
}
|
||||||
case "findLast":
|
case "findLast":
|
||||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
for (let index = length - 1; index >= 0; index -= 1) {
|
||||||
if (yield* apply([items[index], index, items])) return items[index]
|
if (yield* apply([target[index], index, target])) return target[index]
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
case "findLastIndex":
|
case "findLastIndex":
|
||||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
for (let index = length - 1; index >= 0; index -= 1) {
|
||||||
if (yield* apply([items[index], index, items])) return index
|
if (yield* apply([target[index], index, target])) return index
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,14 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
|
|||||||
case "hasOwn":
|
case "hasOwn":
|
||||||
return Object.hasOwn(requireObject(), String(args[1]))
|
return Object.hasOwn(requireObject(), String(args[1]))
|
||||||
case "assign": {
|
case "assign": {
|
||||||
const out: Record<string, unknown> = Object.create(null)
|
const target = args[0]
|
||||||
for (const source of args) {
|
if (target === null || typeof target !== "object" || Array.isArray(target) || isSandboxValue(target)) {
|
||||||
|
throw new InterpreterRuntimeError("Object.assign expects a data object target.", node)
|
||||||
|
}
|
||||||
|
const out = target as Record<string, unknown>
|
||||||
|
for (const source of args.slice(1)) {
|
||||||
if (source === null || source === undefined) continue
|
if (source === null || source === undefined) continue
|
||||||
const value = boundedData(source, "Object.assign input")
|
const value = source
|
||||||
if (isSandboxValue(value)) continue
|
if (isSandboxValue(value)) continue
|
||||||
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
||||||
throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
|
throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
|
||||||
|
|||||||
@@ -264,6 +264,46 @@ describe("Error values and instanceof", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
|
describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
|
||||||
|
test("sort and reverse mutate and return the receiver", async () => {
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
const sorted = [3, 1, 2]
|
||||||
|
const sortResult = sorted.sort((a, b) => a - b)
|
||||||
|
const reversed = [1, 2, 3]
|
||||||
|
const reverseResult = reversed.reverse()
|
||||||
|
return { sorted, sameSort: sorted === sortResult, reversed, sameReverse: reversed === reverseResult }
|
||||||
|
`),
|
||||||
|
).toEqual({ sorted: [1, 2, 3], sameSort: true, reversed: [3, 2, 1], sameReverse: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array callbacks receive the receiver and observe later mutations", async () => {
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
const values = [1, 2, 3]
|
||||||
|
const seen = values.map((value, index, receiver) => {
|
||||||
|
if (index === 0) values[1] = 9
|
||||||
|
return [value, receiver === values]
|
||||||
|
})
|
||||||
|
return seen
|
||||||
|
`),
|
||||||
|
).toEqual([
|
||||||
|
[1, true],
|
||||||
|
[9, true],
|
||||||
|
[3, true],
|
||||||
|
])
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
const values = [1, 2, 3]
|
||||||
|
const seen = []
|
||||||
|
values.forEach((value, index) => {
|
||||||
|
seen.push(value)
|
||||||
|
if (index === 0) values.pop()
|
||||||
|
})
|
||||||
|
return seen
|
||||||
|
`),
|
||||||
|
).toEqual([1, 2])
|
||||||
|
})
|
||||||
|
|
||||||
test("splice removes in place and returns the removed elements", async () => {
|
test("splice removes in place and returns the removed elements", async () => {
|
||||||
expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({
|
expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({
|
||||||
removed: [2, 3],
|
removed: [2, 3],
|
||||||
@@ -423,3 +463,38 @@ describe("H5: builtin coercion functions work as array callbacks", () => {
|
|||||||
expect(err.message).toContain("callback")
|
expect(err.message).toContain("callback")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("destructuring assignment", () => {
|
||||||
|
test("assigns object and array patterns to existing bindings", async () => {
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
let a = 0
|
||||||
|
let b = 0
|
||||||
|
;({ a } = { a: 2 })
|
||||||
|
;[a, b] = [3, 4]
|
||||||
|
return [a, b]
|
||||||
|
`),
|
||||||
|
).toEqual([3, 4])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("supports defaults, nesting, rest, and member targets", async () => {
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
let first = 0
|
||||||
|
let fallback = 0
|
||||||
|
let rest = {}
|
||||||
|
const target = {}
|
||||||
|
;[first, fallback = 2, ...target.tail] = [1]
|
||||||
|
;({ nested: { value: target.value }, kept: target.kept = 3, ...rest } = {
|
||||||
|
nested: { value: 4 },
|
||||||
|
extra: 5,
|
||||||
|
})
|
||||||
|
return { first, fallback, target, rest }
|
||||||
|
`),
|
||||||
|
).toEqual({ first: 1, fallback: 2, target: { tail: [], value: 4, kept: 3 }, rest: { extra: 5 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns the assigned value", async () => {
|
||||||
|
expect(await value(`let a = 0; const result = ([a] = [7]); return [a, result]`)).toEqual([7, [7]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -586,6 +586,26 @@ describe("Set", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("stdlib integration", () => {
|
describe("stdlib integration", () => {
|
||||||
|
test("Object.assign mutates and returns its target", async () => {
|
||||||
|
expect(
|
||||||
|
await value(`
|
||||||
|
const target = { a: 1 }
|
||||||
|
const result = Object.assign(target, { b: 2 })
|
||||||
|
return { target, result, same: target === result }
|
||||||
|
`),
|
||||||
|
).toEqual({ target: { a: 1, b: 2 }, result: { a: 1, b: 2 }, same: true })
|
||||||
|
expect(await value(`try { Object.assign(null, { a: 1 }); return false } catch { return true }`)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("assignment resolves and reads its left side before evaluating the right side", async () => {
|
||||||
|
expect(await value(`let x = 1; x += (x = 5); return x`)).toBe(6)
|
||||||
|
expect(await value(`let i = 0; const values = [9]; values[i++] = i; return [values, i]`)).toEqual([[1], 1])
|
||||||
|
expect(await value(`let i = 0; const values = [10, 20]; values[i++] += i; return [values, i]`)).toEqual([
|
||||||
|
[11, 20],
|
||||||
|
1,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test("typeof reports constructors as functions and never throws", async () => {
|
test("typeof reports constructors as functions and never throws", async () => {
|
||||||
expect(await value(`return typeof Map`)).toBe("function")
|
expect(await value(`return typeof Map`)).toBe("function")
|
||||||
expect(await value(`return typeof ((x) => x)`)).toBe("function")
|
expect(await value(`return typeof ((x) => x)`)).toBe("function")
|
||||||
|
|||||||
Reference in New Issue
Block a user