diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 38b51973f..ed1d97bf8 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -82,6 +82,14 @@ function isProp(tag: string, key: string): boolean { return false; } +/** + * Returns a template literal that evaluates to str. You can add interpolation + * sigils into the string if required + */ +function toStringExpression(str: string) { + return `\`${str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/, "\\${")}\``; +} + // ----------------------------------------------------------------------------- // BlockDescription // ----------------------------------------------------------------------------- @@ -311,14 +319,13 @@ export class CodeGenerator { mainCode.push(``); for (let block of this.blocks) { if (block.dom) { - let xmlString = block.asXmlString(); - xmlString = xmlString.replace(/\\/g, "\\\\").replace(/`/g, "\\`"); + let xmlString = toStringExpression(block.asXmlString()); if (block.dynamicTagName) { - xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`); - xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`); - mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`); + xmlString = xmlString.replace(/^`<\w+/, `\`<\${tag || '${block.dom.nodeName}'}`); + xmlString = xmlString.replace(/\w+>`$/, `\${tag || '${block.dom.nodeName}'}>\``); + mainCode.push(`let ${block.blockName} = tag => createBlock(${xmlString});`); } else { - mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`); + mainCode.push(`let ${block.blockName} = createBlock(${xmlString});`); } } } @@ -515,7 +522,7 @@ export class CodeGenerator { const isNewBlock = !block || forceNewBlock; if (isNewBlock) { block = this.createBlock(block, "comment", ctx); - this.insertBlock(`comment(\`${ast.value}\`)`, block, { + this.insertBlock(`comment(${toStringExpression(ast.value)})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block, }); @@ -539,7 +546,7 @@ export class CodeGenerator { if (!block || forceNewBlock) { block = this.createBlock(block, "text", ctx); - this.insertBlock(`text(\`${value}\`)`, block, { + this.insertBlock(`text(${toStringExpression(value)})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block, }); @@ -774,7 +781,8 @@ export class CodeGenerator { expr = compileExpr(ast.expr); if (ast.defaultValue) { this.helpers.add("withDefault"); - expr = `withDefault(${expr}, \`${ast.defaultValue}\`)`; + // FIXME: defaultValue is not translated + expr = `withDefault(${expr}, ${toStringExpression(ast.defaultValue)})`; } } if (!block || forceNewBlock) { @@ -1039,7 +1047,7 @@ export class CodeGenerator { } } - const key = `key + \`${this.generateComponentKey()}\``; + const key = this.generateComponentKey(); if (isDynamic) { const templateVar = generateId("template"); if (!this.staticDefs.find((d) => d.id === "call")) { @@ -1091,11 +1099,13 @@ export class CodeGenerator { } else { let value: string; if (ast.defaultValue) { - const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue; + const defaultValue = toStringExpression( + ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue + ); if (ast.value) { - value = `withDefault(${expr}, \`${defaultValue}\`)`; + value = `withDefault(${expr}, ${defaultValue})`; } else { - value = `\`${defaultValue}\``; + value = defaultValue; } } else { value = expr; @@ -1106,12 +1116,12 @@ export class CodeGenerator { return null; } - generateComponentKey() { + generateComponentKey(currentKey: string = "key") { const parts = [generateId("__")]; for (let i = 0; i < this.target.loopLevel; i++) { parts.push(`\${key${i + 1}}`); } - return parts.join("__"); + return `${currentKey} + \`${parts.join("__")}\``; } /** @@ -1214,7 +1224,6 @@ export class CodeGenerator { } // cmap key - const key = this.generateComponentKey(); let expr: string; if (ast.isDynamic) { expr = generateId("Comp"); @@ -1232,7 +1241,7 @@ export class CodeGenerator { this.insertAnchor(block); } - let keyArg = `key + \`${key}\``; + let keyArg = this.generateComponentKey(); if (ctx.tKeyExpr) { keyArg = `${ctx.tKeyExpr} + ${keyArg}`; } @@ -1311,7 +1320,7 @@ export class CodeGenerator { } let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key"; if (isMultiple) { - key = `${key} + \`${this.generateComponentKey()}\``; + key = this.generateComponentKey(key); } const props = ast.attrs ? this.formatPropObject(ast.attrs) : []; @@ -1354,7 +1363,6 @@ export class CodeGenerator { let { block } = ctx; const name = this.compileInNewTarget("slot", ast.content, ctx); - const key = this.generateComponentKey(); let ctxStr = "ctx"; if (this.target.loopLevel || !this.hasSafeContext) { ctxStr = generateId("ctx"); @@ -1368,7 +1376,8 @@ export class CodeGenerator { }); const target = compileExpr(ast.target); - const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`; + const key = this.generateComponentKey(); + const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`; if (block) { this.insertAnchor(block); } diff --git a/tests/compiler/__snapshots__/comments.test.ts.snap b/tests/compiler/__snapshots__/comments.test.ts.snap index 3547aed8d..8329cf1c8 100644 --- a/tests/compiler/__snapshots__/comments.test.ts.snap +++ b/tests/compiler/__snapshots__/comments.test.ts.snap @@ -1,5 +1,38 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`comments comment node with backslash at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return comment(\` \\\\\\\\ \`); + } +}" +`; + +exports[`comments comment node with backtick at top-level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return comment(\` \\\\\` \`); + } +}" +`; + +exports[`comments comment node with interpolation sigil at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return comment(\` \\\\\${very cool} \`); + } +}" +`; + exports[`comments only a comment 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/compiler/__snapshots__/simple_templates.test.ts.snap b/tests/compiler/__snapshots__/simple_templates.test.ts.snap index e6e75add7..aff0be3f1 100644 --- a/tests/compiler/__snapshots__/simple_templates.test.ts.snap +++ b/tests/compiler/__snapshots__/simple_templates.test.ts.snap @@ -341,6 +341,39 @@ exports[`simple templates, mostly static template with t tag with multiple conte }" `; +exports[`simple templates, mostly static text node with backslash at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return text(\`\\\\\\\\\`); + } +}" +`; + +exports[`simple templates, mostly static text node with backtick at top-level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return text(\`\\\\\`\`); + } +}" +`; + +exports[`simple templates, mostly static text node with interpolation sigil at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return text(\`\\\\\${very cool}\`); + } +}" +`; + exports[`simple templates, mostly static two t-escs next to each other 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/compiler/__snapshots__/t_esc.test.ts.snap b/tests/compiler/__snapshots__/t_esc.test.ts.snap index e5a165bac..f623df5f4 100644 --- a/tests/compiler/__snapshots__/t_esc.test.ts.snap +++ b/tests/compiler/__snapshots__/t_esc.test.ts.snap @@ -1,5 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`t-esc default with backslash at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { withDefault } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(withDefault(undefined, \`\\\\\\\\\`)); + } +}" +`; + +exports[`t-esc default with backtick at top-level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { withDefault } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(withDefault(undefined, \`\\\\\`\`)); + } +}" +`; + +exports[`t-esc default with interpolation sigil at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { withDefault } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(withDefault(undefined, \`\\\\\${very cool}\`)); + } +}" +`; + exports[`t-esc div with falsy values 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/compiler/__snapshots__/t_set.test.ts.snap b/tests/compiler/__snapshots__/t_set.test.ts.snap index b4320fe5e..f858704e2 100644 --- a/tests/compiler/__snapshots__/t_set.test.ts.snap +++ b/tests/compiler/__snapshots__/t_set.test.ts.snap @@ -1,5 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`t-set body with backslash at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { isBoundary, withDefault, setContextValue } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[isBoundary] = 1 + setContextValue(ctx, \\"value\\", \`\\\\\\\\\`); + return text(ctx['value']); + } +}" +`; + +exports[`t-set body with backtick at top-level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { isBoundary, withDefault, setContextValue } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[isBoundary] = 1 + setContextValue(ctx, \\"value\\", \`\\\\\`\`); + return text(ctx['value']); + } +}" +`; + +exports[`t-set body with interpolation sigil at top level 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { isBoundary, withDefault, setContextValue } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[isBoundary] = 1 + setContextValue(ctx, \\"value\\", \`\\\\\${very cool}\`); + return text(ctx['value']); + } +}" +`; + exports[`t-set evaluate value expression 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/compiler/comments.test.ts b/tests/compiler/comments.test.ts index 6b45b53a0..f0fee500b 100644 --- a/tests/compiler/comments.test.ts +++ b/tests/compiler/comments.test.ts @@ -26,4 +26,19 @@ describe("comments", () => { `; expect(renderToString(template)).toBe("
true
"); }); + + test("comment node with backslash at top level", () => { + const template = ""; + expect(renderToString(template)).toBe(""); + }); + + test("comment node with backtick at top-level", () => { + const template = ""; + expect(renderToString(template)).toBe(""); + }); + + test("comment node with interpolation sigil at top level", () => { + const template = ""; + expect(renderToString(template)).toBe(""); + }); }); diff --git a/tests/compiler/simple_templates.test.ts b/tests/compiler/simple_templates.test.ts index 3e62da8e2..61cc89a43 100644 --- a/tests/compiler/simple_templates.test.ts +++ b/tests/compiler/simple_templates.test.ts @@ -154,4 +154,19 @@ describe("simple templates, mostly static", () => { `; expect(renderToString(template, { a: "a", b: "b", c: "c" })).toBe("
abLoadingc
"); }); + + test("text node with backslash at top level", () => { + const template = "\\"; + expect(renderToString(template)).toBe("\\"); + }); + + test("text node with backtick at top-level", () => { + const template = "`"; + expect(renderToString(template)).toBe("`"); + }); + + test("text node with interpolation sigil at top level", () => { + const template = "${very cool}"; + expect(renderToString(template)).toBe("${very cool}"); + }); }); diff --git a/tests/compiler/t_esc.test.ts b/tests/compiler/t_esc.test.ts index ba9003fc6..8782a973f 100644 --- a/tests/compiler/t_esc.test.ts +++ b/tests/compiler/t_esc.test.ts @@ -121,4 +121,19 @@ describe("t-esc", () => { mount(bdom, fixture); expect(fixture.querySelector("span")!.textContent).toBe("

escaped

"); }); + + test("default with backslash at top level", () => { + const template = '\\'; + expect(renderToString(template)).toBe("\\"); + }); + + test("default with backtick at top-level", () => { + const template = '`'; + expect(renderToString(template)).toBe("`"); + }); + + test("default with interpolation sigil at top level", () => { + const template = '${very cool}'; + expect(renderToString(template)).toBe("${very cool}"); + }); }); diff --git a/tests/compiler/t_set.test.ts b/tests/compiler/t_set.test.ts index 030a15b41..2ce549088 100644 --- a/tests/compiler/t_set.test.ts +++ b/tests/compiler/t_set.test.ts @@ -54,6 +54,21 @@ describe("t-set", () => { expect(renderToString(template)).toBe("ok"); }); + test("body with backslash at top level", () => { + const template = '\\'; + expect(renderToString(template)).toBe("\\"); + }); + + test("body with backtick at top-level", () => { + const template = '`'; + expect(renderToString(template)).toBe("`"); + }); + + test("body with interpolation sigil at top level", () => { + const template = '${very cool}'; + expect(renderToString(template)).toBe("${very cool}"); + }); + test("set from body literal (with t-if/t-else", () => { const template = `