From 7143dd3ff52486e0770e17546385df934808ae90 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Mon, 22 Nov 2021 12:04:58 +0100 Subject: [PATCH] [FIX] components: capture context in prop expressions --- src/compiler/code_generator.ts | 23 ++++-- .../__snapshots__/event_handling.test.ts.snap | 72 +++++++------------ .../compiler/__snapshots__/misc.test.ts.snap | 42 ++++------- .../__snapshots__/concurrency.test.ts.snap | 5 +- .../__snapshots__/event_handling.test.ts.snap | 34 +++++++-- .../higher_order_component.test.ts.snap | 3 +- .../__snapshots__/lifecycle.test.ts.snap | 3 +- .../__snapshots__/props.test.ts.snap | 36 ++++++++++ .../__snapshots__/refs.test.ts.snap | 3 +- .../__snapshots__/slots.test.ts.snap | 32 ++++----- .../__snapshots__/t_call.test.ts.snap | 3 +- .../__snapshots__/t_model.test.ts.snap | 28 ++++---- .../__snapshots__/t_on.test.ts.snap | 3 +- tests/components/event_handling.test.ts | 21 ++++++ tests/components/props.test.ts | 25 +++++++ tests/misc/__snapshots__/portal.test.ts.snap | 3 +- 16 files changed, 202 insertions(+), 134 deletions(-) diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 536c506c..ce5a13a8 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -344,8 +344,23 @@ export class CodeGenerator { } this.addLine(`}`); } - - captureExpression(expr: string): string { + /** + * Captures variables that are used inside of an expression. This is useful + * because in compiled code, almost all variables are accessed through the ctx + * object. In the case of functions, that lookup in the context can be delayed + * which can cause issues if the value has changed since the function was + * defined. + * + * @param expr the expression to capture + * @param forceCapture whether the expression should capture its scope even if + * it doesn't contain a function. Useful when the expression will be used as + * a function body. + * @returns a new expression that uses the captured values + */ + captureExpression(expr: string, forceCapture: boolean = false): string { + if (!forceCapture && !expr.includes("=>")) { + return compileExpr(expr); + } const tokens = compileExprToArray(expr); const mapping = new Map(); return tokens @@ -535,7 +550,7 @@ export class CodeGenerator { if (isDynamic) { const str = ast.ref.replace( INTERP_REGEXP, - (expr) => "${" + this.captureExpression(expr.slice(2, -2)) + "}" + (expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}" ); const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`); attrs["block-ref"] = String(idx); @@ -952,7 +967,7 @@ export class CodeGenerator { // props const props: string[] = []; for (let p in ast.props) { - props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`); + props.push(`${p}: ${this.captureExpression(ast.props[p]) || undefined}`); } const propStr = `{${props.join(",")}}`; diff --git a/tests/compiler/__snapshots__/event_handling.test.ts.snap b/tests/compiler/__snapshots__/event_handling.test.ts.snap index 952d042b..6a330be3 100644 --- a/tests/compiler/__snapshots__/event_handling.test.ts.snap +++ b/tests/compiler/__snapshots__/event_handling.test.ts.snap @@ -9,8 +9,7 @@ exports[`t-on can bind event handler 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; return block1([d1]); } }" @@ -116,10 +115,8 @@ exports[`t-on can bind two event handlers 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['handleClick']; - let d1 = [v1, ctx]; - const v2 = ctx['handleDblClick']; - let d2 = [v2, ctx]; + let d1 = [ctx['handleClick'], ctx]; + let d2 = [ctx['handleDblClick'], ctx]; return block1([d1, d2]); } }" @@ -134,8 +131,7 @@ exports[`t-on handler is bound to proper owner 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; return block1([d1]); } }" @@ -155,8 +151,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = ` for (let i1 = 0; i1 < l_block1; i1++) { ctx[\`value\`] = v_block1[i1]; let key1 = ctx['value']; - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; c_block1[i1] = withKey(block2([d1]), key1); } return list(c_block1); @@ -173,8 +168,7 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; return block1([d1]); } }" @@ -202,8 +196,7 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; return block1([d1]); } }" @@ -242,8 +235,7 @@ exports[`t-on receive event in first argument 1`] = ` let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['add']; - let d1 = [v1, ctx]; + let d1 = [ctx['add'], ctx]; return block1([d1]); } }" @@ -258,10 +250,8 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['divClicked']; - let d1 = [v1, ctx]; - const v2 = ctx['btnClicked']; - let d2 = [v2, ctx]; + let d1 = [ctx['divClicked'], ctx]; + let d2 = [ctx['btnClicked'], ctx]; return block1([d1, d2]); } }" @@ -276,8 +266,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = ` let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClick']; - let d1 = [v1, ctx]; + let d1 = [ctx['onClick'], ctx]; let d2 = ctx['text']; return block1([d1, d2]); } @@ -293,8 +282,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = ` let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClick']; - let d1 = [v1, ctx]; + let d1 = [ctx['onClick'], ctx]; let b2 = safeOutput(ctx['html']); return block1([d1], [b2]); } @@ -310,10 +298,8 @@ exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] = let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onCapture']; - let d1 = [\\"capture\\", v1, ctx]; - const v2 = ctx['doSomething']; - let d2 = [v2, ctx]; + let d1 = [\\"capture\\", ctx['onCapture'], ctx]; + let d2 = [ctx['doSomething'], ctx]; return block1([d1, d2]); } }" @@ -343,8 +329,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifi let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClick']; - let d1 = [\\"prevent\\",\\"self\\", v1, ctx]; + let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx]; return block1([d1]); } }" @@ -359,12 +344,9 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop mod let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClickPrevented']; - let d1 = [\\"prevent\\", v1, ctx]; - const v2 = ctx['onClickStopped']; - let d2 = [\\"stop\\", v2, ctx]; - const v3 = ctx['onClickPreventedAndStopped']; - let d3 = [\\"prevent\\",\\"stop\\", v3, ctx]; + let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx]; + let d2 = [\\"stop\\", ctx['onClickStopped'], ctx]; + let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx]; return block1([d1, d2, d3]); } }" @@ -406,8 +388,7 @@ exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifi let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClick']; - let d1 = [\\"self\\",\\"prevent\\", v1, ctx]; + let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx]; return block1([d1]); } }" @@ -422,10 +403,8 @@ exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = ` let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['onClick']; - let d1 = [v1, ctx]; - const v2 = ctx['onClickSelf']; - let d2 = [\\"self\\", v2, ctx]; + let d1 = [ctx['onClick'], ctx]; + let d2 = [\\"self\\", ctx['onClickSelf'], ctx]; return block1([d1, d2]); } }" @@ -440,10 +419,8 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1` let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['divClicked']; - let d1 = [\\"synthetic\\", v1, ctx]; - const v2 = ctx['btnClicked']; - let d2 = [\\"synthetic\\", v2, ctx]; + let d1 = [\\"synthetic\\", ctx['divClicked'], ctx]; + let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx]; return block1([d1, d2]); } }" @@ -523,8 +500,7 @@ exports[`t-on t-on with t-call 1`] = ` let block1 = createBlock(\`

lucas

\`); return function template(ctx, node, key = \\"\\") { - const v1 = ctx['update']; - let d1 = [v1, ctx]; + let d1 = [ctx['update'], ctx]; return block1([d1]); } }" diff --git a/tests/compiler/__snapshots__/misc.test.ts.snap b/tests/compiler/__snapshots__/misc.test.ts.snap index 6d5d8bc2..ec85c11a 100644 --- a/tests/compiler/__snapshots__/misc.test.ts.snap +++ b/tests/compiler/__snapshots__/misc.test.ts.snap @@ -182,7 +182,7 @@ exports[`misc other complex template 1`] = ` ) { let { text, createBlock, list, multi, html, toggler, component } = bdom; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; - const callTemplate_14 = getTemplate(\`LOAD_INFOS_TEMPLATE\`); + const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`); let block1 = createBlock(\`