From c62f77a5d2874570f92ca6936df7d64eee7f4ae1 Mon Sep 17 00:00:00 2001 From: Christopher Pfohl Date: Wed, 7 Feb 2018 05:46:32 -0500 Subject: [PATCH 01/13] Empty Each Blocks should have consistent behavior. Here the `error-each-blocks-empty` behaves as expected. It throws a ParseError. I can't get the whitespace errors to repro in the tests. They're easily seen here: https://svelte.technology/repl?version=1.51.0&gist=e1596f9631619a689e85c80e46506692 If you uncomment each section in sequence it demonstrates the behavior. I can't quite figure out why my examples don't work. --- test/parser/samples/error-comment-unclosed/error.json | 8 ++++++++ test/parser/samples/error-comment-unclosed/input.html | 1 + test/parser/samples/error-each-blocks-empty/error.json | 8 ++++++++ test/parser/samples/error-each-blocks-empty/input.html | 1 + .../samples/error-each-blocks-keyed-whitespace/error.json | 8 ++++++++ .../samples/error-each-blocks-keyed-whitespace/input.html | 8 ++++++++ .../samples/error-each-blocks-whitespace/error.json | 8 ++++++++ .../samples/error-each-blocks-whitespace/input.html | 2 ++ 8 files changed, 44 insertions(+) create mode 100644 test/parser/samples/error-comment-unclosed/error.json create mode 100644 test/parser/samples/error-comment-unclosed/input.html create mode 100644 test/parser/samples/error-each-blocks-empty/error.json create mode 100644 test/parser/samples/error-each-blocks-empty/input.html create mode 100644 test/parser/samples/error-each-blocks-keyed-whitespace/error.json create mode 100644 test/parser/samples/error-each-blocks-keyed-whitespace/input.html create mode 100644 test/parser/samples/error-each-blocks-whitespace/error.json create mode 100644 test/parser/samples/error-each-blocks-whitespace/input.html diff --git a/test/parser/samples/error-comment-unclosed/error.json b/test/parser/samples/error-comment-unclosed/error.json new file mode 100644 index 000000000000..2502a327357b --- /dev/null +++ b/test/parser/samples/error-comment-unclosed/error.json @@ -0,0 +1,8 @@ +{ + "message": "comment was left open", + "loc": { + "line": 1, + "column": 0 + }, + "pos": 0 +} diff --git a/test/parser/samples/error-comment-unclosed/input.html b/test/parser/samples/error-comment-unclosed/input.html new file mode 100644 index 000000000000..725cf9be6088 --- /dev/null +++ b/test/parser/samples/error-comment-unclosed/input.html @@ -0,0 +1 @@ +/); - parser.eat('-->'); + parser.eat('-->', true, 'comment was left open, expected -->'); parser.current().children.push({ start, diff --git a/test/parser/samples/error-comment-unclosed/error.json b/test/parser/samples/error-comment-unclosed/error.json index 2502a327357b..f392e12fa180 100644 --- a/test/parser/samples/error-comment-unclosed/error.json +++ b/test/parser/samples/error-comment-unclosed/error.json @@ -1,8 +1,8 @@ { - "message": "comment was left open", + "message": "comment was left open, expected -->", "loc": { "line": 1, - "column": 0 + "column": 24 }, - "pos": 0 + "pos": 24 } diff --git a/test/parser/samples/error-each-blocks-keyed-whitespace/input.html b/test/parser/samples/error-each-blocks-keyed-whitespace/input.html index c801984a56d0..6984d4ae88bc 100644 --- a/test/parser/samples/error-each-blocks-keyed-whitespace/input.html +++ b/test/parser/samples/error-each-blocks-keyed-whitespace/input.html @@ -1,11 +1,11 @@ {{#each foos as foo @id}} {{/each}} From ff67b137c4111100280d58c96c2c777318a7d89c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 11:46:23 -0500 Subject: [PATCH 04/13] empty blocks are a dev warning, not an error --- src/parse/state/mustache.ts | 9 --------- src/validate/html/index.ts | 11 +++++++++++ src/validate/index.ts | 5 +++-- .../parser/samples/error-each-blocks-empty/input.html | 1 - .../error-each-blocks-keyed-whitespace/error.json | 8 -------- .../error-each-blocks-keyed-whitespace/input.html | 11 ----------- .../samples/error-each-blocks-whitespace/error.json | 8 -------- .../samples/error-each-blocks-whitespace/input.html | 11 ----------- test/validator/index.js | 6 ++++-- .../validator/samples/empty-block-dev.solo/_config.js | 3 +++ .../validator/samples/empty-block-dev.solo/input.html | 3 +++ .../samples/empty-block-dev.solo/warnings.json} | 4 ++-- test/validator/samples/empty-block-prod/input.html | 3 +++ test/validator/samples/empty-block-prod/warnings.json | 1 + 14 files changed, 30 insertions(+), 54 deletions(-) delete mode 100644 test/parser/samples/error-each-blocks-empty/input.html delete mode 100644 test/parser/samples/error-each-blocks-keyed-whitespace/error.json delete mode 100644 test/parser/samples/error-each-blocks-keyed-whitespace/input.html delete mode 100644 test/parser/samples/error-each-blocks-whitespace/error.json delete mode 100644 test/parser/samples/error-each-blocks-whitespace/input.html create mode 100644 test/validator/samples/empty-block-dev.solo/_config.js create mode 100644 test/validator/samples/empty-block-dev.solo/input.html rename test/{parser/samples/error-each-blocks-empty/error.json => validator/samples/empty-block-dev.solo/warnings.json} (93%) create mode 100644 test/validator/samples/empty-block-prod/input.html create mode 100644 test/validator/samples/empty-block-prod/warnings.json diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 20331368d8e9..6e44f7c15ec8 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -5,13 +5,6 @@ import reservedNames from '../../utils/reservedNames'; import { Parser } from '../index'; import { Node } from '../../interfaces'; -function isEmpty(nodes: Node[]) { - if (!nodes || nodes.length > 1) return false; - if (nodes.length === 0) return true; - if (nodes.length > 1) return false; - return nodes[0].type === 'Text' && !/\S/.test(nodes[0].data); -} - function trimWhitespace(block: Node, trimBefore: boolean, trimAfter: boolean) { if (!block.children) return; // AwaitBlock @@ -81,8 +74,6 @@ export default function mustache(parser: Parser) { } // strip leading/trailing whitespace as necessary - if (isEmpty(block.children)) parser.error(`Empty block`, block.start); - const charBefore = parser.template[block.start - 1]; const charAfter = parser.template[parser.index]; const trimBefore = !charBefore || whitespace.test(charBefore); diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index b2a190696f22..9ae50baca14f 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -12,6 +12,13 @@ const meta = new Map([ [':Head', validateHead] ]); +function isEmptyBlock(node: Node) { + if (!/Block$/.test(node.type) || !node.children) return false; + if (node.children.length > 1) return false; + const child = node.children[0]; + return !child || (child.type === 'Text' && !/\S/.test(child.data)); +} + export default function validateHtml(validator: Validator, html: Node) { const refs = new Map(); const refCallees: Node[] = []; @@ -58,6 +65,10 @@ export default function validateHtml(validator: Validator, html: Node) { } } + if (validator.options.dev && isEmptyBlock(node)) { + validator.warn('Empty block', node.start); + } + if (node.children) { if (node.type === 'Element') elementStack.push(node); stack.push(node); diff --git a/src/validate/index.ts b/src/validate/index.ts index b57f2b0c2a1d..da603a437b6c 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -93,7 +93,7 @@ export default function validate( stylesheet: Stylesheet, options: CompileOptions ) { - const { onwarn, onerror, name, filename, store } = options; + const { onwarn, onerror, name, filename, store, dev } = options; try { if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { @@ -114,7 +114,8 @@ export default function validate( onwarn, name, filename, - store + store, + dev }); if (parsed.js) { diff --git a/test/parser/samples/error-each-blocks-empty/input.html b/test/parser/samples/error-each-blocks-empty/input.html deleted file mode 100644 index 00b4b07d5a99..000000000000 --- a/test/parser/samples/error-each-blocks-empty/input.html +++ /dev/null @@ -1 +0,0 @@ -{{#each foos as foo}}{{/each}} diff --git a/test/parser/samples/error-each-blocks-keyed-whitespace/error.json b/test/parser/samples/error-each-blocks-keyed-whitespace/error.json deleted file mode 100644 index 6ecdc198bd9f..000000000000 --- a/test/parser/samples/error-each-blocks-keyed-whitespace/error.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "message": "Empty block", - "loc": { - "line": 1, - "column": 0 - }, - "pos": 0 -} diff --git a/test/parser/samples/error-each-blocks-keyed-whitespace/input.html b/test/parser/samples/error-each-blocks-keyed-whitespace/input.html deleted file mode 100644 index 6984d4ae88bc..000000000000 --- a/test/parser/samples/error-each-blocks-keyed-whitespace/input.html +++ /dev/null @@ -1,11 +0,0 @@ -{{#each foos as foo @id}} -{{/each}} - diff --git a/test/parser/samples/error-each-blocks-whitespace/error.json b/test/parser/samples/error-each-blocks-whitespace/error.json deleted file mode 100644 index 6ecdc198bd9f..000000000000 --- a/test/parser/samples/error-each-blocks-whitespace/error.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "message": "Empty block", - "loc": { - "line": 1, - "column": 0 - }, - "pos": 0 -} diff --git a/test/parser/samples/error-each-blocks-whitespace/input.html b/test/parser/samples/error-each-blocks-whitespace/input.html deleted file mode 100644 index 2b4af0cac62c..000000000000 --- a/test/parser/samples/error-each-blocks-whitespace/input.html +++ /dev/null @@ -1,11 +0,0 @@ -{{#each foos as foo}} -{{/each}} - diff --git a/test/validator/index.js b/test/validator/index.js index 176d060faf47..0a132a7c9e51 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -1,6 +1,6 @@ import * as fs from "fs"; import assert from "assert"; -import { svelte, tryToLoadJson } from "../helpers.js"; +import { svelte, loadConfig, tryToLoadJson } from "../helpers.js"; describe("validate", () => { fs.readdirSync("test/validator/samples").forEach(dir => { @@ -15,6 +15,7 @@ describe("validate", () => { } (solo ? it.only : skip ? it.skip : it)(dir, () => { + const config = loadConfig(`./validator/samples/${dir}/_config.js`); const filename = `test/validator/samples/${dir}/input.html`; const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, ""); @@ -32,7 +33,8 @@ describe("validate", () => { pos: warning.pos, loc: warning.loc }); - } + }, + dev: config.dev }); assert.deepEqual(warnings, expectedWarnings); diff --git a/test/validator/samples/empty-block-dev.solo/_config.js b/test/validator/samples/empty-block-dev.solo/_config.js new file mode 100644 index 000000000000..e26996239d88 --- /dev/null +++ b/test/validator/samples/empty-block-dev.solo/_config.js @@ -0,0 +1,3 @@ +export default { + dev: true +}; \ No newline at end of file diff --git a/test/validator/samples/empty-block-dev.solo/input.html b/test/validator/samples/empty-block-dev.solo/input.html new file mode 100644 index 000000000000..53e2ca83334c --- /dev/null +++ b/test/validator/samples/empty-block-dev.solo/input.html @@ -0,0 +1,3 @@ +{{#each things as thing}} + +{{/each}} \ No newline at end of file diff --git a/test/parser/samples/error-each-blocks-empty/error.json b/test/validator/samples/empty-block-dev.solo/warnings.json similarity index 93% rename from test/parser/samples/error-each-blocks-empty/error.json rename to test/validator/samples/empty-block-dev.solo/warnings.json index 6ecdc198bd9f..6cbc567ada7f 100644 --- a/test/parser/samples/error-each-blocks-empty/error.json +++ b/test/validator/samples/empty-block-dev.solo/warnings.json @@ -1,8 +1,8 @@ -{ +[{ "message": "Empty block", "loc": { "line": 1, "column": 0 }, "pos": 0 -} +}] \ No newline at end of file diff --git a/test/validator/samples/empty-block-prod/input.html b/test/validator/samples/empty-block-prod/input.html new file mode 100644 index 000000000000..53e2ca83334c --- /dev/null +++ b/test/validator/samples/empty-block-prod/input.html @@ -0,0 +1,3 @@ +{{#each things as thing}} + +{{/each}} \ No newline at end of file diff --git a/test/validator/samples/empty-block-prod/warnings.json b/test/validator/samples/empty-block-prod/warnings.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/test/validator/samples/empty-block-prod/warnings.json @@ -0,0 +1 @@ +[] \ No newline at end of file From 198f132b2827ccfd385f9aea312c0762c98ecf04 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 11:51:24 -0500 Subject: [PATCH 05/13] doh --- .../samples/{empty-block-dev.solo => empty-block-dev}/_config.js | 0 .../samples/{empty-block-dev.solo => empty-block-dev}/input.html | 0 .../{empty-block-dev.solo => empty-block-dev}/warnings.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test/validator/samples/{empty-block-dev.solo => empty-block-dev}/_config.js (100%) rename test/validator/samples/{empty-block-dev.solo => empty-block-dev}/input.html (100%) rename test/validator/samples/{empty-block-dev.solo => empty-block-dev}/warnings.json (100%) diff --git a/test/validator/samples/empty-block-dev.solo/_config.js b/test/validator/samples/empty-block-dev/_config.js similarity index 100% rename from test/validator/samples/empty-block-dev.solo/_config.js rename to test/validator/samples/empty-block-dev/_config.js diff --git a/test/validator/samples/empty-block-dev.solo/input.html b/test/validator/samples/empty-block-dev/input.html similarity index 100% rename from test/validator/samples/empty-block-dev.solo/input.html rename to test/validator/samples/empty-block-dev/input.html diff --git a/test/validator/samples/empty-block-dev.solo/warnings.json b/test/validator/samples/empty-block-dev/warnings.json similarity index 100% rename from test/validator/samples/empty-block-dev.solo/warnings.json rename to test/validator/samples/empty-block-dev/warnings.json From cccc3e4c41ef912566be20c35afe2fccaf00770c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 13:01:27 -0500 Subject: [PATCH 06/13] failing test for second part of #1100 --- .../TextInput.html | 1 + .../store-component-binding-deep/_config.js | 42 +++++++++++++++++++ .../store-component-binding-deep/main.html | 10 +++++ .../store-component-binding/_config.js | 12 ++++++ 4 files changed, 65 insertions(+) create mode 100644 test/runtime/samples/store-component-binding-deep/TextInput.html create mode 100644 test/runtime/samples/store-component-binding-deep/_config.js create mode 100644 test/runtime/samples/store-component-binding-deep/main.html diff --git a/test/runtime/samples/store-component-binding-deep/TextInput.html b/test/runtime/samples/store-component-binding-deep/TextInput.html new file mode 100644 index 000000000000..f24d608cd586 --- /dev/null +++ b/test/runtime/samples/store-component-binding-deep/TextInput.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding-deep/_config.js b/test/runtime/samples/store-component-binding-deep/_config.js new file mode 100644 index 000000000000..66ae5ac4ae22 --- /dev/null +++ b/test/runtime/samples/store-component-binding-deep/_config.js @@ -0,0 +1,42 @@ +import { Store } from '../../../../store.js'; + +const store = new Store({ + name: { + value: 'world' + } +}); + +export default { + store, + + html: ` +

