object.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
  2. import { isBlockedMember } from "../tool-runtime.js"
  3. import { isCodeModeValue, CodeModeMap, CodeModePromise, CodeModeSet, CodeModeURLSearchParams } from "../values.js"
  4. import { boundedData, coerceToString } from "./value.js"
  5. export const objectMethodsPreservingIdentity = new Set(["assign", "values", "entries", "fromEntries"])
  6. export const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
  7. const requireObject = (): Record<string, unknown> => {
  8. const input = args[0]
  9. if (Array.isArray(input)) return input as unknown as Record<string, unknown>
  10. if (isCodeModeValue(input)) return {}
  11. if (input instanceof CodeModePromise) {
  12. throw new InterpreterRuntimeError(
  13. `Object.${name} received an un-awaited Promise; await it before inspecting the result.`,
  14. node,
  15. "InvalidDataValue",
  16. )
  17. }
  18. if (input === null || typeof input !== "object") {
  19. throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
  20. }
  21. const prototype = Object.getPrototypeOf(input)
  22. if (prototype !== null && prototype !== Object.prototype) {
  23. throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
  24. }
  25. return input as Record<string, unknown>
  26. }
  27. const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
  28. if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
  29. out[key] = item
  30. }
  31. const addEntry = (out: Record<string, unknown>, key: unknown, item: unknown): void => {
  32. boundedData(key, "Object.fromEntries key")
  33. boundedData(item, "Object.fromEntries value")
  34. guardedSet(out, coerceToString(key), item)
  35. }
  36. switch (name) {
  37. case "keys":
  38. return Object.keys(requireObject())
  39. case "values":
  40. return Object.values(requireObject())
  41. case "entries":
  42. return Object.entries(requireObject()).map(([key, item]) => [key, item])
  43. case "hasOwn":
  44. return Object.hasOwn(requireObject(), String(args[1]))
  45. case "assign": {
  46. const target = args[0]
  47. if (target === null || typeof target !== "object" || Array.isArray(target) || isCodeModeValue(target)) {
  48. throw new InterpreterRuntimeError("Object.assign expects a data object target.", node)
  49. }
  50. const out = target as Record<string, unknown>
  51. for (const source of args.slice(1)) {
  52. if (source === null || source === undefined || isCodeModeValue(source)) continue
  53. if (typeof source !== "object" || Array.isArray(source)) {
  54. throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
  55. }
  56. for (const [key, item] of Object.entries(source)) guardedSet(out, key, item)
  57. }
  58. return out
  59. }
  60. case "fromEntries": {
  61. if (args[0] instanceof CodeModeMap) {
  62. const out: Record<string, unknown> = Object.create(null)
  63. for (const [key, item] of args[0].map.entries()) addEntry(out, key, item)
  64. return out
  65. }
  66. if (args[0] instanceof CodeModeURLSearchParams) {
  67. const out: Record<string, unknown> = Object.create(null)
  68. for (const [key, value] of args[0].params.entries()) guardedSet(out, key, value)
  69. return out
  70. }
  71. const pairs = args[0] instanceof CodeModeSet ? Array.from(args[0].set.values()) : args[0]
  72. if (!Array.isArray(pairs)) {
  73. boundedData(args[0], "Object.fromEntries input")
  74. throw new InterpreterRuntimeError("Object.fromEntries expects an array of [key, value] pairs.", node)
  75. }
  76. const out: Record<string, unknown> = Object.create(null)
  77. for (const pair of pairs) {
  78. const validated = boundedData(pair, "Object.fromEntries entry")
  79. if (validated === null || typeof validated !== "object" || isCodeModeValue(validated))
  80. throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] entry objects.", node)
  81. const entry = pair as Record<string, unknown>
  82. addEntry(out, entry[0], entry[1])
  83. }
  84. return out
  85. }
  86. }
  87. throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
  88. }