fix(codemode): align string, array, and Date behavior (#37775)
This commit is contained in:
parent
fe0c74f4df
commit
391cfbcfe7
6 changed files with 165 additions and 36 deletions
|
|
@ -428,16 +428,14 @@ const invokeStringReplacer = <R>(
|
|||
let end = 0
|
||||
for (const match of matches) {
|
||||
const replacement = yield* apply(match.args)
|
||||
const resolved =
|
||||
args[1] instanceof CodeModeFunction && args[1].async && replacement instanceof CodeModePromise
|
||||
? yield* runner.settlePromise(replacement)
|
||||
: replacement
|
||||
// Error values are branded plain objects; boundedData would strip the brand before coercion.
|
||||
output.push(
|
||||
value.slice(end, match.offset),
|
||||
errorBrandName(resolved)
|
||||
? coerceToString(resolved)
|
||||
: coerceToString(boundedData(resolved, `String.${name} replacer result`)),
|
||||
replacement instanceof CodeModePromise
|
||||
? "[object Promise]"
|
||||
: errorBrandName(replacement)
|
||||
? coerceToString(replacement)
|
||||
: coerceToString(boundedData(replacement, `String.${name} replacer result`)),
|
||||
)
|
||||
end = match.offset + match.match.length
|
||||
}
|
||||
|
|
@ -664,11 +662,20 @@ const invokeArrayMethod = <R>(
|
|||
return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1))
|
||||
case "reverse":
|
||||
return Effect.succeed(target.reverse())
|
||||
case "sort":
|
||||
case "sort": {
|
||||
const length = target.length
|
||||
const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(target, index)).filter((own) => !own).length
|
||||
const itemCount = length - holeCount
|
||||
return Effect.map(sortArray(runner, target, args[0], "Array.sort", node), (sorted) => {
|
||||
target.splice(0, target.length, ...sorted)
|
||||
sorted.slice(0, itemCount).forEach((item, index) => {
|
||||
target[index] = item
|
||||
})
|
||||
Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
|
||||
Reflect.deleteProperty(target, index)
|
||||
})
|
||||
return target
|
||||
})
|
||||
}
|
||||
case "toSorted":
|
||||
return sortArray(runner, target, args[0], "Array.toSorted", node)
|
||||
case "toReversed":
|
||||
|
|
|
|||
|
|
@ -1095,7 +1095,7 @@ export class Interpreter<R> {
|
|||
const args = yield* self.evaluateCallArguments(argNodes)
|
||||
switch (name) {
|
||||
case "Date":
|
||||
return self.constructDate(args)
|
||||
return yield* self.constructDate(args, node)
|
||||
case "RegExp":
|
||||
return self.constructRegExp(args, node)
|
||||
case "Map":
|
||||
|
|
@ -1133,17 +1133,37 @@ export class Interpreter<R> {
|
|||
)
|
||||
}
|
||||
|
||||
private constructDate(args: Array<unknown>): CodeModeDate {
|
||||
if (args.length === 0) return new CodeModeDate(Date.now())
|
||||
private constructDate(args: Array<unknown>, node: AstNode): Effect.Effect<CodeModeDate, unknown, R> {
|
||||
if (args.length === 0) return Effect.succeed(new CodeModeDate(Date.now()))
|
||||
if (args.length === 1) {
|
||||
const arg = args[0]
|
||||
if (arg instanceof CodeModeDate) return new CodeModeDate(arg.time)
|
||||
if (typeof arg === "number") return new CodeModeDate(new Date(arg).getTime())
|
||||
if (typeof arg === "string") return new CodeModeDate(Date.parse(arg))
|
||||
return new CodeModeDate(Number.NaN)
|
||||
if (arg instanceof CodeModeDate) return Effect.succeed(new CodeModeDate(arg.time))
|
||||
return Effect.map(this.toDatePrimitive(arg, node), (value) =>
|
||||
typeof value === "string"
|
||||
? new CodeModeDate(Date.parse(value))
|
||||
: new CodeModeDate(new Date(coerceToNumber(value)).getTime()),
|
||||
)
|
||||
}
|
||||
const parts = args.map((arg) => coerceToNumber(arg))
|
||||
return new CodeModeDate(new Date(...(parts as [number, number])).getTime())
|
||||
return Effect.succeed(new CodeModeDate(new Date(...(parts as [number, number])).getTime()))
|
||||
}
|
||||
|
||||
private toDatePrimitive(value: unknown, node: AstNode): Effect.Effect<unknown, unknown, R> {
|
||||
if (value === null || (typeof value !== "object" && typeof value !== "function")) return Effect.succeed(value)
|
||||
const object = value as Record<string, unknown>
|
||||
const self = this
|
||||
return Effect.gen(function* () {
|
||||
if (Object.hasOwn(object, "valueOf") && typeofValue(object.valueOf) === "function") {
|
||||
const result = yield* self.runner.invokeCallable(object.valueOf, [], node)
|
||||
if (result === null || (typeof result !== "object" && typeof result !== "function")) return result
|
||||
}
|
||||
if (!Object.hasOwn(object, "toString")) return coerceToString(value)
|
||||
if (typeofValue(object.toString) === "function") {
|
||||
const result = yield* self.runner.invokeCallable(object.toString, [], node)
|
||||
if (result === null || (typeof result !== "object" && typeof result !== "function")) return result
|
||||
}
|
||||
throw new InterpreterRuntimeError("Cannot convert object to primitive value.", node).as("TypeError")
|
||||
})
|
||||
}
|
||||
|
||||
private constructRegExp(args: Array<unknown>, node: AstNode): CodeModeRegExp {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const invokeDateMethod = (value: CodeModeDate, name: string, node: AstNod
|
|||
case "valueOf":
|
||||
return value.time
|
||||
case "toISOString":
|
||||
if (!Number.isFinite(value.time)) throw new InterpreterRuntimeError("Invalid time value.", node)
|
||||
if (!Number.isFinite(value.time)) throw new InterpreterRuntimeError("Invalid time value.", node).as("RangeError")
|
||||
return hosted.toISOString()
|
||||
case "toJSON":
|
||||
return Number.isFinite(value.time) ? hosted.toISOString() : null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue