From 8e3d500f848591013eabeff741051d3aee528d1c Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 9 Mar 2023 08:59:10 -0500 Subject: [PATCH] Fixed issue with computed properties in object destructuring --- src/compiler/compile/nodes/AwaitBlock.ts | 4 +- src/compiler/compile/nodes/CatchBlock.ts | 1 + src/compiler/compile/nodes/ConstTag.ts | 4 +- src/compiler/compile/nodes/EachBlock.ts | 6 +- src/compiler/compile/nodes/ThenBlock.ts | 1 + src/compiler/compile/nodes/shared/Context.ts | 86 ++++++++++++++----- .../compile/nodes/shared/Expression.ts | 1 + .../compile/render_dom/wrappers/AwaitBlock.ts | 13 ++- .../compile/render_dom/wrappers/EachBlock.ts | 12 ++- .../wrappers/shared/add_const_tags.ts | 11 ++- 10 files changed, 108 insertions(+), 31 deletions(-) diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts index 4a669b6365..56471cde2c 100644 --- a/src/compiler/compile/nodes/AwaitBlock.ts +++ b/src/compiler/compile/nodes/AwaitBlock.ts @@ -35,12 +35,12 @@ export default class AwaitBlock extends Node { if (this.then_node) { this.then_contexts = []; - unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component, context_rest_properties: this.context_rest_properties }); + unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component, context_rest_properties: this.context_rest_properties, owner: this }); } if (this.catch_node) { this.catch_contexts = []; - unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component, context_rest_properties: this.context_rest_properties }); + unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component, context_rest_properties: this.context_rest_properties, owner: this }); } this.pending = new PendingBlock(component, this, scope, info.pending); diff --git a/src/compiler/compile/nodes/CatchBlock.ts b/src/compiler/compile/nodes/CatchBlock.ts index ba6a4b77a6..d92b4eda56 100644 --- a/src/compiler/compile/nodes/CatchBlock.ts +++ b/src/compiler/compile/nodes/CatchBlock.ts @@ -17,6 +17,7 @@ export default class CatchBlock extends AbstractBlock { this.scope = scope.child(); if (parent.catch_node) { parent.catch_contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this); }); } diff --git a/src/compiler/compile/nodes/ConstTag.ts b/src/compiler/compile/nodes/ConstTag.ts index 44a50aa005..2163670db7 100644 --- a/src/compiler/compile/nodes/ConstTag.ts +++ b/src/compiler/compile/nodes/ConstTag.ts @@ -61,10 +61,12 @@ export default class ConstTag extends Node { node: this.node.expression.left, scope: this.scope, component: this.component, - context_rest_properties: this.context_rest_properties + context_rest_properties: this.context_rest_properties, + owner: this }); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; const owner = this.scope.get_owner(context.key.name); if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { this.component.error(this.node, compiler_errors.invalid_const_declaration(context.key.name)); diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index 4a5ea19e37..5f5b69813e 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -42,10 +42,12 @@ export default class EachBlock extends AbstractBlock { this.scope = scope.child(); this.context_rest_properties = new Map(); this.contexts = []; - unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component, context_rest_properties: this.context_rest_properties }); + unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component, owner: this, context_rest_properties: this.context_rest_properties }); this.contexts.forEach(context => { - this.scope.add(context.key.name, this.expression.dependencies, this); + if (context.type === 'DestructuredVariable') { + this.scope.add(context.key.name, this.expression.dependencies, this); + } }); if (this.index) { diff --git a/src/compiler/compile/nodes/ThenBlock.ts b/src/compiler/compile/nodes/ThenBlock.ts index 6aee3f916f..b394db1e47 100644 --- a/src/compiler/compile/nodes/ThenBlock.ts +++ b/src/compiler/compile/nodes/ThenBlock.ts @@ -17,6 +17,7 @@ export default class ThenBlock extends AbstractBlock { this.scope = scope.child(); if (parent.then_node) { parent.then_contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this); }); } diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 185e3b4c88..3fa1b8e452 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -1,13 +1,24 @@ -import { x } from 'code-red'; -import { Node, Identifier, Expression } from 'estree'; +import { b, x } from 'code-red'; +import { Node, Identifier, Expression as ESTreeExpression } from 'estree'; import { walk } from 'estree-walker'; import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import { clone } from '../../../utils/clone'; import Component from '../../Component'; +import Block from '../../render_dom/Block'; import flatten_reference from '../../utils/flatten_reference'; +import { INode } from '../interfaces'; +import Expression from './Expression'; import TemplateScope from './TemplateScope'; -export interface Context { +export type Context = DestructuredVariable | ComputedProperty; + +interface ComputedProperty { + type: 'ComputedProperty'; + declaration: (block: Block, scope: TemplateScope, ctx: string) => Node[]; +} + +interface DestructuredVariable { + type: 'DestructuredVariable' key: Identifier; name?: string; modifier: (node: Node) => Node; @@ -21,26 +32,32 @@ export function unpack_destructuring({ default_modifier = (node) => node, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props = 0 }: { contexts: Context[]; node: Node; - modifier?: Context['modifier']; - default_modifier?: Context['default_modifier']; + modifier?: DestructuredVariable['modifier']; + default_modifier?: DestructuredVariable['default_modifier']; scope: TemplateScope; component: Component; context_rest_properties: Map; + owner: INode; + number_of_computed_props?: number; }) { if (!node) return; if (node.type === 'Identifier') { contexts.push({ + type: 'DestructuredVariable', key: node as Identifier, modifier, default_modifier }); } else if (node.type === 'RestElement') { contexts.push({ + type: 'DestructuredVariable', key: node.argument as Identifier, modifier, default_modifier @@ -56,7 +73,9 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); context_rest_properties.set((element.argument as Identifier).name, element); } else if (element && element.type === 'AssignmentPattern') { @@ -76,7 +95,9 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); } else { unpack_destructuring({ @@ -86,7 +107,9 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); } }); @@ -105,29 +128,43 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); context_rest_properties.set((property.argument as Identifier).name, property); } else if (property.type === 'Property') { const key = property.key; const value = property.value; - let property_name: any; let new_modifier: (node: Node) => Node; if (property.computed) { // TODO: If the property is computed, ie, { [computed_key]: prop }, the computed_key can be any type of expression. + const computed_property = `#computed_property_${number_of_computed_props}`; + new_modifier = (node) => x`${modifier(node)}[${computed_property}]`; + used_properties.push(x`${computed_property}`); + number_of_computed_props += 1; + + contexts.push({ + type: 'ComputedProperty', + declaration: (block, scope, ctx) => { + const computed_expression = new Expression(component, owner, scope, key); + return b`const ${computed_property} = ${computed_expression.manipulate(block, ctx)}`; + } + }); } else if (key.type === 'Identifier') { // e.g. { someProperty: ... } - property_name = key.name; + const property_name = key.name; new_modifier = (node) => x`${modifier(node)}.${property_name}`; + used_properties.push(x`"${property_name}"`); } else if (key.type === 'Literal') { // e.g. { "property-in-quotes": ... } or { 14: ... } - property_name = key.value; + const property_name = key.value; new_modifier = (node) => x`${modifier(node)}["${property_name}"]`; + used_properties.push(x`"${property_name}"`); } - used_properties.push(x`"${property_name}"`); if (value.type === 'AssignmentPattern') { // e.g. { property = default } or { property: newName = default } const n = contexts.length; @@ -147,7 +184,9 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); } else { // e.g. { property } or { property: newName } @@ -158,7 +197,9 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + owner, + number_of_computed_props }); } } @@ -169,14 +210,17 @@ export function unpack_destructuring({ function update_reference( contexts: Context[], n: number, - expression: Expression, + expression: ESTreeExpression, to_ctx: (name: string) => Node ): Node { const find_from_context = (node: Identifier) => { for (let i = n; i < contexts.length; i++) { - const { key } = contexts[i]; - if (node.name === key.name) { - throw new Error(`Cannot access '${node.name}' before initialization`); + const cur_context = contexts[i]; + if (cur_context.type === 'DestructuredVariable') { + const { key } = cur_context; + if (node.name === key.name) { + throw new Error(`Cannot access '${node.name}' before initialization`); + } } } return to_ctx(node.name); @@ -187,7 +231,7 @@ function update_reference( } // NOTE: avoid unnecessary deep clone? - expression = clone(expression) as Expression; + expression = clone(expression) as ESTreeExpression; walk(expression, { enter(node, parent: Node) { if ( diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 1b1558ec17..3a85bf35c8 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -373,6 +373,7 @@ export default class Expression { // add to get_xxx_context // child_ctx[x] = function () { ... } (template_scope.get_owner(deps[0]) as EachBlock).contexts.push({ + type: 'DestructuredVariable', key: func_id, modifier: () => func_expression, default_modifier: node => node diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 9c07267223..37c12ba623 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -69,7 +69,9 @@ class AwaitBlockBranch extends Wrapper { this.renderer.add_to_context(this.value, true); } else { contexts.forEach(context => { - this.renderer.add_to_context(context.key.name, true); + if (context.type === 'DestructuredVariable') { + this.renderer.add_to_context(context.key.name, true); + } }); this.value = this.block.parent.get_unique_name('value').name; this.value_contexts = contexts; @@ -96,7 +98,14 @@ class AwaitBlockBranch extends Wrapper { } render_get_context() { - const props = this.is_destructured ? this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), name => this.renderer.reference(name))};`) : null; + const props = this.is_destructured ? this.value_contexts.map(prop => { + const to_ctx = name => this.renderer.reference(name); + if (prop.type === 'ComputedProperty') { + return prop.declaration(this.block, this.has_consts(this.node) ? this.node.scope : null, '#ctx'); + } else { + return b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`; + } + }) : null; const const_tags_props = this.has_consts(this.node) ? add_const_tags(this.block, this.node.const_tags, '#ctx') : null; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 1032f4c6e3..5bb3a88b1e 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -86,6 +86,7 @@ export default class EachBlockWrapper extends Wrapper { block.add_dependencies(dependencies); this.node.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; renderer.add_to_context(context.key.name, true); }); add_const_tags_context(renderer, this.node.const_tags); @@ -147,6 +148,7 @@ export default class EachBlockWrapper extends Wrapper { const store = object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null; node.contexts.forEach(prop => { + if (prop.type !== 'DestructuredVariable') return; this.block.bindings.set(prop.key.name, { object: this.vars.each_block_value, property: this.index_name, @@ -361,7 +363,15 @@ export default class EachBlockWrapper extends Wrapper { this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); } - this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), name => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`); + this.context_props = this.node.contexts.map(prop => { + const to_ctx = (name: string) => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; + + if (prop.type === 'DestructuredVariable') { + return b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`; + } else { + return prop.declaration(block, this.node.scope, 'child_ctx'); + } + }); if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts b/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts index cd4dc7f033..21fac5f877 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts @@ -1,6 +1,6 @@ import ConstTag from '../../../nodes/ConstTag'; import Block from '../../Block'; -import { b, x } from 'code-red'; +import { b, Node, x } from 'code-red'; import Renderer from '../../Renderer'; export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) { @@ -8,8 +8,14 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string const_tags.forEach((const_tag, i) => { const name = `#constants_${i}`; const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`); + const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; + const_tag.contexts.forEach(context => { - const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), name => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`); + if (context.type === 'DestructuredVariable') { + const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`); + } else { + const_tags_props.push(context.declaration(block, const_tag.scope, ctx)); + } }); }); return const_tags_props; @@ -18,6 +24,7 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) { const_tags.forEach(const_tag => { const_tag.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; renderer.add_to_context(context.key.name, true); }); });