From b869e48bfffaec42f73fdd80ff9a9bcb3467678e Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 4 Aug 2017 21:36:14 -0400 Subject: [PATCH 1/4] saner escaping/unescaping of `@` and `#` sigils --- src/generators/dom/Block.ts | 4 ++-- src/generators/dom/index.ts | 12 +++++++----- src/generators/server-side-rendering/index.ts | 16 +++++++--------- .../server-side-rendering/visitors/Component.ts | 3 ++- src/utils/stringify.ts | 4 +++- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 70323ae5cfb3..4c494de794a2 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -355,8 +355,8 @@ export default class Block { ${properties} }; } - `.replace(/(\\\\)?#(\w*)/g, (match, escaped, name) => { - return escaped ? match.slice(2) : this.alias(name); + `.replace(/(#+)(\w*)/g, (match: string, sigil: string, name: string) => { + return sigil === '#' ? this.alias(name) : sigil.slice(1) + name; }); } } diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index e2cbc4751c1c..4458a3f9d4a5 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -145,9 +145,12 @@ export default function dom( if (generator.stylesheet.hasStyles && options.css !== false) { const { css, cssMap } = generator.stylesheet.render(options.filename); - const textContent = stringify(options.dev ? + // special case: we only want to escape '@' and not '#', because at this point all we'll be unescaping is '@' + const textContent = JSON.stringify((options.dev ? `${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` : - css); + css).replace(/(@+)/g, (match: string) => { + return match + match[0]; + })); builder.addBlock(deindent` function @add_css () { @@ -281,9 +284,8 @@ export default function dom( let result = builder .toString() - .replace(/(\\\\)?([@#])(\w*)/g, (match: string, escaped: string, sigil: string, name: string) => { - if (escaped) return match.slice(2); - if (sigil !== '@') return match; + .replace(/(@+)(\w*)/g, (match: string, sigil: string, name: string) => { + if (sigil !== '@') return sigil.slice(1) + name; if (name in shared) { if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 59f9701e7a9e..0f1771da7ad6 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -6,6 +6,7 @@ import preprocess from './preprocess'; import visit from './visit'; import { removeNode, removeObjectKey } from '../../utils/removeNode'; import { Parsed, Node, CompileOptions } from '../../interfaces'; +import { stringify } from '../../utils/stringify'; export class SsrGenerator extends Generator { bindings: string[]; @@ -93,7 +94,7 @@ export default function ssr( var ${name} = {}; - ${name}.filename = ${JSON.stringify(options.filename)}; + ${name}.filename = ${stringify(options.filename)}; ${name}.data = function () { return ${templateProperties.data ? `@template.data()` : `{}`}; @@ -133,8 +134,8 @@ export default function ssr( deindent` components.push({ filename: ${name}.filename, - css: ${JSON.stringify(css)}, - map: ${JSON.stringify(cssMap)} + css: ${stringify(css)}, + map: ${stringify(cssMap.toString())} }); `} @@ -169,7 +170,7 @@ export default function ssr( var escaped = { '"': '"', - "'": ''', + "'": '&##39;', '&': '&', '<': '<', '>': '>' @@ -178,11 +179,8 @@ export default function ssr( function __escape ( html ) { return String( html ).replace( /["'&<>]/g, match => escaped[ match ] ); } - `.replace(/(\\)?([@#])(\w*)/g, (match: string, escaped: string, sigil: string, name: string) => { - if (escaped) return match.slice(1); - if (sigil !== '@') return match; - - return generator.alias(name); + `.replace(/(@+|#+)(\w*)/g, (match: string, sigil: string, name: string) => { + return sigil === '@' ? generator.alias(name) : sigil.slice(1) + name; }); return generator.generate(result, options, { name, format }); diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 75785d6ffd8d..0b04a13b11ad 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -5,6 +5,7 @@ import Block from '../Block'; import { Node } from '../../../interfaces'; import getObject from '../../../utils/getObject'; import getTailSnippet from '../../../utils/getTailSnippet'; +import { stringify } from '../../../utils/stringify'; export default function visitComponent( generator: SsrGenerator, @@ -41,7 +42,7 @@ export default function visitComponent( } else if (attribute.value.length === 1) { const chunk = attribute.value[0]; if (chunk.type === 'Text') { - value = isNaN(chunk.data) ? JSON.stringify(chunk.data) : chunk.data; + value = isNaN(chunk.data) ? stringify(chunk.data) : chunk.data; } else { const { snippet } = block.contextualise(chunk.expression); value = snippet; diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index c16ac7a9371e..de9f223d9aba 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -3,5 +3,7 @@ export function stringify(data: string) { } export function escape(data: string) { - return data.replace(/([^\\@#])?([@#])/g, '$1\\$2'); + return data.replace(/(@+|#+)/g, (match: string) => { + return match + match[0]; + }); } From 0e9f16b58954f108ecd4bb7dcf25817f8b2f1dc7 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 5 Aug 2017 06:22:00 -0400 Subject: [PATCH 2/4] don't over-escape '#' in sharedPath --- src/generators/dom/index.ts | 11 ++++------- src/utils/stringify.ts | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 4458a3f9d4a5..fc9a2457e426 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -145,12 +145,9 @@ export default function dom( if (generator.stylesheet.hasStyles && options.css !== false) { const { css, cssMap } = generator.stylesheet.render(options.filename); - // special case: we only want to escape '@' and not '#', because at this point all we'll be unescaping is '@' - const textContent = JSON.stringify((options.dev ? + const textContent = stringify(options.dev ? `${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` : - css).replace(/(@+)/g, (match: string) => { - return match + match[0]; - })); + css, { onlyEscapeAtSymbol: true }); builder.addBlock(deindent` function @add_css () { @@ -304,13 +301,13 @@ export default function dom( }); result = - `import { ${names.join(', ')} } from ${stringify(sharedPath)};\n\n` + + `import { ${names.join(', ')} } from ${stringify(sharedPath, { onlyEscapeAtSymbol: true })};\n\n` + result; } else if (format === 'cjs') { const SHARED = '__shared'; - let requires = `var ${SHARED} = require( ${stringify(sharedPath)} );`; + let requires = `var ${SHARED} = require( ${stringify(sharedPath, { onlyEscapeAtSymbol: true })} );`; used.forEach(name => { const alias = generator.alias(name); requires += `\nvar ${alias} = ${SHARED}.${name};`; diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index de9f223d9aba..ab83bec5b459 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -1,9 +1,9 @@ -export function stringify(data: string) { - return JSON.stringify(escape(data)); +export function stringify(data: string, options = {}) { + return JSON.stringify(escape(data, options)); } -export function escape(data: string) { - return data.replace(/(@+|#+)/g, (match: string) => { +export function escape(data: string, { onlyEscapeAtSymbol = false } = {}) { + return data.replace(onlyEscapeAtSymbol ? /(@+)/g : /(@+|#+)/g, (match: string) => { return match + match[0]; }); } From 6ef59234a3f37db00cbd7422ba30e5166e657a69 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 5 Aug 2017 06:31:40 -0400 Subject: [PATCH 3/4] add test --- .../samples/component-static-at-symbol/Email.html | 1 + .../samples/component-static-at-symbol/_config.js | 3 +++ .../runtime/samples/component-static-at-symbol/main.html | 9 +++++++++ 3 files changed, 13 insertions(+) create mode 100644 test/runtime/samples/component-static-at-symbol/Email.html create mode 100644 test/runtime/samples/component-static-at-symbol/_config.js create mode 100644 test/runtime/samples/component-static-at-symbol/main.html diff --git a/test/runtime/samples/component-static-at-symbol/Email.html b/test/runtime/samples/component-static-at-symbol/Email.html new file mode 100644 index 000000000000..629ae71a0457 --- /dev/null +++ b/test/runtime/samples/component-static-at-symbol/Email.html @@ -0,0 +1 @@ +email diff --git a/test/runtime/samples/component-static-at-symbol/_config.js b/test/runtime/samples/component-static-at-symbol/_config.js new file mode 100644 index 000000000000..a2f60e3de70a --- /dev/null +++ b/test/runtime/samples/component-static-at-symbol/_config.js @@ -0,0 +1,3 @@ +export default { + html: `email` +}; diff --git a/test/runtime/samples/component-static-at-symbol/main.html b/test/runtime/samples/component-static-at-symbol/main.html new file mode 100644 index 000000000000..f8a0e1c6cda6 --- /dev/null +++ b/test/runtime/samples/component-static-at-symbol/main.html @@ -0,0 +1,9 @@ + + + From db4f148025ee2a1f388a00466b95472d307f852b Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 5 Aug 2017 06:38:00 -0400 Subject: [PATCH 4/4] actually, we don't want to escape @ or # in sharedPath at all --- src/generators/dom/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index fc9a2457e426..6fa230d1eb5d 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -301,13 +301,13 @@ export default function dom( }); result = - `import { ${names.join(', ')} } from ${stringify(sharedPath, { onlyEscapeAtSymbol: true })};\n\n` + + `import { ${names.join(', ')} } from ${JSON.stringify(sharedPath)};\n\n` + result; } else if (format === 'cjs') { const SHARED = '__shared'; - let requires = `var ${SHARED} = require( ${stringify(sharedPath, { onlyEscapeAtSymbol: true })} );`; + let requires = `var ${SHARED} = require( ${JSON.stringify(sharedPath)} );`; used.forEach(name => { const alias = generator.alias(name); requires += `\nvar ${alias} = ${SHARED}.${name};`;