diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 8f8fe29564..f0dfa5fbcc 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -204,6 +204,9 @@ export default class EachBlockWrapper extends Wrapper { const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); + if (this.renderer.options.dev) { + block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); + } // TODO which is better — Object.create(array) or array.slice()? renderer.blocks.push(b` @@ -421,6 +424,7 @@ export default class EachBlockWrapper extends Wrapper { 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();`} @@ -572,6 +576,7 @@ export default class EachBlockWrapper extends Wrapper { const update = b` ${!this.block.has_update_method && b`const #old_length = ${this.vars.each_block_value}.length;`} ${this.vars.each_block_value} = ${snippet}; + ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} let #i; for (#i = ${start}; #i < ${data_length}; #i += 1) { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 1829225ad9..7f4b52eb3b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -79,6 +79,16 @@ export function set_data_dev(text, data) { text.data = data; } +export function validate_each_argument(arg) { + if (!arg || !('length' in arg)) { + let msg = '{#each} only iterates over array-like objects.'; + if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { + msg += ' You can use a spread to convert this iterable into an array.'; + } + throw new Error(msg); + } +} + type Props = Record; export interface SvelteComponentDev { 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 fc65c59eda..3209afe8a2 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -13,7 +13,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -88,6 +89,7 @@ function create_fragment(ctx) { let t1; let t2; let each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -122,6 +124,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 710d6b2232..4b08be44d9 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -13,7 +13,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -82,6 +83,7 @@ function create_fragment(ctx) { let t1; let t2; let each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -116,6 +118,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index 054dda7953..9985c814a2 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -10,7 +10,8 @@ import { noop, safe_not_equal, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -64,6 +65,7 @@ function create_each_block(ctx) { function create_fragment(ctx) { let each_1_anchor; let each_value = things; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -91,6 +93,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 0) { each_value = things; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js b/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js new file mode 100644 index 0000000000..83481d9ebe --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js @@ -0,0 +1,6 @@ +export default { + compileOptions: { + dev: true + }, + error: `{#each} only iterates over array-like objects. You can use a spread to convert this iterable into an array.` +}; diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte b/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte new file mode 100644 index 0000000000..e1aae26d12 --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte @@ -0,0 +1,7 @@ + + +{#each foo as item} +
{item}
+{/each} \ No newline at end of file diff --git a/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js new file mode 100644 index 0000000000..62e5fc209b --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js @@ -0,0 +1,6 @@ +export default { + compileOptions: { + dev: true + }, + error: `{#each} only iterates over array-like objects.` +}; diff --git a/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte b/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte new file mode 100644 index 0000000000..f85df8d84b --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte @@ -0,0 +1,3 @@ +{#each {} as item} +
{item}
+{/each} \ No newline at end of file