feat(codemode): expand standard library parity (#37943)

This commit is contained in:
Aiden Cline 2026-07-20 09:59:37 -05:00 committed by GitHub
commit 3da0dea8e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 171 additions and 15 deletions

View file

@ -714,6 +714,19 @@ const invokeArrayMethod = <R>(
for (const item of inserted) rejectCircularInsertion(target, item, "Array.splice result", node)
return Effect.succeed(target.splice(start, deleteCount, ...inserted))
}
case "toSpliced": {
if (args.length === 0) return Effect.succeed([...target])
const start = optNumber(args[0], "start") ?? 0
if (args.length === 1) {
const copied = [...target]
copied.splice(start)
return Effect.succeed(copied)
}
const deleteCount = optNumber(args[1], "delete count") ?? 0
const copied = [...target]
copied.splice(start, deleteCount, ...args.slice(2))
return Effect.succeed(copied)
}
case "fill": {
rejectCircularInsertion(target, args[0], "Array.fill result", node)
return Effect.succeed(target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end")))

View file

@ -1,5 +1,5 @@
import type { SafeObject } from "../tool-runtime.js"
import type { CodeModePromise, CodeModeURL } from "../values.js"
import type { CodeModePromise, CodeModeRegExp, CodeModeURL } from "../values.js"
export type SourcePosition = {
line: number
@ -35,7 +35,7 @@ export type StatementResult =
| { kind: "continue" }
export type MemberReference = {
target: SafeObject | Array<unknown> | CodeModeURL
target: SafeObject | Array<unknown> | CodeModeRegExp | CodeModeURL
key: string | number
}

View file

@ -1958,6 +1958,7 @@ export class Interpreter<R> {
return new ComputedValue(undefined)
}
if (objectValue instanceof CodeModeRegExp) {
if (key === "lastIndex") return { target: objectValue, key }
if (typeof key === "string" && regexpProperties.has(key)) {
return new ComputedValue((objectValue.regex as unknown as Record<string, unknown>)[key])
}
@ -2052,6 +2053,7 @@ export class Interpreter<R> {
if (typeof reference.key === "string") return new IntrinsicReference(reference.target, reference.key)
return reference.target[reference.key]
}
if (reference.target instanceof CodeModeRegExp) return reference.target.lastIndex
if (reference.target instanceof CodeModeURL) {
return (reference.target.url as unknown as Record<string, unknown>)[String(reference.key)]
}
@ -2082,6 +2084,9 @@ export class Interpreter<R> {
) {
throw new InterpreterRuntimeError("Only data fields may be deleted in CodeMode.", target, "InvalidDataValue")
}
if (reference.target instanceof CodeModeRegExp) {
return Reflect.deleteProperty(reference.target.regex, reference.key)
}
return Reflect.deleteProperty(reference.target, reference.key)
})
}
@ -2114,16 +2119,20 @@ export class Interpreter<R> {
}
}
const key = Array.isArray(reference.target) ? reference.key : String(reference.key)
const current =
reference.target instanceof CodeModeURL
? (reference.target.url as unknown as Record<string, unknown>)[key]
: (reference.target as Record<PropertyKey, unknown>)[key]
const { write, next, result } = yield* compute(current)
const { write, next, result } = yield* compute(self.readReferenceValue(reference, key))
if (write) self.assignToReference(reference, key, next, node)
return result
})
}
private readReferenceValue(reference: MemberReference, key: number | string): unknown {
if (reference.target instanceof CodeModeURL) {
return (reference.target.url as unknown as Record<string, unknown>)[key]
}
if (reference.target instanceof CodeModeRegExp) return reference.target.lastIndex
return (reference.target as Record<PropertyKey, unknown>)[key]
}
private assignToReference(reference: MemberReference, key: number | string, next: unknown, node: AstNode): void {
if (Array.isArray(reference.target)) {
const target = reference.target
@ -2152,6 +2161,10 @@ export class Interpreter<R> {
throw new InterpreterRuntimeError(`URL.${property} received an invalid value.`, node).as("TypeError")
}
}
if (reference.target instanceof CodeModeRegExp) {
reference.target.lastIndex = next
return
}
const target = reference.target as SafeObject
const objectKey = key as string
rejectCircularInsertion(target, next, "Object assignment result", node)

View file

@ -29,6 +29,7 @@ export const arrayMethods = new Set([
"shift",
"unshift",
"splice",
"toSpliced",
"fill",
"copyWithin",
"keys",

View file

@ -4,6 +4,8 @@ export const dateMethods = new Set([
"toISOString",
"toJSON",
"toString",
"toUTCString",
"toGMTString",
"getFullYear",
"getMonth",
"getDate",
@ -51,6 +53,9 @@ export const invokeDateMethod = (value: CodeModeDate, name: string, node: AstNod
return Number.isFinite(value.time) ? hosted.toISOString() : null
case "toString":
return coerceToString(value)
case "toUTCString":
case "toGMTString":
return hosted.toUTCString()
case "getFullYear":
return hosted.getFullYear()
case "getMonth":

View file

@ -59,9 +59,18 @@ export const invokeRegExpMethod = (
): unknown => {
switch (name) {
case "test":
return value.regex.test(coerceToString(args[0]))
case "exec": {
const matched = value.regex.exec(coerceToString(args[0]))
const input = coerceToString(args[0])
const lastIndex = value.lastIndex
const stateful = value.regex.global || value.regex.sticky
value.regex.lastIndex = toLength(lastIndex)
if (name === "test") {
const matched = value.regex.test(input)
if (!stateful) value.lastIndex = lastIndex
return matched
}
const matched = value.regex.exec(input)
if (!stateful) value.lastIndex = lastIndex
return matched === null ? null : matchToValue(matched)
}
case "toString":
@ -70,7 +79,13 @@ export const invokeRegExpMethod = (
throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node)
}
}
const toLength = (value: unknown): number => {
const number = coerceToNumber(value)
if (Number.isNaN(number) || number <= 0) return 0
return Math.min(Math.floor(number), Number.MAX_SAFE_INTEGER)
}
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
import { isBlockedMember, type SafeObject } from "../tool-runtime.js"
import { CodeModeRegExp } from "../values.js"
import { coerceToString } from "./value.js"
import { coerceToNumber, coerceToString } from "./value.js"

View file

@ -13,6 +13,14 @@ export class CodeModeRegExp {
constructor(pattern: string, flags: string) {
this.regex = new RegExp(pattern, flags)
}
get lastIndex(): unknown {
return Reflect.get(this.regex, "lastIndex")
}
set lastIndex(value: unknown) {
Reflect.set(this.regex, "lastIndex", value)
}
}
export class CodeModeMap {