feat(codemode): support destructuring assignment (#35600)
This commit is contained in:
parent
0e1c0414c2
commit
d603eed5a1
2 changed files with 118 additions and 0 deletions
|
|
@ -1403,6 +1403,84 @@ class Interpreter<R> {
|
|||
})
|
||||
}
|
||||
|
||||
private assignPattern(pattern: AstNode, value: unknown, node: AstNode): Effect.Effect<void, unknown, R> {
|
||||
const self = this
|
||||
return Effect.gen(function* () {
|
||||
if (pattern.type === "Identifier") {
|
||||
self.setIdentifierValue(getString(pattern, "name"), value, pattern)
|
||||
return
|
||||
}
|
||||
|
||||
if (pattern.type === "MemberExpression") {
|
||||
yield* self.writeMember(pattern, value)
|
||||
return
|
||||
}
|
||||
|
||||
if (pattern.type === "AssignmentPattern") {
|
||||
const resolved = value === undefined ? yield* self.evaluateExpression(getNode(pattern, "right")) : value
|
||||
yield* self.assignPattern(getNode(pattern, "left"), resolved, node)
|
||||
return
|
||||
}
|
||||
|
||||
if (pattern.type === "ObjectPattern") {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value) || isRuntimeReference(value)) {
|
||||
throw new InterpreterRuntimeError(
|
||||
"Object destructuring requires a data object value.",
|
||||
pattern,
|
||||
"InvalidDataValue",
|
||||
)
|
||||
}
|
||||
|
||||
const source = value as SafeObject
|
||||
const consumed = new Set<string>()
|
||||
for (const propertyValue of getArray(pattern, "properties")) {
|
||||
const property = asNode(propertyValue, "properties")
|
||||
if (property.type === "RestElement") {
|
||||
const rest: SafeObject = Object.create(null) as SafeObject
|
||||
for (const [key, item] of Object.entries(source)) {
|
||||
if (!consumed.has(key) && !isBlockedMember(key)) rest[key] = item
|
||||
}
|
||||
yield* self.assignPattern(getNode(property, "argument"), rest, property)
|
||||
continue
|
||||
}
|
||||
if (
|
||||
property.type !== "Property" ||
|
||||
getBoolean(property, "computed") ||
|
||||
getString(property, "kind") !== "init"
|
||||
) {
|
||||
throw new InterpreterRuntimeError("Only named object destructuring properties are supported.", property)
|
||||
}
|
||||
const keyNode = getNode(property, "key")
|
||||
const key = keyNode.type === "Identifier" ? getString(keyNode, "name") : String(keyNode.value)
|
||||
if (isBlockedMember(key)) {
|
||||
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, keyNode)
|
||||
}
|
||||
consumed.add(key)
|
||||
yield* self.assignPattern(getNode(property, "value"), source[key], property)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (pattern.type === "ArrayPattern") {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new InterpreterRuntimeError("Array destructuring requires an array value.", pattern)
|
||||
}
|
||||
for (const [index, item] of getArray(pattern, "elements").entries()) {
|
||||
if (item === null) continue
|
||||
const element = asNode(item, `elements[${index}]`)
|
||||
if (element.type === "RestElement") {
|
||||
yield* self.assignPattern(getNode(element, "argument"), value.slice(index), element)
|
||||
break
|
||||
}
|
||||
yield* self.assignPattern(element, value[index], pattern)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
throw new InterpreterRuntimeError(`Unsupported assignment pattern '${pattern.type}'.`, node)
|
||||
})
|
||||
}
|
||||
|
||||
private evaluateExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
|
||||
switch (node.type) {
|
||||
case "Literal": {
|
||||
|
|
@ -1804,6 +1882,11 @@ class Interpreter<R> {
|
|||
if (operator === "??=" || operator === "||=" || operator === "&&=") {
|
||||
return yield* self.evaluateLogicalAssignment(node, left, operator)
|
||||
}
|
||||
if (operator === "=" && (left.type === "ObjectPattern" || left.type === "ArrayPattern")) {
|
||||
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
|
||||
yield* self.assignPattern(left, rightValue, node)
|
||||
return rightValue
|
||||
}
|
||||
if (left.type === "Identifier") {
|
||||
const name = getString(left, "name")
|
||||
if (operator !== "=") {
|
||||
|
|
|
|||
|
|
@ -463,3 +463,38 @@ describe("H5: builtin coercion functions work as array callbacks", () => {
|
|||
expect(err.message).toContain("callback")
|
||||
})
|
||||
})
|
||||
|
||||
describe("destructuring assignment", () => {
|
||||
test("assigns object and array patterns to existing bindings", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
let a = 0
|
||||
let b = 0
|
||||
;({ a } = { a: 2 })
|
||||
;[a, b] = [3, 4]
|
||||
return [a, b]
|
||||
`),
|
||||
).toEqual([3, 4])
|
||||
})
|
||||
|
||||
test("supports defaults, nesting, rest, and member targets", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
let first = 0
|
||||
let fallback = 0
|
||||
let rest = {}
|
||||
const target = {}
|
||||
;[first, fallback = 2, ...target.tail] = [1]
|
||||
;({ nested: { value: target.value }, kept: target.kept = 3, ...rest } = {
|
||||
nested: { value: 4 },
|
||||
extra: 5,
|
||||
})
|
||||
return { first, fallback, target, rest }
|
||||
`),
|
||||
).toEqual({ first: 1, fallback: 2, target: { tail: [], value: 4, kept: 3 }, rest: { extra: 5 } })
|
||||
})
|
||||
|
||||
test("returns the assigned value", async () => {
|
||||
expect(await value(`let a = 0; const result = ([a] = [7]); return [a, result]`)).toEqual([7, [7]])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue