From f244b80f7a80df54aa07f85c35720fa6ca880318 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 28 Feb 2020 23:07:34 -0500 Subject: [PATCH 01/23] docs: referenced_from_script var value (#4486) --- site/content/docs/04-compile-time.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index 0cb2ac3caf..f9bfa772fe 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -113,7 +113,8 @@ const { * `module` is `true` if the value is declared in a `context="module"` script * `mutated` is `true` if the value's properties are assigned to inside the component * `reassigned` is `true` if the value is reassigned inside the component - * `referenced` is `true` if the value is used outside the declaration + * `referenced` is `true` if the value is used in the template + * `referenced_from_script` is `true` if the value is used in the ` + \ No newline at end of file From 8feb85a61fe32ec164ea0358e5966338c5c69e1d Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 1 Mar 2020 00:32:11 +0800 Subject: [PATCH 03/23] fix bitmask overflow for slot (#4485) --- CHANGELOG.md | 1 + src/runtime/internal/utils.ts | 6 ++- .../bitmask-overflow-slot-3/Echo.svelte | 9 ++++ .../bitmask-overflow-slot-3/_config.js | 30 ++++++++++++ .../bitmask-overflow-slot-3/main.svelte | 11 +++++ .../bitmask-overflow-slot-4/Echo.svelte | 11 +++++ .../bitmask-overflow-slot-4/_config.js | 41 ++++++++++++++++ .../bitmask-overflow-slot-4/main.svelte | 12 +++++ .../bitmask-overflow-slot-5/Echo.svelte | 13 +++++ .../bitmask-overflow-slot-5/_config.js | 49 +++++++++++++++++++ .../bitmask-overflow-slot-5/main.svelte | 13 +++++ 11 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte create mode 100644 test/runtime/samples/bitmask-overflow-slot-3/_config.js create mode 100644 test/runtime/samples/bitmask-overflow-slot-3/main.svelte create mode 100644 test/runtime/samples/bitmask-overflow-slot-4/Echo.svelte create mode 100644 test/runtime/samples/bitmask-overflow-slot-4/_config.js create mode 100644 test/runtime/samples/bitmask-overflow-slot-4/main.svelte create mode 100644 test/runtime/samples/bitmask-overflow-slot-5/Echo.svelte create mode 100644 test/runtime/samples/bitmask-overflow-slot-5/_config.js create mode 100644 test/runtime/samples/bitmask-overflow-slot-5/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 60553037e4..8197888f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463)) +* Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481)) ## 3.19.1 diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 4ba7e18c15..acb88c7669 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -83,7 +83,11 @@ export function get_slot_changes(definition, $$scope, dirty, fn) { if (definition[2] && fn) { const lets = definition[2](fn(dirty)); - if (typeof $$scope.dirty === 'object') { + if ($$scope.dirty === undefined) { + return lets; + } + + if (typeof lets === 'object') { const merged = []; const len = Math.max($$scope.dirty.length, lets.length); for (let i = 0; i < len; i += 1) { diff --git a/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte b/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte new file mode 100644 index 0000000000..d3ecf142c9 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-3/_config.js b/test/runtime/samples/bitmask-overflow-slot-3/_config.js new file mode 100644 index 0000000000..93e548e5f7 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-3/_config.js @@ -0,0 +1,30 @@ +export default { + + html: ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

0

+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

1

+ + `); + + // change from outside + component._0 = 'a'; + component._40 = 'b'; + + assert.htmlEqual(target.innerHTML, ` +

a_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39b

+

1

+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-3/main.svelte b/test/runtime/samples/bitmask-overflow-slot-3/main.svelte new file mode 100644 index 0000000000..ae798e4aee --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-3/main.svelte @@ -0,0 +1,11 @@ + + + +

{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}

+

{dummy}

+
+ diff --git a/test/runtime/samples/bitmask-overflow-slot-4/Echo.svelte b/test/runtime/samples/bitmask-overflow-slot-4/Echo.svelte new file mode 100644 index 0000000000..2e1beda492 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-4/Echo.svelte @@ -0,0 +1,11 @@ + + +

{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}

+ + \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-4/_config.js b/test/runtime/samples/bitmask-overflow-slot-4/_config.js new file mode 100644 index 0000000000..cdaa5de77f --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-4/_config.js @@ -0,0 +1,41 @@ +export default { + html: ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

0

+

0

+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

0

+

1

+ + `); + + // change from outside + component._0 = 'a'; + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

a

+

1

+ + `); + + // change from outside through props + component._40 = 'b'; + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39b

+

a

+

1

+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-4/main.svelte b/test/runtime/samples/bitmask-overflow-slot-4/main.svelte new file mode 100644 index 0000000000..7e02487a30 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-4/main.svelte @@ -0,0 +1,12 @@ + + + +

{_0}

+

{dummy}

+
+ diff --git a/test/runtime/samples/bitmask-overflow-slot-5/Echo.svelte b/test/runtime/samples/bitmask-overflow-slot-5/Echo.svelte new file mode 100644 index 0000000000..dddb3f7642 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-5/Echo.svelte @@ -0,0 +1,13 @@ + + +

{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}

+

{b}

+ + \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-5/_config.js b/test/runtime/samples/bitmask-overflow-slot-5/_config.js new file mode 100644 index 0000000000..7dedb8f7eb --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-5/_config.js @@ -0,0 +1,49 @@ +export default { + html: ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

b

+

-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40

+

0

+

0

+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

b

+

-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40

+

0

+

1

+ + `); + + // change from outside + component.a = 'AA'; + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

b

+

-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40

+

AA

+

1

+ + `); + + // change from outside through props + component.b = 'BB'; + + assert.htmlEqual(target.innerHTML, ` +

_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40

+

BB

+

-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40

+

AA

+

1

+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-5/main.svelte b/test/runtime/samples/bitmask-overflow-slot-5/main.svelte new file mode 100644 index 0000000000..b17d6f7bae --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-5/main.svelte @@ -0,0 +1,13 @@ + + + +

{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}

+

{a}

+

{dummy}

+
+ From 37a2d6c6eada0c4e893fb90f3d8faa6eb3036f6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Feb 2020 11:34:18 -0500 Subject: [PATCH 04/23] Bump codecov from 3.5.0 to 3.6.5 (#4433) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.5.0 to 3.6.5. - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/commits) Signed-off-by: dependabot[bot] --- package-lock.json | 111 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b275078ec..f0f4e17422 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,6 +137,12 @@ } } }, + "@tootallnate/once": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", + "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "dev": true + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -609,16 +615,16 @@ } }, "codecov": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.5.0.tgz", - "integrity": "sha512-/OsWOfIHaQIr7aeZ4pY0UC1PZT6kimoKFOFYFNb6wxo3iw12nRrh+mNGH72rnXxNsq6SGfesVPizm/6Q3XqcFQ==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.6.5.tgz", + "integrity": "sha512-v48WuDMUug6JXwmmfsMzhCHRnhUf8O3duqXvltaYJKrO1OekZWpB/eH6iIoaxMl8Qli0+u3OxptdsBOYiD7VAQ==", "dev": true, "requires": { - "argv": "^0.0.2", - "ignore-walk": "^3.0.1", - "js-yaml": "^3.13.1", - "teeny-request": "^3.11.3", - "urlgrey": "^0.4.4" + "argv": "0.0.2", + "ignore-walk": "3.0.3", + "js-yaml": "3.13.1", + "teeny-request": "6.0.1", + "urlgrey": "0.4.4" } }, "color-convert": { @@ -1670,6 +1676,37 @@ "whatwg-encoding": "^1.0.1" } }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -1707,9 +1744,9 @@ "dev": true }, "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { "minimatch": "^3.0.4" @@ -3362,6 +3399,15 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dev": true, + "requires": { + "stubs": "^3.0.0" + } + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -3409,6 +3455,12 @@ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "dev": true + }, "sucrase": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.10.1.tgz", @@ -3449,14 +3501,43 @@ } }, "teeny-request": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz", + "integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==", "dev": true, "requires": { - "https-proxy-agent": "^2.2.1", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^4.0.0", "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", "uuid": "^3.3.2" + }, + "dependencies": { + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + } + } } }, "test-exclude": { From b6aaa44880892a64712f629bff40f1433d92dfdb Mon Sep 17 00:00:00 2001 From: pushkin Date: Sat, 29 Feb 2020 18:43:53 +0100 Subject: [PATCH 05/23] check for unknown props even if component doesn't have writable props (#4454) --- src/compiler/compile/render_dom/index.ts | 6 +++++- test/js/samples/debug-hoisted/expected.js | 6 ++++++ test/js/samples/debug-no-dependencies/expected.js | 12 +++++++++++- test/js/samples/loop-protect/expected.js | 8 ++++++++ .../samples/dev-warning-unknown-props-2/Foo.svelte | 1 + .../samples/dev-warning-unknown-props-2/_config.js | 9 +++++++++ .../samples/dev-warning-unknown-props-2/main.svelte | 5 +++++ 7 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/dev-warning-unknown-props-2/Foo.svelte create mode 100644 test/runtime/samples/dev-warning-unknown-props-2/_config.js create mode 100644 test/runtime/samples/dev-warning-unknown-props-2/main.svelte diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 71e8681646..3d1870ccaa 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -259,6 +259,9 @@ export default function dom( inject_state; if (has_invalidate) { args.push(x`$$props`, x`$$invalidate`); + } else if (component.compile_options.dev) { + // $$props arg is still needed for unknown prop check + args.push(x`$$props`); } const has_create_fragment = block.has_content(); @@ -300,6 +303,7 @@ export default function dom( const initial_context = renderer.context.slice(0, i + 1); const has_definition = ( + component.compile_options.dev || (instance_javascript && instance_javascript.length > 0) || filtered_props.length > 0 || uses_props || @@ -379,7 +383,7 @@ export default function dom( }); let unknown_props_check; - if (component.compile_options.dev && !component.var_lookup.has('$$props') && writable_props.length) { + if (component.compile_options.dev && !component.var_lookup.has('$$props')) { unknown_props_check = b` const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}]; @_Object.keys($$props).forEach(key => { diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js index c77168ef4f..190ff12c50 100644 --- a/test/js/samples/debug-hoisted/expected.js +++ b/test/js/samples/debug-hoisted/expected.js @@ -50,6 +50,12 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let obj = { x: 5 }; let kobzol = 5; + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); + }); + $$self.$capture_state = () => ({ obj, kobzol }); $$self.$inject_state = $$props => { diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index 9985c814a2..a097869e74 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -134,10 +134,20 @@ function create_fragment(ctx) { return block; } +function instance($$self, $$props) { + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); + }); + + return []; +} + class Component extends SvelteComponentDev { constructor(options) { super(options); - init(this, options, null, create_fragment, safe_not_equal, {}); + init(this, options, instance, create_fragment, safe_not_equal, {}); dispatch_dev("SvelteRegisterComponent", { component: this, diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index 4eccaae7cb..f433bd61a9 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -6,6 +6,7 @@ import { detach_dev, dispatch_dev, element, + globals, init, insert_dev, loop_guard, @@ -13,6 +14,7 @@ import { safe_not_equal } from "svelte/internal"; +const { console: console_1 } = globals; const file = undefined; function create_fragment(ctx) { @@ -102,6 +104,12 @@ function instance($$self, $$props, $$invalidate) { } while (true); } + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console_1.warn(` was created with unknown prop '${key}'`); + }); + function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { $$invalidate(0, node = $$value); diff --git a/test/runtime/samples/dev-warning-unknown-props-2/Foo.svelte b/test/runtime/samples/dev-warning-unknown-props-2/Foo.svelte new file mode 100644 index 0000000000..bc56c4d894 --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-2/Foo.svelte @@ -0,0 +1 @@ +Foo diff --git a/test/runtime/samples/dev-warning-unknown-props-2/_config.js b/test/runtime/samples/dev-warning-unknown-props-2/_config.js new file mode 100644 index 0000000000..9bff4a2a74 --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-2/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + + warnings: [ + ` was created with unknown prop 'fo'` + ] +}; diff --git a/test/runtime/samples/dev-warning-unknown-props-2/main.svelte b/test/runtime/samples/dev-warning-unknown-props-2/main.svelte new file mode 100644 index 0000000000..1566cf3e41 --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-2/main.svelte @@ -0,0 +1,5 @@ + + + From 3f647a84f6c295334ac8309caa9724a3fa137ace Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 29 Feb 2020 12:44:54 -0500 Subject: [PATCH 06/23] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8197888f57..3dd0aadf8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) +* In `dev` mode, check for unknown props even if the component has no writable props ([#4323](https://github.com/sveltejs/svelte/issues/4323)) * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463)) * Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481)) From 3a37de364bfbe75202d8e9fcef9e76b9ce6faaa2 Mon Sep 17 00:00:00 2001 From: Brian Takita Date: Sat, 29 Feb 2020 12:50:54 -0500 Subject: [PATCH 07/23] chore: more specific typings, and add README note about Yarn (#4483) --- README.md | 2 ++ src/compiler/compile/Component.ts | 26 +++++++++---------- src/compiler/compile/nodes/Let.ts | 12 ++++----- .../compile/nodes/shared/Expression.ts | 4 +-- src/compiler/compile/render_dom/index.ts | 4 +-- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3a43725637..fa725804a9 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ cd svelte npm install ``` +> Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte. + > Many tests depend on newlines being preserved as ``. On Windows, you can ensure this by cloning with: > ```bash > git -c core.autocrlf=false clone https://github.com/sveltejs/svelte.git diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 2569820464..a7fe2992d1 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -239,7 +239,7 @@ export default class Component { const program: any = { type: 'Program', body: result.js }; walk(program, { - enter: (node, parent, key) => { + enter: (node: Node, parent: Node, key) => { if (node.type === 'Identifier') { if (node.name[0] === '@') { if (node.name[1] === '_') { @@ -526,7 +526,7 @@ export default class Component { if (!script) return; walk(script.content, { - enter(node) { + enter(node: Node) { if (node.type === 'LabeledStatement' && node.label.name === '$') { component.warn(node as any, { code: 'module-script-reactive-declaration', @@ -715,7 +715,7 @@ export default class Component { let scope_updated = false; walk(content, { - enter(node, parent, prop, index) { + enter(node: Node, parent, prop, index) { if (map.has(node)) { scope = map.get(node); } @@ -741,7 +741,7 @@ export default class Component { component.warn_on_undefined_store_value_references(node, parent, scope); }, - leave(node) { + leave(node: Node) { // do it on leave, to prevent infinite loop if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) { const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout); @@ -785,7 +785,7 @@ export default class Component { let scope = instance_scope; walk(content, { - enter(node, parent) { + enter(node: Node, parent: Node) { if (map.has(node)) { scope = map.get(node); } @@ -818,7 +818,7 @@ export default class Component { } }, - leave(node) { + leave(node: Node) { if (map.has(node)) { scope = scope.parent; } @@ -886,7 +886,7 @@ export default class Component { let scope = instance_scope; walk(this.ast.instance.content, { - enter(node, parent, key, index) { + enter(node: Node, parent, key, index) { if (/Function/.test(node.type)) { return this.skip(); } @@ -963,7 +963,7 @@ export default class Component { } }, - leave(node, parent, _key, index) { + leave(node: Node, parent, _key, index) { if (map.has(node)) { scope = scope.parent; } @@ -1064,7 +1064,7 @@ export default class Component { walking.add(fn_declaration); walk(fn_declaration, { - enter(node, parent) { + enter(node: Node, parent) { if (!hoistable) return this.skip(); if (map.has(node)) { @@ -1112,7 +1112,7 @@ export default class Component { } }, - leave(node) { + leave(node: Node) { if (map.has(node)) { scope = scope.parent; } @@ -1155,7 +1155,7 @@ export default class Component { const map = this.instance_scope_map; walk(node.body, { - enter(node, parent) { + enter(node: Node, parent) { if (map.has(node)) { scope = map.get(node); } @@ -1195,7 +1195,7 @@ export default class Component { } }, - leave(node) { + leave(node: Node) { if (map.has(node)) { scope = scope.parent; } @@ -1455,4 +1455,4 @@ function get_relative_path(from: string, to: string) { } return from_parts.concat(to_parts).join('/'); -} \ No newline at end of file +} diff --git a/src/compiler/compile/nodes/Let.ts b/src/compiler/compile/nodes/Let.ts index 7b92c5ce70..ef38cd36a8 100644 --- a/src/compiler/compile/nodes/Let.ts +++ b/src/compiler/compile/nodes/Let.ts @@ -1,7 +1,7 @@ import Node from './shared/Node'; import Component from '../Component'; import { walk } from 'estree-walker'; -import { Identifier } from 'estree'; +import { BasePattern, Identifier } from 'estree'; const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']); @@ -22,7 +22,7 @@ export default class Let extends Node { this.value = info.expression; walk(info.expression, { - enter(node) { + enter(node: Identifier|BasePattern) { if (!applicable.has(node.type)) { component.error(node as any, { code: 'invalid-let', @@ -31,16 +31,16 @@ export default class Let extends Node { } if (node.type === 'Identifier') { - names.push(node.name); + names.push((node as Identifier).name); } // slightly unfortunate hack if (node.type === 'ArrayExpression') { - (node as any).type = 'ArrayPattern'; + node.type = 'ArrayPattern'; } if (node.type === 'ObjectExpression') { - (node as any).type = 'ObjectPattern'; + node.type = 'ObjectPattern'; } } }); @@ -48,4 +48,4 @@ export default class Let extends Node { names.push(this.name.name); } } -} \ No newline at end of file +} diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 1882c19267..db0908c3f4 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -143,7 +143,7 @@ export default class Expression { } }, - leave(node) { + leave(node: Node) { if (map.has(node)) { scope = scope.parent; } @@ -338,7 +338,7 @@ export default class Expression { }); } - return (this.manipulated = node); + return (this.manipulated = node as Node); } } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3d1870ccaa..d6da614276 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -200,7 +200,7 @@ export default function dom( let execution_context: Node | null = null; walk(component.ast.instance.content, { - enter(node) { + enter(node: Node) { if (map.has(node)) { scope = map.get(node) as Scope; @@ -212,7 +212,7 @@ export default function dom( } }, - leave(node) { + leave(node: Node) { if (map.has(node)) { scope = scope.parent; } From d802c3b266be31a509cb69f3a81666b20842f3ef Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 1 Mar 2020 11:47:50 +0800 Subject: [PATCH 08/23] in spread, distinguish never-updating and always-updating props (#4487) --- CHANGELOG.md | 1 + .../wrappers/InlineComponent/index.ts | 15 +++- .../samples/spread-component-2/Widget.svelte | 13 ++++ .../samples/spread-component-2/_config.js | 76 +++++++++++++++++++ .../samples/spread-component-2/main.svelte | 12 +++ 5 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/spread-component-2/Widget.svelte create mode 100644 test/runtime/samples/spread-component-2/_config.js create mode 100644 test/runtime/samples/spread-component-2/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd0aadf8b..8cdc334868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) +* Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480)) * In `dev` mode, check for unknown props even if the component has no writable props ([#4323](https://github.com/sveltejs/svelte/issues/4323)) * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463)) * Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481)) diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 28b3c938f9..8088bbac91 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -224,7 +224,9 @@ export default class InlineComponentWrapper extends Wrapper { const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) ? renderer.dirty(Array.from(dependencies)) : null; + const unchanged = dependencies.size === 0; + let change_object; if (attr.is_spread) { const value = attr.expression.manipulate(block); initial_props.push(value); @@ -233,13 +235,20 @@ export default class InlineComponentWrapper extends Wrapper { if (attr.expression.node.type !== 'ObjectExpression') { value_object = x`@get_spread_object(${value})`; } - changes.push(condition ? x`${condition} && ${value_object}` : value_object); + change_object = value_object; } else { const obj = x`{ ${name}: ${attr.get_value(block)} }`; initial_props.push(obj); - - changes.push(condition ? x`${condition} && ${obj}` : x`${levels}[${i}]`); + change_object = obj; } + + changes.push( + unchanged + ? x`${levels}[${i}]` + : condition + ? x`${condition} && ${change_object}` + : change_object + ); }); block.chunks.init.push(b` diff --git a/test/runtime/samples/spread-component-2/Widget.svelte b/test/runtime/samples/spread-component-2/Widget.svelte new file mode 100644 index 0000000000..ae27aeb5e5 --- /dev/null +++ b/test/runtime/samples/spread-component-2/Widget.svelte @@ -0,0 +1,13 @@ + + +

foo: {foo}

+

baz: {baz} ({typeof baz})

+

qux: {qux}

+

quux: {quux}

+

selected: {selected}

diff --git a/test/runtime/samples/spread-component-2/_config.js b/test/runtime/samples/spread-component-2/_config.js new file mode 100644 index 0000000000..6d36e8e60d --- /dev/null +++ b/test/runtime/samples/spread-component-2/_config.js @@ -0,0 +1,76 @@ +export default { + props: { + list: [{ + foo: 'lol', + baz: 40 + 2, + qux: 5, + quux: 'core' + }, { + foo: 'lolzz', + baz: 50 + 2, + qux: 1, + quux: 'quuxx' + }], + }, + + html: ` +
+

foo: lol

+

baz: 42 (number)

+

qux: 0

+

quux: core

+

selected: true

+

foo: lolzz

+

baz: 52 (number)

+

qux: 0

+

quux: quuxx

+

selected: false

+
+ `, + + test({ assert, component, target }) { + component.list = [{ + foo: 'lol', + baz: 40 + 3, + qux: 8, + quux: 'heart' + }, { + foo: 'lolzz', + baz: 50 + 3, + qux: 8, + quux: 'heartxx' + }]; + + assert.htmlEqual(target.innerHTML, ` +
+

foo: lol

+

baz: 43 (number)

+

qux: 0

+

quux: heart

+

selected: true

+

foo: lolzz

+

baz: 53 (number)

+

qux: 0

+

quux: heartxx

+

selected: false

+
+ `); + + component.qux = 1; + + assert.htmlEqual(target.innerHTML, ` +
+

foo: lol

+

baz: 43 (number)

+

qux: 1

+

quux: heart

+

selected: false

+

foo: lolzz

+

baz: 53 (number)

+

qux: 1

+

quux: heartxx

+

selected: true

+
+ `); + } +}; diff --git a/test/runtime/samples/spread-component-2/main.svelte b/test/runtime/samples/spread-component-2/main.svelte new file mode 100644 index 0000000000..436e11f9c5 --- /dev/null +++ b/test/runtime/samples/spread-component-2/main.svelte @@ -0,0 +1,12 @@ + + +
+ {#each list as item, index (item.foo)} + + {/each} +
From addea43e4f6c16e19083a78163210d7903543583 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 2 Mar 2020 09:01:00 -0500 Subject: [PATCH 09/23] docs: describe falsy and nullish attribute behavior (#4498) --- site/content/docs/02-template-syntax.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 55b913ca94..d25b348254 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -58,6 +58,17 @@ Or they can *be* JavaScript expressions. --- +Boolean attributes are included on the element if their value is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and excluded if it's [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy). + +All other attributes are included unless their value is [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish) (`null` or `undefined`). + +```html + +
This div has no title attribute
+``` + +--- + An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed: ```html From 926a2aebd8915c53cf81b0b1e4038c2b1952e297 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Wed, 4 Mar 2020 18:37:48 +0800 Subject: [PATCH 10/23] fix render fallback slot content due to whitespace (#4500) --- CHANGELOG.md | 1 + src/compiler/compile/nodes/Text.ts | 29 ++++++++ .../compile/render_dom/wrappers/Fragment.ts | 6 +- .../wrappers/InlineComponent/index.ts | 25 ++++--- .../compile/render_dom/wrappers/Text.ts | 32 +------- .../compile/render_ssr/handlers/Element.ts | 11 ++- .../render_ssr/handlers/InlineComponent.ts | 23 ++++-- .../utils/remove_whitespace_children.ts | 73 +++++++++++++++++++ src/compiler/utils/link.ts | 4 + .../Nested.svelte | 4 + .../component-slot-fallback-empty/_config.js | 13 ++++ .../component-slot-fallback-empty/main.svelte | 10 +++ 12 files changed, 178 insertions(+), 53 deletions(-) create mode 100644 src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts create mode 100644 src/compiler/utils/link.ts create mode 100644 test/runtime/samples/component-slot-fallback-empty/Nested.svelte create mode 100644 test/runtime/samples/component-slot-fallback-empty/_config.js create mode 100644 test/runtime/samples/component-slot-fallback-empty/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdc334868..b0672d790e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) * Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480)) +* Use the fallback content for slots if they are passed only whitespace ([#4092](https://github.com/sveltejs/svelte/issues/4092)) * In `dev` mode, check for unknown props even if the component has no writable props ([#4323](https://github.com/sveltejs/svelte/issues/4323)) * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463)) * Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481)) diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts index bfd28a5073..6b7432c22f 100644 --- a/src/compiler/compile/nodes/Text.ts +++ b/src/compiler/compile/nodes/Text.ts @@ -3,6 +3,18 @@ import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; +// Whitespace inside one of these elements will not result in +// a whitespace node being created in any circumstances. (This +// list is almost certainly very incomplete) +const elements_without_text = new Set([ + 'audio', + 'datalist', + 'dl', + 'optgroup', + 'select', + 'video', +]); + export default class Text extends Node { type: 'Text'; data: string; @@ -13,4 +25,21 @@ export default class Text extends Node { this.data = info.data; this.synthetic = info.synthetic || false; } + + should_skip() { + if (/\S/.test(this.data)) return false; + + const parent_element = this.find_nearest(/(?:Element|InlineComponent|Head)/); + if (!parent_element) return false; + + if (parent_element.type === 'Head') return true; + if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0]; + + // svg namespace exclusions + if (/svg$/.test(parent_element.namespace)) { + if (this.prev && this.prev.type === "Element" && this.prev.name === "tspan") return false; + } + + return parent_element.namespace || elements_without_text.has(parent_element.name); + } } diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 224b17d43f..a0984b69b9 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -17,6 +17,7 @@ import { INode } from '../../nodes/interfaces'; import Renderer from '../Renderer'; import Block from '../Block'; import { trim_start, trim_end } from '../../../utils/trim'; +import { link } from '../../../utils/link'; import { Identifier } from 'estree'; const wrappers = { @@ -38,11 +39,6 @@ const wrappers = { Window }; -function link(next: Wrapper, prev: Wrapper) { - prev.next = next; - if (next) next.prev = prev; -} - function trimmable_at(child: INode, next_sibling: Wrapper): boolean { // Whitespace is trimmable if one of the following is true: // The child and its sibling share a common nearest each block (not at an each block boundary) diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 8088bbac91..8c8bd70696 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -138,11 +138,27 @@ export default class InlineComponentWrapper extends Wrapper { const statements: Array = []; const updates: Array = []; + if (this.fragment) { + this.renderer.add_to_context('$$scope', true); + const default_slot = this.slots.get('default'); + + this.fragment.nodes.forEach((child) => { + child.render(default_slot.block, null, x`#nodes` as unknown as Identifier); + }); + } + let props; const name_changes = block.get_unique_name(`${name.name}_changes`); const uses_spread = !!this.node.attributes.find(a => a.is_spread); + // removing empty slot + for (const slot of this.slots.keys()) { + if (!this.slots.get(slot).block.has_content()) { + this.slots.delete(slot); + } + } + const initial_props = this.slots.size > 0 ? [ p`$$slots: { @@ -172,15 +188,6 @@ export default class InlineComponentWrapper extends Wrapper { } } - if (this.fragment) { - this.renderer.add_to_context('$$scope', true); - const default_slot = this.slots.get('default'); - - this.fragment.nodes.forEach((child) => { - child.render(default_slot.block, null, x`#nodes` as unknown as Identifier); - }); - } - if (component.compile_options.dev) { // TODO this is a terrible hack, but without it the component // will complain that options.target is missing. This would diff --git a/src/compiler/compile/render_dom/wrappers/Text.ts b/src/compiler/compile/render_dom/wrappers/Text.ts index 1978cba0d7..7ef8aebd70 100644 --- a/src/compiler/compile/render_dom/wrappers/Text.ts +++ b/src/compiler/compile/render_dom/wrappers/Text.ts @@ -5,36 +5,6 @@ import Wrapper from './shared/Wrapper'; import { x } from 'code-red'; import { Identifier } from 'estree'; -// Whitespace inside one of these elements will not result in -// a whitespace node being created in any circumstances. (This -// list is almost certainly very incomplete) -const elements_without_text = new Set([ - 'audio', - 'datalist', - 'dl', - 'optgroup', - 'select', - 'video', -]); - -// TODO this should probably be in Fragment -function should_skip(node: Text) { - if (/\S/.test(node.data)) return false; - - const parent_element = node.find_nearest(/(?:Element|InlineComponent|Head)/); - if (!parent_element) return false; - - if (parent_element.type === 'Head') return true; - if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && node === parent_element.children[0]; - - // svg namespace exclusions - if (/svg$/.test(parent_element.namespace)) { - if (node.prev && node.prev.type === "Element" && node.prev.name === "tspan") return false; - } - - return parent_element.namespace || elements_without_text.has(parent_element.name); -} - export default class TextWrapper extends Wrapper { node: Text; data: string; @@ -50,7 +20,7 @@ export default class TextWrapper extends Wrapper { ) { super(renderer, block, parent, node); - this.skip = should_skip(this.node); + this.skip = this.node.should_skip(); this.data = data; this.var = (this.skip ? null : x`t`) as unknown as Identifier; } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 6e6a61974a..ad5c421bc4 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -6,10 +6,14 @@ import Renderer, { RenderOptions } from '../Renderer'; import Element from '../../nodes/Element'; import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; +import remove_whitespace_children from './utils/remove_whitespace_children'; export default function(node: Element, renderer: Renderer, options: RenderOptions & { slot_scopes: Map; }) { + + const children = remove_whitespace_children(node.children, node.next); + // awkward special case let node_contents; @@ -133,7 +137,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption if (node_contents !== undefined) { if (contenteditable) { renderer.push(); - renderer.render(node.children, options); + renderer.render(children, options); const result = renderer.pop(); renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); @@ -145,7 +149,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_string(``); } } else if (slot && nearest_inline_component) { - renderer.render(node.children, options); + renderer.render(children, options); if (!is_void(node.name)) { renderer.add_string(``); @@ -163,10 +167,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption output: renderer.pop() }); } else { - renderer.render(node.children, options); + renderer.render(children, options); if (!is_void(node.name)) { renderer.add_string(``); } } } + diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 5c4d9c73b8..37f05a941c 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -2,6 +2,7 @@ import { string_literal } from '../../utils/stringify'; import Renderer, { RenderOptions } from '../Renderer'; import { get_slot_scope } from './shared/get_slot_scope'; import InlineComponent from '../../nodes/InlineComponent'; +import remove_whitespace_children from './utils/remove_whitespace_children'; import { p, x } from 'code-red'; function get_prop_value(attribute) { @@ -67,12 +68,14 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend const slot_fns = []; - if (node.children.length) { + const children = remove_whitespace_children(node.children, node.next); + + if (children.length) { const slot_scopes = new Map(); renderer.push(); - renderer.render(node.children, Object.assign({}, options, { + renderer.render(children, Object.assign({}, options, { slot_scopes })); @@ -82,9 +85,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend }); slot_scopes.forEach(({ input, output }, name) => { - slot_fns.push( - p`${name}: (${input}) => ${output}` - ); + if (!is_empty_template_literal(output)) { + slot_fns.push( + p`${name}: (${input}) => ${output}` + ); + } }); } @@ -94,3 +99,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); } + +function is_empty_template_literal(template_literal) { + return ( + template_literal.expressions.length === 0 && + template_literal.quasis.length === 1 && + template_literal.quasis[0].value.raw === "" + ); +} \ No newline at end of file diff --git a/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts b/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts new file mode 100644 index 0000000000..b6c1bf493a --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts @@ -0,0 +1,73 @@ +import { INode } from '../../../nodes/interfaces'; +import { trim_end, trim_start } from '../../../../utils/trim'; +import { link } from '../../../../utils/link'; + +// similar logic from `compile/render_dom/wrappers/Fragment` +// We want to remove trailing whitespace inside an element/component/block, +// *unless* there is no whitespace between this node and its next sibling +export default function remove_whitespace_children(children: INode[], next?: INode): INode[] { + const nodes: INode[] = []; + let last_child: INode; + let i = children.length; + while (i--) { + const child = children[i]; + + if (child.type === 'Text') { + if (child.should_skip()) { + continue; + } + + let { data } = child; + + if (nodes.length === 0) { + const should_trim = next + ? next.type === 'Text' && + /^\s/.test(next.data) && + trimmable_at(child, next) + : !child.has_ancestor('EachBlock'); + + if (should_trim) { + data = trim_end(data); + if (!data) continue; + } + } + + // glue text nodes (which could e.g. be separated by comments) together + if (last_child && last_child.type === 'Text') { + last_child.data = data + last_child.data; + continue; + } + + nodes.unshift(child); + link(last_child, last_child = child); + } else { + nodes.unshift(child); + link(last_child, last_child = child); + } + } + + const first = nodes[0]; + if (first && first.type === 'Text') { + first.data = trim_start(first.data); + if (!first.data) { + first.var = null; + nodes.shift(); + + if (nodes[0]) { + nodes[0].prev = null; + } + } + } + + return nodes; +} + +function trimmable_at(child: INode, next_sibling: INode): boolean { + // Whitespace is trimmable if one of the following is true: + // The child and its sibling share a common nearest each block (not at an each block boundary) + // The next sibling's previous node is an each block + return ( + next_sibling.find_nearest(/EachBlock/) === + child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock' + ); +} diff --git a/src/compiler/utils/link.ts b/src/compiler/utils/link.ts new file mode 100644 index 0000000000..7cdef75d94 --- /dev/null +++ b/src/compiler/utils/link.ts @@ -0,0 +1,4 @@ +export function link(next: T, prev: T) { + prev.next = next; + if (next) next.prev = prev; +} diff --git a/test/runtime/samples/component-slot-fallback-empty/Nested.svelte b/test/runtime/samples/component-slot-fallback-empty/Nested.svelte new file mode 100644 index 0000000000..9e6683feb7 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-empty/Nested.svelte @@ -0,0 +1,4 @@ +
+

default fallback content

+ bar fallback +
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-empty/_config.js b/test/runtime/samples/component-slot-fallback-empty/_config.js new file mode 100644 index 0000000000..2e153d24db --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-empty/_config.js @@ -0,0 +1,13 @@ +export default { + html: ` +
+

default fallback content

+ +
+ +
+

default fallback content

+ bar fallback +
+ ` +}; diff --git a/test/runtime/samples/component-slot-fallback-empty/main.svelte b/test/runtime/samples/component-slot-fallback-empty/main.svelte new file mode 100644 index 0000000000..7ae5f4c5d7 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-empty/main.svelte @@ -0,0 +1,10 @@ + + + + + + + + From f2ee7efb942d68e22881fb042ff6870473c2d82e Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 4 Mar 2020 19:56:13 -0500 Subject: [PATCH 11/23] add dev runtime warning for unknown slot names (#4501) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/Block.ts | 3 +-- src/compiler/compile/render_dom/index.ts | 5 +++-- src/runtime/internal/dev.ts | 7 +++++++ test/js/samples/capture-inject-state/expected.js | 4 ++++ test/js/samples/debug-empty/expected.js | 6 +++++- test/js/samples/debug-foo-bar-baz-things/expected.js | 6 +++++- test/js/samples/debug-foo/expected.js | 6 +++++- test/js/samples/debug-hoisted/expected.js | 5 ++++- test/js/samples/debug-no-dependencies/expected.js | 5 ++++- .../dev-warning-missing-data-computed/expected.js | 6 +++++- test/js/samples/loop-protect/expected.js | 8 ++++++-- .../runtime/samples/component-slot-warning/Nested.svelte | 1 + test/runtime/samples/component-slot-warning/_config.js | 9 +++++++++ test/runtime/samples/component-slot-warning/main.svelte | 7 +++++++ 15 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 test/runtime/samples/component-slot-warning/Nested.svelte create mode 100644 test/runtime/samples/component-slot-warning/_config.js create mode 100644 test/runtime/samples/component-slot-warning/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index b0672d790e..f39308e5ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* In `dev` mode, display a runtime warning when a component is passed an unexpected slot ([#1020](https://github.com/sveltejs/svelte/issues/1020), [#1447](https://github.com/sveltejs/svelte/issues/1447)) * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) * Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480)) * Use the fallback content for slots if they are passed only whitespace ([#4092](https://github.com/sveltejs/svelte/issues/4092)) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 62bdc5bdd9..2da77d3fbf 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -412,8 +412,7 @@ export default class Block { } has_content() { - return this.renderer.options.dev || - this.first || + return this.first || this.event_listeners.length > 0 || this.chunks.intro.length > 0 || this.chunks.outro.length > 0 || diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index d6da614276..fc3c94a2be 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -264,7 +264,7 @@ export default function dom( args.push(x`$$props`); } - const has_create_fragment = block.has_content(); + const has_create_fragment = component.compile_options.dev || block.has_content(); if (has_create_fragment) { body.push(b` function create_fragment(#ctx) { @@ -412,7 +412,8 @@ export default function dom( ${unknown_props_check} - ${component.slots.size ? b`let { $$slots = {}, $$scope } = $$props;` : null} + ${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null} + ${component.compile_options.dev && b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`} ${renderer.binding_groups.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`} diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 8aefc88ba7..751f1f802b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -89,6 +89,13 @@ export function validate_each_argument(arg) { } } +export function validate_slots(name, slot, keys) { + for (const slot_key of Object.keys(slot)) { + if (!~keys.indexOf(slot_key)) { + console.warn(`<${name}> received an unexpected slot "${slot_key}".`); + } + } +} type Props = Record; export interface SvelteComponentDev { diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js index 8593145565..cd719ac5d2 100644 --- a/test/js/samples/capture-inject-state/expected.js +++ b/test/js/samples/capture-inject-state/expected.js @@ -14,6 +14,7 @@ import { space, subscribe, text, + validate_slots, validate_store } from "svelte/internal"; @@ -114,6 +115,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + $$self.$set = $$props => { if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop)); if ("alias" in $$props) $$invalidate(1, realName = $$props.alias); diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 87d78bd698..dd142adb26 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -12,7 +12,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_slots } from "svelte/internal"; const file = undefined; @@ -75,6 +76,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + $$self.$set = $$props => { if ("name" in $$props) $$invalidate(0, name = $$props.name); }; diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 589c4a7832..977702b99f 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -14,7 +14,8 @@ import { set_data_dev, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -179,6 +180,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + $$self.$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 10129e1b28..fe62ff77bf 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -14,7 +14,8 @@ import { set_data_dev, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -171,6 +172,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + $$self.$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js index 190ff12c50..0e634297f0 100644 --- a/test/js/samples/debug-hoisted/expected.js +++ b/test/js/samples/debug-hoisted/expected.js @@ -4,7 +4,8 @@ import { dispatch_dev, init, noop, - safe_not_equal + safe_not_equal, + validate_slots } from "svelte/internal"; const file = undefined; @@ -56,6 +57,8 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); $$self.$capture_state = () => ({ obj, kobzol }); $$self.$inject_state = $$props => { diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index a097869e74..76068e8cf4 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -11,7 +11,8 @@ import { safe_not_equal, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -141,6 +142,8 @@ function instance($$self, $$props) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); return []; } diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index 9c28e04064..0a50e2cd97 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -12,7 +12,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_slots } from "svelte/internal"; const file = undefined; @@ -72,6 +73,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + $$self.$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index f433bd61a9..c52d9df437 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -11,7 +11,8 @@ import { insert_dev, loop_guard, noop, - safe_not_equal + safe_not_equal, + validate_slots } from "svelte/internal"; const { console: console_1 } = globals; @@ -110,6 +111,9 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console_1.warn(` was created with unknown prop '${key}'`); }); + let { $$slots = {}, $$scope } = $$props; + validate_slots("Component", $$slots, []); + function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { $$invalidate(0, node = $$value); @@ -161,4 +165,4 @@ class Component extends SvelteComponentDev { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/runtime/samples/component-slot-warning/Nested.svelte b/test/runtime/samples/component-slot-warning/Nested.svelte new file mode 100644 index 0000000000..c6f086d96c --- /dev/null +++ b/test/runtime/samples/component-slot-warning/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-warning/_config.js b/test/runtime/samples/component-slot-warning/_config.js new file mode 100644 index 0000000000..6ffe624782 --- /dev/null +++ b/test/runtime/samples/component-slot-warning/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + warnings: [ + ' received an unexpected slot "default".', + ' received an unexpected slot "slot1".' + ] +}; diff --git a/test/runtime/samples/component-slot-warning/main.svelte b/test/runtime/samples/component-slot-warning/main.svelte new file mode 100644 index 0000000000..c29ef3e85b --- /dev/null +++ b/test/runtime/samples/component-slot-warning/main.svelte @@ -0,0 +1,7 @@ + + + + + From a8291227ce6ebf959749ae5d6f86b017a140d4af Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 4 Mar 2020 20:28:19 -0500 Subject: [PATCH 12/23] fit bitmask overflow initial dirty value in 'if' blocks (#4507) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/Renderer.ts | 1 + .../compile/render_dom/wrappers/IfBlock.ts | 20 +++++- .../wrappers/shared/get_slot_definition.ts | 2 +- .../samples/bitmask-overflow-if/_config.js | 24 +++++++ .../samples/bitmask-overflow-if/main.svelte | 62 +++++++++++++++++++ 6 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 test/runtime/samples/bitmask-overflow-if/_config.js create mode 100644 test/runtime/samples/bitmask-overflow-if/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index f39308e5ff..e58b9b6e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) * Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480)) * Use the fallback content for slots if they are passed only whitespace ([#4092](https://github.com/sveltejs/svelte/issues/4092)) +* Fix bitmask overflow for `{#if}` blocks ([#4263](https://github.com/sveltejs/svelte/issues/4263)) * In `dev` mode, check for unknown props even if the component has no writable props ([#4323](https://github.com/sveltejs/svelte/issues/4323)) * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463)) * Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481)) diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index fbb0c76d7d..2501279214 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -230,6 +230,7 @@ export default class Renderer { return bitmask; }; + // TODO: context-overflow make it less gross return { // Using a ParenthesizedExpression allows us to create // the expression lazily. TODO would be better if diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index dfc2228bc5..0506c943f9 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -288,7 +288,7 @@ export default class IfBlockWrapper extends Wrapper { } block.chunks.init.push(b` - let ${current_block_type} = ${select_block_type}(#ctx, -1); + let ${current_block_type} = ${select_block_type}(#ctx, ${this.get_initial_dirty_bit()}); let ${name} = ${get_block}; `); @@ -407,12 +407,12 @@ export default class IfBlockWrapper extends Wrapper { if (has_else) { block.chunks.init.push(b` - ${current_block_type_index} = ${select_block_type}(#ctx, -1); + ${current_block_type_index} = ${select_block_type}(#ctx, ${this.get_initial_dirty_bit()}); ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); `); } else { block.chunks.init.push(b` - if (~(${current_block_type_index} = ${select_block_type}(#ctx, -1))) { + if (~(${current_block_type_index} = ${select_block_type}(#ctx, ${this.get_initial_dirty_bit()}))) { ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); } `); @@ -587,4 +587,18 @@ export default class IfBlockWrapper extends Wrapper { `); } } + + get_initial_dirty_bit() { + const _this = this; + // TODO: context-overflow make it less gross + + const val = x`-1`; + return { + ...val, + elements: [val], + get type() { + return _this.renderer.context_overflow ? 'ArrayExpression' : 'UnaryExpression'; + }, + }; + } } diff --git a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts index 9ab48dd035..0f28689df3 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts @@ -63,7 +63,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le const { context_lookup } = block.renderer; // i am well aware that this code is gross - // TODO make it less gross + // TODO: context-overflow make it less gross const changes = { type: 'ParenthesizedExpression', get expression() { diff --git a/test/runtime/samples/bitmask-overflow-if/_config.js b/test/runtime/samples/bitmask-overflow-if/_config.js new file mode 100644 index 0000000000..74bc70d414 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-if/_config.js @@ -0,0 +1,24 @@ +export default { + html: ` + 012345678910111213141516171819202122232425262728293031323334353637383940 + expected: true + if: true + + `, + + async test({ assert, component, target, window }) { + const button = target.querySelector("button"); + await button.dispatchEvent(new window.MouseEvent("click")); + + assert.htmlEqual( + target.innerHTML, + ` + 112345678910111213141516171819202122232425262728293031323334353637383940 + expected: false + if: false +
+ + ` + ); + } +}; diff --git a/test/runtime/samples/bitmask-overflow-if/main.svelte b/test/runtime/samples/bitmask-overflow-if/main.svelte new file mode 100644 index 0000000000..2c1c453091 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-if/main.svelte @@ -0,0 +1,62 @@ + + + +{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40} + +expected: {_a.indexOf(_0) && _0 === '0' && _1 === '1'} +{#if _a.indexOf(_0) && _0 === '0' && _1 === '1'} +if: true +{:else} +if: false +
+{/if} + + \ No newline at end of file From 345d5f27ee2ed5fdd3f0bf2bea84c9f0f57b038e Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 5 Mar 2020 10:12:30 +0800 Subject: [PATCH 13/23] fix lazy code breaks in build --- src/compiler/compile/render_dom/wrappers/IfBlock.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 0506c943f9..207e1e349e 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -9,7 +9,7 @@ import FragmentWrapper from './Fragment'; import { b, x } from 'code-red'; import { walk } from 'estree-walker'; import { is_head } from './shared/is_head'; -import { Identifier, Node } from 'estree'; +import { Identifier, Node, UnaryExpression } from 'estree'; function is_else_if(node: ElseBlock) { return ( @@ -591,14 +591,17 @@ export default class IfBlockWrapper extends Wrapper { get_initial_dirty_bit() { const _this = this; // TODO: context-overflow make it less gross - - const val = x`-1`; + const val: UnaryExpression = x`-1` as UnaryExpression; return { - ...val, - elements: [val], get type() { return _this.renderer.context_overflow ? 'ArrayExpression' : 'UnaryExpression'; }, + // as [-1] + elements: [val], + // as -1 + operator: val.operator, + prefix: val.prefix, + argument: val.argument, }; } } From fd378f2d37463a1b9f3aba03a08c6693dea98d78 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 4 Mar 2020 21:31:38 -0500 Subject: [PATCH 14/23] -> v3.19.2 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e58b9b6e06..f1564848b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.19.2 * In `dev` mode, display a runtime warning when a component is passed an unexpected slot ([#1020](https://github.com/sveltejs/svelte/issues/1020), [#1447](https://github.com/sveltejs/svelte/issues/1447)) * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) diff --git a/package-lock.json b/package-lock.json index f0f4e17422..014348f37e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.19.1", + "version": "3.19.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7a02f1da10..009e5143a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.19.1", + "version": "3.19.2", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 48721520bdb9fa28f19e6940c67fd6e1f6396813 Mon Sep 17 00:00:00 2001 From: keke Date: Sun, 8 Mar 2020 21:36:11 +0800 Subject: [PATCH 15/23] docs: fix self-closing tag (#4524) --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index c75ec694d9..0bbae24185 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -974,7 +974,7 @@ app.count += 1; Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `` [element](docs#svelte_options). ```html - + +
Length: {length}
+
Values: {values.join(',')}
+ +
\ No newline at end of file diff --git a/test/runtime/samples/$$rest-without-props/_config.js b/test/runtime/samples/$$rest-without-props/_config.js new file mode 100644 index 0000000000..017f9df561 --- /dev/null +++ b/test/runtime/samples/$$rest-without-props/_config.js @@ -0,0 +1,54 @@ +export default { + props: { + a: 3, + b: 4, + c: 5, + d: 6 + }, + html: ` +
Length: 3
+
Values: 4,5,1
+
+ + `, + async test({ assert, target, window, }) { + const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); + const clickEvent = new window.MouseEvent('click'); + + await btn1.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 4,5,1
+
+ + `); + + await btn2.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 34,5,1
+
+ + `); + + await btn3.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 34,5,31
+
+ + `); + + await btn4.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 4
+
Values: 34,5,31,2
+
+ + `); + } +}; diff --git a/test/runtime/samples/$$rest-without-props/main.svelte b/test/runtime/samples/$$rest-without-props/main.svelte new file mode 100644 index 0000000000..21b2690584 --- /dev/null +++ b/test/runtime/samples/$$rest-without-props/main.svelte @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/$$rest/App.svelte b/test/runtime/samples/$$rest/App.svelte new file mode 100644 index 0000000000..875372f670 --- /dev/null +++ b/test/runtime/samples/$$rest/App.svelte @@ -0,0 +1,13 @@ + +
Length: {length}
+
Values: {values.join(',')}
+ +
+
\ No newline at end of file diff --git a/test/runtime/samples/$$rest/_config.js b/test/runtime/samples/$$rest/_config.js new file mode 100644 index 0000000000..255927f354 --- /dev/null +++ b/test/runtime/samples/$$rest/_config.js @@ -0,0 +1,60 @@ +export default { + props: { + a: 3, + b: 4, + c: 5, + d: 6 + }, + html: ` +
Length: 3
+
Values: 4,5,1
+
+
+ + `, + + async test({ assert, target, window, }) { + const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); + const clickEvent = new window.MouseEvent('click'); + + await btn1.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 4,5,1
+
+
+ + `); + + await btn2.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 34,5,1
+
+
+ + `); + + await btn3.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 3
+
Values: 34,5,31
+
+
+ + `); + + await btn4.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` +
Length: 4
+
Values: 34,5,31,2
+
+
+ + `); + } +}; diff --git a/test/runtime/samples/$$rest/main.svelte b/test/runtime/samples/$$rest/main.svelte new file mode 100644 index 0000000000..21b2690584 --- /dev/null +++ b/test/runtime/samples/$$rest/main.svelte @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/test/runtime/samples/dev-warning-unknown-props-with-$$props/Foo.svelte b/test/runtime/samples/dev-warning-unknown-props-with-$$props/Foo.svelte index 9e5c62339d..375b1a6a0a 100644 --- a/test/runtime/samples/dev-warning-unknown-props-with-$$props/Foo.svelte +++ b/test/runtime/samples/dev-warning-unknown-props-with-$$props/Foo.svelte @@ -4,4 +4,4 @@
{foo}
-
{JSON.stringify($$props)}
+
{JSON.stringify($$restProps)}
diff --git a/test/runtime/samples/dev-warning-unknown-props-with-$$rest/Foo.svelte b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/Foo.svelte new file mode 100644 index 0000000000..9e5c62339d --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/Foo.svelte @@ -0,0 +1,7 @@ + + +
{foo}
+
{JSON.stringify($$props)}
diff --git a/test/runtime/samples/dev-warning-unknown-props-with-$$rest/_config.js b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/_config.js new file mode 100644 index 0000000000..62ad08624d --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/_config.js @@ -0,0 +1,7 @@ +export default { + compileOptions: { + dev: true + }, + + warnings: [] +}; diff --git a/test/runtime/samples/dev-warning-unknown-props-with-$$rest/main.svelte b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/main.svelte new file mode 100644 index 0000000000..1566cf3e41 --- /dev/null +++ b/test/runtime/samples/dev-warning-unknown-props-with-$$rest/main.svelte @@ -0,0 +1,5 @@ + + + From a66437b3c15e735139f9afb2ec25e3e1c612cd82 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 12 Mar 2020 19:51:59 +0800 Subject: [PATCH 17/23] allow to be part of a slot (#4532) --- CHANGELOG.md | 1 + src/compiler/parse/state/tag.ts | 4 ++-- test/parser/samples/error-self-reference/error.json | 2 +- .../samples/self-reference-component/Countdown.svelte | 7 +++++++ .../samples/self-reference-component/_config.js | 3 +++ .../samples/self-reference-component/main.svelte | 10 ++++++++++ 6 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/self-reference-component/Countdown.svelte create mode 100644 test/runtime/samples/self-reference-component/_config.js create mode 100644 test/runtime/samples/self-reference-component/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 7848af35be..451fc58ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) ## 3.19.2 diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 40bb01de9b..ce8a0a9aa7 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -241,7 +241,7 @@ function read_tag_name(parser: Parser) { while (i--) { const fragment = parser.stack[i]; - if (fragment.type === 'IfBlock' || fragment.type === 'EachBlock') { + if (fragment.type === 'IfBlock' || fragment.type === 'EachBlock' || fragment.type === 'InlineComponent') { legal = true; break; } @@ -250,7 +250,7 @@ function read_tag_name(parser: Parser) { if (!legal) { parser.error({ code: `invalid-self-placement`, - message: ` components can only exist inside if-blocks or each-blocks` + message: ` components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components` }, start); } diff --git a/test/parser/samples/error-self-reference/error.json b/test/parser/samples/error-self-reference/error.json index 798b5cb300..31ecab2b12 100644 --- a/test/parser/samples/error-self-reference/error.json +++ b/test/parser/samples/error-self-reference/error.json @@ -1,6 +1,6 @@ { "code": "invalid-self-placement", - "message": " components can only exist inside if-blocks or each-blocks", + "message": " components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components", "start": { "line": 1, "column": 1, diff --git a/test/runtime/samples/self-reference-component/Countdown.svelte b/test/runtime/samples/self-reference-component/Countdown.svelte new file mode 100644 index 0000000000..21178f2567 --- /dev/null +++ b/test/runtime/samples/self-reference-component/Countdown.svelte @@ -0,0 +1,7 @@ + + +{#if count > 0} + +{/if} \ No newline at end of file diff --git a/test/runtime/samples/self-reference-component/_config.js b/test/runtime/samples/self-reference-component/_config.js new file mode 100644 index 0000000000..e3e0ad3a4f --- /dev/null +++ b/test/runtime/samples/self-reference-component/_config.js @@ -0,0 +1,3 @@ +export default { + html: '5 4 3 2 1 0', +}; \ No newline at end of file diff --git a/test/runtime/samples/self-reference-component/main.svelte b/test/runtime/samples/self-reference-component/main.svelte new file mode 100644 index 0000000000..fd28ec4e40 --- /dev/null +++ b/test/runtime/samples/self-reference-component/main.svelte @@ -0,0 +1,10 @@ + + +{count} + + + + \ No newline at end of file From ec3589e31425c54cda3c5f6a80b89eb3aaa7bd52 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 15 Mar 2020 02:15:24 +0800 Subject: [PATCH 18/23] fix hydration of top-level content (#4550) --- CHANGELOG.md | 1 + src/runtime/internal/Component.ts | 6 ++++-- test/hydration/samples/top-level-cleanup-2/_after.html | 1 + test/hydration/samples/top-level-cleanup-2/_before.html | 2 ++ test/hydration/samples/top-level-cleanup-2/_config.js | 1 + test/hydration/samples/top-level-cleanup-2/main.svelte | 1 + test/hydration/samples/top-level-cleanup/_after.html | 1 + test/hydration/samples/top-level-cleanup/_before.html | 1 + test/hydration/samples/top-level-cleanup/_config.js | 1 + test/hydration/samples/top-level-cleanup/main.svelte | 1 + 10 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/hydration/samples/top-level-cleanup-2/_after.html create mode 100644 test/hydration/samples/top-level-cleanup-2/_before.html create mode 100644 test/hydration/samples/top-level-cleanup-2/_config.js create mode 100644 test/hydration/samples/top-level-cleanup-2/main.svelte create mode 100644 test/hydration/samples/top-level-cleanup/_after.html create mode 100644 test/hydration/samples/top-level-cleanup/_before.html create mode 100644 test/hydration/samples/top-level-cleanup/_config.js create mode 100644 test/hydration/samples/top-level-cleanup/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 451fc58ca6..084a900295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) +* Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542)) ## 3.19.2 diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 10588a0804..7d2a92fa1b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,7 +1,7 @@ import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; import { blank_object, is_function, run, run_all, noop } from './utils'; -import { children } from './dom'; +import { children, detach } from './dom'; import { transition_in } from './transitions'; interface Fragment { @@ -146,8 +146,10 @@ export function init(component, options, instance, create_fragment, not_equal, p if (options.target) { if (options.hydrate) { + const nodes = children(options.target); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - $$.fragment && $$.fragment!.l(children(options.target)); + $$.fragment && $$.fragment!.l(nodes); + nodes.forEach(detach); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment!.c(); diff --git a/test/hydration/samples/top-level-cleanup-2/_after.html b/test/hydration/samples/top-level-cleanup-2/_after.html new file mode 100644 index 0000000000..8dca56dd88 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup-2/_after.html @@ -0,0 +1 @@ +
Hello world
\ No newline at end of file diff --git a/test/hydration/samples/top-level-cleanup-2/_before.html b/test/hydration/samples/top-level-cleanup-2/_before.html new file mode 100644 index 0000000000..fdc2379f63 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup-2/_before.html @@ -0,0 +1,2 @@ +
This should be thrown away
+
hello
\ No newline at end of file diff --git a/test/hydration/samples/top-level-cleanup-2/_config.js b/test/hydration/samples/top-level-cleanup-2/_config.js new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup-2/_config.js @@ -0,0 +1 @@ +export default {}; diff --git a/test/hydration/samples/top-level-cleanup-2/main.svelte b/test/hydration/samples/top-level-cleanup-2/main.svelte new file mode 100644 index 0000000000..8dca56dd88 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup-2/main.svelte @@ -0,0 +1 @@ +
Hello world
\ No newline at end of file diff --git a/test/hydration/samples/top-level-cleanup/_after.html b/test/hydration/samples/top-level-cleanup/_after.html new file mode 100644 index 0000000000..8dca56dd88 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup/_after.html @@ -0,0 +1 @@ +
Hello world
\ No newline at end of file diff --git a/test/hydration/samples/top-level-cleanup/_before.html b/test/hydration/samples/top-level-cleanup/_before.html new file mode 100644 index 0000000000..c7e4bba233 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup/_before.html @@ -0,0 +1 @@ +
This should be thrown away
\ No newline at end of file diff --git a/test/hydration/samples/top-level-cleanup/_config.js b/test/hydration/samples/top-level-cleanup/_config.js new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup/_config.js @@ -0,0 +1 @@ +export default {}; diff --git a/test/hydration/samples/top-level-cleanup/main.svelte b/test/hydration/samples/top-level-cleanup/main.svelte new file mode 100644 index 0000000000..8dca56dd88 --- /dev/null +++ b/test/hydration/samples/top-level-cleanup/main.svelte @@ -0,0 +1 @@ +
Hello world
\ No newline at end of file From 404ed3dbfe84e2d16c2a3658257536e194c33463 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 15 Mar 2020 02:27:39 +0800 Subject: [PATCH 19/23] fix else-block update in keyed each-block (#4558) Co-authored-by: Benjamin W. Broersma --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/EachBlock.ts | 60 +++++++++---------- .../samples/each-block-keyed-else/_config.js | 37 ++++++++++++ .../samples/each-block-keyed-else/main.svelte | 12 ++++ .../each-block-unkeyed-else-2/_config.js | 37 ++++++++++++ .../each-block-unkeyed-else-2/main.svelte | 12 ++++ 6 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 test/runtime/samples/each-block-keyed-else/_config.js create mode 100644 test/runtime/samples/each-block-keyed-else/main.svelte create mode 100644 test/runtime/samples/each-block-unkeyed-else-2/_config.js create mode 100644 test/runtime/samples/each-block-unkeyed-else-2/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 084a900295..1e9024c318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) +* Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549)) * Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542)) ## 3.19.2 diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index f0dfa5fbcc..43a0f754f9 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -62,6 +62,8 @@ export default class EachBlockWrapper extends Wrapper { context_props: Array; index_name: Identifier; + updates: Array = []; + dependencies: Set; var: Identifier = { type: 'Identifier', name: 'each' }; @@ -235,6 +237,12 @@ export default class EachBlockWrapper extends Wrapper { update_mount_node }; + const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only + this.node.expression.dynamic_dependencies().forEach((dependency: string) => { + all_dependencies.add(dependency); + }); + this.dependencies = all_dependencies; + if (this.node.key) { this.render_keyed(args); } else { @@ -291,7 +299,7 @@ export default class EachBlockWrapper extends Wrapper { `); if (this.else.block.has_update_method) { - block.chunks.update.push(b` + this.updates.push(b` if (!${this.vars.data_length} && ${each_block_else}) { ${each_block_else}.p(#ctx, #dirty); } else if (!${this.vars.data_length}) { @@ -304,7 +312,7 @@ export default class EachBlockWrapper extends Wrapper { } `); } else { - block.chunks.update.push(b` + this.updates.push(b` if (${this.vars.data_length}) { if (${each_block_else}) { ${each_block_else}.d(1); @@ -323,6 +331,14 @@ export default class EachBlockWrapper extends Wrapper { `); } + if (this.updates.length) { + block.chunks.update.push(b` + if (${block.renderer.dirty(Array.from(all_dependencies))}) { + ${this.updates} + } + `); + } + this.fragment.render(this.block, null, x`#nodes` as Identifier); if (this.else) { @@ -415,24 +431,17 @@ export default class EachBlockWrapper extends Wrapper { ? `@outro_and_destroy_block` : `@destroy_block`; - const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only - this.node.expression.dynamic_dependencies().forEach((dependency: string) => { - all_dependencies.add(dependency); - }); + if (this.dependencies.size) { + this.updates.push(b` + const ${this.vars.each_block_value} = ${snippet}; + ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} - if (all_dependencies.size) { - block.chunks.update.push(b` - if (${block.renderer.dirty(Array.from(all_dependencies))}) { - const ${this.vars.each_block_value} = ${snippet}; - ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} - - ${this.block.has_outros && b`@group_outros();`} - ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} - ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} - ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); - ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} - ${this.block.has_outros && b`@check_outros();`} - } + ${this.block.has_outros && b`@group_outros();`} + ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} + ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} + ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); + ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} + ${this.block.has_outros && b`@check_outros();`} `); } @@ -504,12 +513,7 @@ export default class EachBlockWrapper extends Wrapper { } `); - const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only - this.node.expression.dynamic_dependencies().forEach((dependency: string) => { - all_dependencies.add(dependency); - }); - - if (all_dependencies.size) { + if (this.dependencies.size) { const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method); const for_loop_body = this.block.has_update_method @@ -588,11 +592,7 @@ export default class EachBlockWrapper extends Wrapper { ${remove_old_blocks} `; - block.chunks.update.push(b` - if (${block.renderer.dirty(Array.from(all_dependencies))}) { - ${update} - } - `); + this.updates.push(update); } if (this.block.has_outros) { diff --git a/test/runtime/samples/each-block-keyed-else/_config.js b/test/runtime/samples/each-block-keyed-else/_config.js new file mode 100644 index 0000000000..a5bf722a80 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-else/_config.js @@ -0,0 +1,37 @@ +export default { + props: { + animals: ['alpaca', 'baboon', 'capybara'], + foo: 'something else' + }, + + html: ` + before +

alpaca

+

baboon

+

capybara

+ after + `, + + test({ assert, component, target }) { + component.animals = []; + assert.htmlEqual(target.innerHTML, ` + before +

no animals, but rather something else

+ after + `); + + component.foo = 'something other'; + assert.htmlEqual(target.innerHTML, ` + before +

no animals, but rather something other

+ after + `); + + component.animals = ['wombat']; + assert.htmlEqual(target.innerHTML, ` + before +

wombat

+ after + `); + } +}; diff --git a/test/runtime/samples/each-block-keyed-else/main.svelte b/test/runtime/samples/each-block-keyed-else/main.svelte new file mode 100644 index 0000000000..2a82653ff1 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-else/main.svelte @@ -0,0 +1,12 @@ + + +before +{#each animals as animal (animal)} +

{animal}

+{:else} +

no animals, but rather {foo}

+{/each} +after diff --git a/test/runtime/samples/each-block-unkeyed-else-2/_config.js b/test/runtime/samples/each-block-unkeyed-else-2/_config.js new file mode 100644 index 0000000000..a5bf722a80 --- /dev/null +++ b/test/runtime/samples/each-block-unkeyed-else-2/_config.js @@ -0,0 +1,37 @@ +export default { + props: { + animals: ['alpaca', 'baboon', 'capybara'], + foo: 'something else' + }, + + html: ` + before +

alpaca

+

baboon

+

capybara

+ after + `, + + test({ assert, component, target }) { + component.animals = []; + assert.htmlEqual(target.innerHTML, ` + before +

no animals, but rather something else

+ after + `); + + component.foo = 'something other'; + assert.htmlEqual(target.innerHTML, ` + before +

no animals, but rather something other

+ after + `); + + component.animals = ['wombat']; + assert.htmlEqual(target.innerHTML, ` + before +

wombat

+ after + `); + } +}; diff --git a/test/runtime/samples/each-block-unkeyed-else-2/main.svelte b/test/runtime/samples/each-block-unkeyed-else-2/main.svelte new file mode 100644 index 0000000000..3275cb1f83 --- /dev/null +++ b/test/runtime/samples/each-block-unkeyed-else-2/main.svelte @@ -0,0 +1,12 @@ + + +before +{#each animals as animal} +

{animal}

+{:else} +

no animals, but rather {foo}

+{/each} +after From ef791cc61644e013ea9bf8af4791d737b2241835 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2020 16:10:38 -0400 Subject: [PATCH 20/23] Bump acorn from 7.1.0 to 7.1.1 (#4553) Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.1.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/7.1.0...7.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 014348f37e..858d90fd1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -280,9 +280,9 @@ "dev": true }, "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "acorn-globals": { From 0ccdca21da697bed429e6fda7d57e1e6e02af970 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2020 16:10:57 -0400 Subject: [PATCH 21/23] Bump acorn from 7.0.0 to 7.1.1 in /site (#4554) Bumps [acorn](https://github.com/acornjs/acorn) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/7.0.0...7.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index 646ca0ce53..5ce377c88c 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1358,9 +1358,9 @@ } }, "acorn": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", - "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "ansi-colors": { From e06a900b2331e70f8b057dcbd36f6bd304f4c7dc Mon Sep 17 00:00:00 2001 From: vlasy Date: Sat, 14 Mar 2020 21:15:37 +0100 Subject: [PATCH 22/23] throw compiler error when binding directly to const variables (#4506) --- src/compiler/compile/nodes/Binding.ts | 5 +++++ .../samples/binding-const-field/errors.json | 1 + .../samples/binding-const-field/input.svelte | 7 +++++++ test/validator/samples/binding-const/errors.json | 15 +++++++++++++++ test/validator/samples/binding-const/input.svelte | 5 +++++ 5 files changed, 33 insertions(+) create mode 100644 test/validator/samples/binding-const-field/errors.json create mode 100644 test/validator/samples/binding-const-field/input.svelte create mode 100644 test/validator/samples/binding-const/errors.json create mode 100644 test/validator/samples/binding-const/input.svelte diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 7d6fad0a81..2f199709c8 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -72,6 +72,11 @@ export default class Binding extends Node { }); variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; + + if (info.expression.type === 'Identifier' && !variable.writable) component.error(this.expression.node, { + code: 'invalid-binding', + message: 'Cannot bind to a variable which is not writable', + }); } const type = parent.get_static_attribute_value('type'); diff --git a/test/validator/samples/binding-const-field/errors.json b/test/validator/samples/binding-const-field/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/binding-const-field/errors.json @@ -0,0 +1 @@ +[] diff --git a/test/validator/samples/binding-const-field/input.svelte b/test/validator/samples/binding-const-field/input.svelte new file mode 100644 index 0000000000..055a16438d --- /dev/null +++ b/test/validator/samples/binding-const-field/input.svelte @@ -0,0 +1,7 @@ + + + diff --git a/test/validator/samples/binding-const/errors.json b/test/validator/samples/binding-const/errors.json new file mode 100644 index 0000000000..6d48af9c4e --- /dev/null +++ b/test/validator/samples/binding-const/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "invalid-binding", + "message": "Cannot bind to a variable which is not writable", + "pos": 61, + "start": { + "line": 5, + "column": 19, + "character": 61 + }, + "end": { + "line": 5, + "column": 24, + "character": 66 + } +}] \ No newline at end of file diff --git a/test/validator/samples/binding-const/input.svelte b/test/validator/samples/binding-const/input.svelte new file mode 100644 index 0000000000..1857a1932c --- /dev/null +++ b/test/validator/samples/binding-const/input.svelte @@ -0,0 +1,5 @@ + + + From c46b3727f11132cf765028522d97f3fedf91c051 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 14 Mar 2020 16:17:12 -0400 Subject: [PATCH 23/23] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9024c318..6b9d08ea2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) +* Disallow binding directly to `const` variables ([#4479](https://github.com/sveltejs/svelte/issues/4479)) * Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549)) * Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542))