|
|
@@ -1,17 +1,20 @@
|
|
|
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
|
import { isBlockedMember } from "../tool-runtime.js"
|
|
|
-import { isSandboxValue, SandboxMap, SandboxURLSearchParams } from "../values.js"
|
|
|
+import { isSandboxValue, SandboxMap, SandboxSet, SandboxURLSearchParams } from "../values.js"
|
|
|
import { boundedData, coerceToString } from "./value.js"
|
|
|
|
|
|
export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"])
|
|
|
+export const objectMethodsPreservingIdentity = new Set(["assign", "values", "entries", "fromEntries"])
|
|
|
|
|
|
export const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
|
if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
|
|
|
const requireObject = (): Record<string, unknown> => {
|
|
|
+ const input = args[0]
|
|
|
const value = boundedData(args[0], `Object.${name} input`)
|
|
|
+ if (Array.isArray(input)) return input as unknown as Record<string, unknown>
|
|
|
if (isSandboxValue(value)) return {}
|
|
|
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
|
- throw new InterpreterRuntimeError(`Object.${name} expects a data object.`, node)
|
|
|
+ if (value === null || typeof value !== "object") {
|
|
|
+ throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node)
|
|
|
}
|
|
|
return value as Record<string, unknown>
|
|
|
}
|
|
|
@@ -19,6 +22,11 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
|
|
|
if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
|
|
|
out[key] = item
|
|
|
}
|
|
|
+ const addEntry = (out: Record<string, unknown>, key: unknown, item: unknown): void => {
|
|
|
+ boundedData(key, "Object.fromEntries key")
|
|
|
+ boundedData(item, "Object.fromEntries value")
|
|
|
+ guardedSet(out, coerceToString(key), item)
|
|
|
+ }
|
|
|
switch (name) {
|
|
|
case "keys": {
|
|
|
const value = boundedData(args[0], "Object.keys input")
|
|
|
@@ -55,7 +63,7 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
|
|
|
case "fromEntries": {
|
|
|
if (args[0] instanceof SandboxMap) {
|
|
|
const out: Record<string, unknown> = Object.create(null)
|
|
|
- for (const [key, item] of args[0].map.entries()) guardedSet(out, coerceToString(key), item)
|
|
|
+ for (const [key, item] of args[0].map.entries()) addEntry(out, key, item)
|
|
|
return out
|
|
|
}
|
|
|
if (args[0] instanceof SandboxURLSearchParams) {
|
|
|
@@ -63,16 +71,18 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
|
|
|
for (const [key, value] of args[0].params.entries()) guardedSet(out, key, value)
|
|
|
return out
|
|
|
}
|
|
|
- const pairs = boundedData(args[0], "Object.fromEntries input")
|
|
|
+ const pairs = args[0] instanceof SandboxSet ? Array.from(args[0].set.values()) : args[0]
|
|
|
if (!Array.isArray(pairs)) {
|
|
|
+ boundedData(args[0], "Object.fromEntries input")
|
|
|
throw new InterpreterRuntimeError("Object.fromEntries expects an array of [key, value] pairs.", node)
|
|
|
}
|
|
|
const out: Record<string, unknown> = Object.create(null)
|
|
|
for (const pair of pairs) {
|
|
|
- if (!Array.isArray(pair)) {
|
|
|
- throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] pairs.", node)
|
|
|
- }
|
|
|
- guardedSet(out, String(pair[0]), pair[1])
|
|
|
+ const validated = boundedData(pair, "Object.fromEntries entry")
|
|
|
+ if (validated === null || typeof validated !== "object" || isSandboxValue(validated))
|
|
|
+ throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] entry objects.", node)
|
|
|
+ const entry = pair as Record<string, unknown>
|
|
|
+ addEntry(out, entry[0], entry[1])
|
|
|
}
|
|
|
return out
|
|
|
}
|