diff --git a/typescript/src/ts/program.ts b/typescript/src/ts/program.ts index 5fc21b5d..712d00cc 100644 --- a/typescript/src/ts/program.ts +++ b/typescript/src/ts/program.ts @@ -98,7 +98,7 @@ function isValidFunctionName(name: unknown): name is string { */ export function createModuleTextFromProgram(jsonObject: object): Result { const steps = (jsonObject as Program)["@steps"]; - if (!(Array.isArray(steps) && steps.every(step => typeof step === "object" && step !== null && step.hasOwnProperty("@func")))) { + if (!(Array.isArray(steps) && steps.every(step => typeof step === "object" && step !== null && Object.prototype.hasOwnProperty.call(step, "@func")))) { return error("JSON object is not a valid program"); } let hasError = false; @@ -119,15 +119,15 @@ export function createModuleTextFromProgram(jsonObject: object): Result } function objectToString(obj: Record) { - if (obj.hasOwnProperty("@ref")) { + if (Object.prototype.hasOwnProperty.call(obj, "@ref")) { const index = obj["@ref"]; if (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < currentStep && Object.keys(obj).length === 1) { return `step${index + 1}`; } } - else if (obj.hasOwnProperty("@func")) { + else if (Object.prototype.hasOwnProperty.call(obj, "@func")) { const func = obj["@func"]; - const hasArgs = obj.hasOwnProperty("@args"); + const hasArgs = Object.prototype.hasOwnProperty.call(obj, "@args"); const args = hasArgs ? obj["@args"] : []; if (isValidFunctionName(func) && (Array.isArray(args)) && Object.keys(obj).length === (hasArgs ? 2 : 1)) { return `api.${func}(${arrayToString(args)})`; @@ -171,16 +171,16 @@ export async function evaluateJsonProgram(program: Program, onCall: (func: strin } async function evaluateObject(obj: Record) { - if (obj.hasOwnProperty("@ref")) { + if (Object.prototype.hasOwnProperty.call(obj, "@ref")) { const index = obj["@ref"]; if (Object.keys(obj).length === 1 && typeof index === "number" && Number.isInteger(index) && index >= 0 && index < results.length) { return results[index]; } throw new Error(`Invalid result reference: ${String(index)}`); } - else if (obj.hasOwnProperty("@func")) { + else if (Object.prototype.hasOwnProperty.call(obj, "@func")) { const func = obj["@func"]; - const hasArgs = obj.hasOwnProperty("@args"); + const hasArgs = Object.prototype.hasOwnProperty.call(obj, "@args"); const args = hasArgs ? obj["@args"] : []; if (!isValidFunctionName(func)) { throw new Error(`Invalid function name: ${JSON.stringify(func)}`); diff --git a/typescript/test/program.test.ts b/typescript/test/program.test.ts index b95515bb..53ba9a0b 100644 --- a/typescript/test/program.test.ts +++ b/typescript/test/program.test.ts @@ -2,6 +2,39 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { evaluateJsonProgram, createModuleTextFromProgram, Program, FunctionCall } from "../dist/ts/index.js"; +describe("program objects with own property names", () => { + const programs: Array<[string, Program]> = [ + ["a literal hasOwnProperty field", { + "@steps": [{ "@func": "identity", "@args": [{ hasOwnProperty: "user data", value: 7 }] }], + }], + ["a null-prototype call and argument", { + "@steps": [Object.assign(Object.create(null), { + "@func": "identity", + "@args": [Object.assign(Object.create(null), { hasOwnProperty: "user data", value: 7 })], + })], + }], + ]; + + for (const [name, program] of programs) { + it(`generates a module for ${name}`, () => { + const result = createModuleTextFromProgram(program); + assert.ok(result.success); + assert.equal(result.data, + 'import { API } from "./schema";\nfunction program(api: API) {\n' + + ' return api.identity({ "hasOwnProperty": "user data", "value": 7 });\n}'); + }); + + it(`evaluates ${name} without losing the argument`, async () => { + const result = await evaluateJsonProgram(program, async (func, args) => { + assert.equal(func, "identity"); + assert.deepEqual(args, [{ hasOwnProperty: "user data", value: 7 }]); + return args[0]; + }); + assert.deepEqual(result, { hasOwnProperty: "user data", value: 7 }); + }); + } +}); + // --------------------------------------------------------------------------- // evaluateJsonProgram result-reference bounds checking // ---------------------------------------------------------------------------