Hello world!

+ + `, + + test(assert, component, target, window) { + const input = target.querySelector('input'); + const event = new window.Event('input'); + + const changeRecord = []; + store.onchange((state, changes) => { + changeRecord.push({ state, changes }); + }); + + input.value = 'everybody'; + input.dispatchEvent(event); + + assert.equal(store.get('name').value, 'everybody'); + assert.htmlEqual(target.innerHTML, ` +

Hello everybody!

+ + `); + + assert.deepEqual(changeRecord, [ + { + state: { name: { value: 'everybody' } }, + changes: { name: true } + } + ]); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding-deep/main.html b/test/runtime/samples/store-component-binding-deep/main.html new file mode 100644 index 000000000000..f77016b5494c --- /dev/null +++ b/test/runtime/samples/store-component-binding-deep/main.html @@ -0,0 +1,10 @@ +

Hello {{$name.value}}!

+ + + \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding/_config.js b/test/runtime/samples/store-component-binding/_config.js index aefc4ec652ac..1d2beab7a778 100644 --- a/test/runtime/samples/store-component-binding/_config.js +++ b/test/runtime/samples/store-component-binding/_config.js @@ -16,6 +16,11 @@ export default { const input = target.querySelector('input'); const event = new window.Event('input'); + const changeRecord = []; + store.onchange((state, changes) => { + changeRecord.push({ state, changes }); + }); + input.value = 'everybody'; input.dispatchEvent(event); @@ -24,5 +29,12 @@ export default {

Hello everybody!

`); + + assert.deepEqual(changeRecord, [ + { + state: { name: 'everybody' }, + changes: { name: true } + } + ]); } }; \ No newline at end of file From 4b8eb251c7c4e565a5231c28ed159702be15366d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 13:33:40 -0500 Subject: [PATCH 07/13] tidy up --- src/generators/nodes/Component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 493302c3d58d..09e9215a255e 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -157,7 +157,6 @@ export default class Component extends Node { let setFromChild; if (!isStoreProp && block.contexts.has(key)) { - const prop = binding.dependencies[0]; const computed = isComputed(binding.value); const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; @@ -175,7 +174,7 @@ export default class Component extends Node { else if (binding.value.type === 'MemberExpression') { setFromChild = deindent` ${binding.snippet} = childState.${binding.name}; - ${binding.dependencies.map((prop: string) => `${newState}.${prop} = state.${prop};`).join('\n')} + ${newState}.${key} = state.${key}; `; } From a16c77569031ef45b5d246b69dc9049c02761dcb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 19:54:55 -0500 Subject: [PATCH 08/13] remove some more junk --- src/generators/nodes/Component.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 09e9215a255e..46077893162c 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -156,7 +156,7 @@ export default class Component extends Node { let setFromChild; - if (!isStoreProp && block.contexts.has(key)) { + if (block.contexts.has(key)) { const computed = isComputed(binding.value); const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; @@ -171,15 +171,17 @@ export default class Component extends Node { `; } - else if (binding.value.type === 'MemberExpression') { - setFromChild = deindent` - ${binding.snippet} = childState.${binding.name}; - ${newState}.${key} = state.${key}; - `; - } - else { - setFromChild = `${newState}.${key} = childState.${binding.name};`; + if (binding.value.type === 'MemberExpression') { + setFromChild = deindent` + ${binding.snippet} = childState.${binding.name}; + ${newState}.${key} = state.${key}; + `; + } + + else { + setFromChild = `${newState}.${key} = childState.${binding.name};`; + } } statements.push(deindent` @@ -223,32 +225,25 @@ export default class Component extends Node { var ${initialisers}; ${!setStoreFromChildOnChange.isEmpty() && deindent` ${setStoreFromChildOnChange} - ${name_updating} = @assign({}, changed); #component.store.set(newStoreState); `} ${!setParentFromChildOnChange.isEmpty() && deindent` ${setParentFromChildOnChange} - ${name_updating} = @assign({}, changed); #component._set(newState); `} ${name_updating} = {}; } `); - // TODO can `!childState` ever be true? beforecreate = deindent` #component.root._beforecreate.push(function() { var childState = ${name}.get(), ${initialisers}; - if (!childState) return; - ${setParentFromChildOnInit} ${!setStoreFromChildOnInit.isEmpty() && deindent` ${setStoreFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; #component.store.set(newStoreState); `} ${!setParentFromChildOnInit.isEmpty() && deindent` ${setParentFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; #component._set(newState); `} ${name_updating} = {}; From b3382aa7a3efb0c368801a969d384f9afb480c2e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 20:28:01 -0500 Subject: [PATCH 09/13] combine component binding init/update --- src/generators/nodes/Component.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 46077893162c..59647d2c5ab8 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -138,10 +138,7 @@ export default class Component extends Node { statements.push(`var ${name_initial_data} = ${initialPropString};`); const setParentFromChildOnChange = new CodeBuilder(); - const setParentFromChildOnInit = new CodeBuilder(); - const setStoreFromChildOnChange = new CodeBuilder(); - const setStoreFromChildOnInit = new CodeBuilder(); bindings.forEach((binding: Binding) => { let { name: key } = getObject(binding.value); @@ -196,11 +193,6 @@ export default class Component extends Node { setFromChild ); - (isStoreProp ? setStoreFromChildOnInit : setParentFromChildOnInit).addConditional( - `!${name_updating}.${binding.name}`, - setFromChild - ); - // TODO could binding.dependencies.length ever be 0? if (binding.dependencies.length) { updates.push(deindent` @@ -237,16 +229,7 @@ export default class Component extends Node { beforecreate = deindent` #component.root._beforecreate.push(function() { - var childState = ${name}.get(), ${initialisers}; - ${!setStoreFromChildOnInit.isEmpty() && deindent` - ${setStoreFromChildOnInit} - #component.store.set(newStoreState); - `} - ${!setParentFromChildOnInit.isEmpty() && deindent` - ${setParentFromChildOnInit} - #component._set(newState); - `} - ${name_updating} = {}; + ${name}._bind({ ${bindings.map(b => `${b.name}: 1`).join(', ')} }, ${name}.get()); }); `; } else if (initialProps.length) { From 72776b0b2ac3d6bd68eefe599ac528410bc3e959 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 21:28:36 -0500 Subject: [PATCH 10/13] fix store bindings --- src/generators/nodes/Component.ts | 49 ++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 59647d2c5ab8..584444da6552 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -137,16 +137,14 @@ export default class Component extends Node { block.addVariable(name_updating, '{}'); statements.push(`var ${name_initial_data} = ${initialPropString};`); - const setParentFromChildOnChange = new CodeBuilder(); - const setStoreFromChildOnChange = new CodeBuilder(); + let hasLocalBindings = false; + let hasStoreBindings = false; + + const builder = new CodeBuilder(); bindings.forEach((binding: Binding) => { let { name: key } = getObject(binding.value); - const isStoreProp = generator.options.store && key[0] === '$'; - if (isStoreProp) key = key.slice(1); - const newState = isStoreProp ? 'newStoreState' : 'newState'; - binding.contexts.forEach(context => { allContexts.add(context); }); @@ -163,21 +161,37 @@ export default class Component extends Node { list[index]${tail} = childState.${binding.name}; ${binding.dependencies - .map((prop: string) => `${newState}.${prop} = state.${prop};`) + .map((name: string) => { + const isStoreProp = generator.options.store && name[0] === '$'; + const prop = isStoreProp ? name.slice(1) : name; + const newState = isStoreProp ? 'newStoreState' : 'newState'; + + if (isStoreProp) hasStoreBindings = true; + else hasLocalBindings = true; + + return `${newState}.${prop} = state.${name};`; + }) .join('\n')} `; } else { + const isStoreProp = generator.options.store && key[0] === '$'; + const prop = isStoreProp ? key.slice(1) : key; + const newState = isStoreProp ? 'newStoreState' : 'newState'; + + if (isStoreProp) hasStoreBindings = true; + else hasLocalBindings = true; + if (binding.value.type === 'MemberExpression') { setFromChild = deindent` ${binding.snippet} = childState.${binding.name}; - ${newState}.${key} = state.${key}; + ${newState}.${prop} = state.${key}; `; } else { - setFromChild = `${newState}.${key} = childState.${binding.name};`; + setFromChild = `${newState}.${prop} = childState.${binding.name};`; } } @@ -188,7 +202,7 @@ export default class Component extends Node { }` ); - (isStoreProp ? setStoreFromChildOnChange : setParentFromChildOnChange).addConditional( + builder.addConditional( `!${name_updating}.${binding.name} && changed.${binding.name}`, setFromChild ); @@ -208,21 +222,16 @@ export default class Component extends Node { const initialisers = [ 'state = #component.get()', - !setParentFromChildOnChange.isEmpty() && 'newState = {}', - !setStoreFromChildOnChange.isEmpty() && 'newStoreState = {}', + hasLocalBindings && 'newState = {}', + hasStoreBindings && 'newStoreState = {}', ].filter(Boolean).join(', '); componentInitProperties.push(deindent` _bind: function(changed, childState) { var ${initialisers}; - ${!setStoreFromChildOnChange.isEmpty() && deindent` - ${setStoreFromChildOnChange} - #component.store.set(newStoreState); - `} - ${!setParentFromChildOnChange.isEmpty() && deindent` - ${setParentFromChildOnChange} - #component._set(newState); - `} + ${builder} + ${hasStoreBindings && `#component.store.set(newStoreState);`} + ${hasLocalBindings && `#component._set(newState);`} ${name_updating} = {}; } `); From dfff2957a00cc156ac74cd900978fd992273edba Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 22:00:32 -0500 Subject: [PATCH 11/13] increase test coverage --- .../store-component-binding-each/Widget.html | 1 + .../store-component-binding-each/_config.js | 30 +++++++++++++++++++ .../store-component-binding-each/main.html | 15 ++++++++++ 3 files changed, 46 insertions(+) create mode 100644 test/runtime/samples/store-component-binding-each/Widget.html create mode 100644 test/runtime/samples/store-component-binding-each/_config.js create mode 100644 test/runtime/samples/store-component-binding-each/main.html diff --git a/test/runtime/samples/store-component-binding-each/Widget.html b/test/runtime/samples/store-component-binding-each/Widget.html new file mode 100644 index 000000000000..f24d608cd586 --- /dev/null +++ b/test/runtime/samples/store-component-binding-each/Widget.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/store-component-binding-each/_config.js b/test/runtime/samples/store-component-binding-each/_config.js new file mode 100644 index 000000000000..5d6e6889aef2 --- /dev/null +++ b/test/runtime/samples/store-component-binding-each/_config.js @@ -0,0 +1,30 @@ +import { Store } from '../../../../store.js'; + +const store = new Store({ + a: ['foo', 'bar', 'baz'] +}); + +export default { + store, + + html: ` + +

foo, bar, baz

+ `, + + test(assert, component, target, window) { + const event = new window.MouseEvent('input'); + const inputs = target.querySelectorAll('input'); + + inputs[0].value = 'blah'; + inputs[0].dispatchEvent(event); + + assert.deepEqual(store.get('a'), ['blah', 'bar', 'baz']); + assert.htmlEqual(target.innerHTML, ` + +

blah, bar, baz

+ `); + + component.destroy(); + }, +}; diff --git a/test/runtime/samples/store-component-binding-each/main.html b/test/runtime/samples/store-component-binding-each/main.html new file mode 100644 index 000000000000..e150c57af881 --- /dev/null +++ b/test/runtime/samples/store-component-binding-each/main.html @@ -0,0 +1,15 @@ +{{#each $a as x}} + +{{/each}} + +

{{$a.join(', ')}}

+ + From bc6ee3ef9f70d747d66e1c31e918f95467a5dc6e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 22:08:39 -0500 Subject: [PATCH 12/13] increase test coverage, handle immediately-closed blocks --- src/parse/state/mustache.ts | 2 +- .../samples/empty-block-dev/input.html | 4 +++- .../samples/empty-block-dev/warnings.json | 24 +++++++++++++------ .../samples/empty-block-prod/input.html | 4 +++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 6e44f7c15ec8..0a255b125602 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -6,7 +6,7 @@ import { Parser } from '../index'; import { Node } from '../../interfaces'; function trimWhitespace(block: Node, trimBefore: boolean, trimAfter: boolean) { - if (!block.children) return; // AwaitBlock + if (!block.children || block.children.length === 0) return; // AwaitBlock const firstChild = block.children[0]; const lastChild = block.children[block.children.length - 1]; diff --git a/test/validator/samples/empty-block-dev/input.html b/test/validator/samples/empty-block-dev/input.html index 53e2ca83334c..32b892d470e8 100644 --- a/test/validator/samples/empty-block-dev/input.html +++ b/test/validator/samples/empty-block-dev/input.html @@ -1,3 +1,5 @@ {{#each things as thing}} -{{/each}} \ No newline at end of file +{{/each}} + +{{#each things as thing}}{{/each}} \ No newline at end of file diff --git a/test/validator/samples/empty-block-dev/warnings.json b/test/validator/samples/empty-block-dev/warnings.json index 6cbc567ada7f..54584bf13585 100644 --- a/test/validator/samples/empty-block-dev/warnings.json +++ b/test/validator/samples/empty-block-dev/warnings.json @@ -1,8 +1,18 @@ -[{ - "message": "Empty block", - "loc": { - "line": 1, - "column": 0 +[ + { + "message": "Empty block", + "loc": { + "line": 1, + "column": 0 + }, + "pos": 0 }, - "pos": 0 -}] \ No newline at end of file + { + "message": "Empty block", + "loc": { + "line": 5, + "column": 0 + }, + "pos": 38 + } +] \ No newline at end of file diff --git a/test/validator/samples/empty-block-prod/input.html b/test/validator/samples/empty-block-prod/input.html index 53e2ca83334c..32b892d470e8 100644 --- a/test/validator/samples/empty-block-prod/input.html +++ b/test/validator/samples/empty-block-prod/input.html @@ -1,3 +1,5 @@ {{#each things as thing}} -{{/each}} \ No newline at end of file +{{/each}} + +{{#each things as thing}}{{/each}} \ No newline at end of file From f77314f647f5e79c3f66f0fec27a8762535740b7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Feb 2018 23:29:47 -0500 Subject: [PATCH 13/13] increase test coverage --- .../samples/each-block-multiple-children/_config.js | 3 +++ .../samples/each-block-multiple-children/input.html | 10 ++++++++++ .../samples/each-block-multiple-children/warnings.json | 1 + 3 files changed, 14 insertions(+) create mode 100644 test/validator/samples/each-block-multiple-children/_config.js create mode 100644 test/validator/samples/each-block-multiple-children/input.html create mode 100644 test/validator/samples/each-block-multiple-children/warnings.json diff --git a/test/validator/samples/each-block-multiple-children/_config.js b/test/validator/samples/each-block-multiple-children/_config.js new file mode 100644 index 000000000000..e26996239d88 --- /dev/null +++ b/test/validator/samples/each-block-multiple-children/_config.js @@ -0,0 +1,3 @@ +export default { + dev: true +}; \ No newline at end of file diff --git a/test/validator/samples/each-block-multiple-children/input.html b/test/validator/samples/each-block-multiple-children/input.html new file mode 100644 index 000000000000..9d996a3acf17 --- /dev/null +++ b/test/validator/samples/each-block-multiple-children/input.html @@ -0,0 +1,10 @@ +{{#each things as thing}} + this only exists... + ...to increase test coverage +{{/each}} + +{{#each things as thing}}{{soDoesThis}}{{/each}} + +{{#each things as thing}} + andThis +{{/each}} \ No newline at end of file diff --git a/test/validator/samples/each-block-multiple-children/warnings.json b/test/validator/samples/each-block-multiple-children/warnings.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/test/validator/samples/each-block-multiple-children/warnings.json @@ -0,0 +1 @@ +[] \ No newline at end of file