Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 84 additions & 12 deletions src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Slot from './nodes/Slot';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration, FunctionDeclaration, FunctionExpression, Pattern, Expression } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, b } from 'code-red';
import { print, b, x } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element';
Expand All @@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap';
import { flatten } from '../utils/flatten';

interface ComponentOptions {
namespace?: string;
Expand Down Expand Up @@ -1007,15 +1008,15 @@ export default class Component {
return null;
}

rewrite_props(get_insert: (variable: Var) => Node[]) {
rewrite_props_and_add_subscriptions(get_subscriptions: (variable: Var) => Node[]) {
if (!this.ast.instance) return;

const component = this;
const { instance_scope, instance_scope_map: map } = this;
let scope = instance_scope;

walk(this.ast.instance.content, {
enter(node: Node) {
enter(node: Node, parent: Node, key, index) {
if (regex_contains_term_function.test(node.type)) {
return this.skip();
}
Expand All @@ -1031,7 +1032,7 @@ export default class Component {
if (node.type === 'VariableDeclaration') {
// NOTE: `var` does not follow block scoping
if (node.kind === 'var' || scope === instance_scope) {
const inserts = [];
const subscriptions = [];
const props = [];

function add_new_props(exported: Identifier, local: Pattern, default_value: Expression) {
Expand Down Expand Up @@ -1067,7 +1068,7 @@ export default class Component {
function get_new_name(local: Identifier): Identifier {
const variable = component.var_lookup.get(local.name);
if (variable.subscribable) {
inserts.push(get_insert(variable));
subscriptions.push(get_subscriptions(variable));
}

if (variable.export_name && variable.writable) {
Expand Down Expand Up @@ -1136,17 +1137,88 @@ export default class Component {
add_new_props({ type: 'Identifier', name: variable.export_name }, declarator.id, declarator.init);
node.declarations.splice(index--, 1);
}
if (variable.subscribable && (is_props || declarator.init)) {
inserts.push(get_insert(variable));

const for_in_of_loop_init = key === 'left' && (parent.type === 'ForInStatement' || parent.type === 'ForOfStatement');
if (variable.subscribable && (is_props || declarator.init || for_in_of_loop_init)) {
subscriptions.push(get_subscriptions(variable));
}
}
}

// Assertion that if we see props, it must be at the top level
if (props.length > 0 && !(parent.type === 'Program' && Array.isArray(parent[key]))) {
throw new Error('export is not at the top level');
}

const flattened_subscriptions = flatten(subscriptions);
// parent.type === 'Program' or 'BlockStatement' or 'SwitchCase' and key === 'body'
if (Array.isArray(parent[key])) {
// If the variable declaration is part of some block, that is, among an array of statements
// then, we add the subscriptions and the $$props declaration after declaration
if (subscriptions.length > 0) {
parent[key].splice(index + 1, 0, ...flattened_subscriptions);
}
if (props.length > 0) {
// b`` might return a Node array, but the $$props declaration will be flattened later
parent[key].splice(index + 1, 0, b`let { ${props} } = $$props;`);
}
if (node.declarations.length == 0) {
// After extracting all the prop names, remove if there are no declarations left
parent[key].splice(index, 1);
}
} else if (subscriptions.length > 0) {

if (key === 'left' && (parent.type === 'ForInStatement' || parent.type === 'ForOfStatement')) {
// if it is for (var x in array) or for (var x of array)
// we are transforming from:
//
// for (var x of array) {
// // body
// }
// or
// for (var x of array)
// // statement
// to:
// for (var x of array) {
// // subscription inserts
// // body or statement
// }
if (parent.body.type !== 'BlockStatement') {
parent.body = {
type: 'BlockStatement',
body: [parent.body]
};
}
parent.body.body.unshift(...flattened_subscriptions);
} else if (key === 'init' && parent.type === 'ForStatement') {
// If the variable declaration is like for (var i = writable(1); ...), we instead get a dummy variable setting
// calling an immediately-invoked function expression containing all the subscription functions
// e.g. for (var i = writable(1), $$subscriptions = (() => { subscription inserts })(); ...)
node.declarations.push({
type: 'VariableDeclarator',
id: component.get_unique_name('$$subscriptions', scope),
init: x`(() => {
${flattened_subscriptions}
})()`
});
} else {
// Variable declaration is a statement without a parent list of statements, so we transform it by
// putting the var in a block statement and call subscription inserts
// e.g.
// if (condition)
// var a = writable(1);
// turns into
// if (condition) {
// var a = writable(1);
// // subscription inserts here
// }
parent[key] = {
type: 'BlockStatement',
body: [parent[key], ...flattened_subscriptions]
};
}
}

this.replace(b`
${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${props} } = $$props;`}
${inserts}
` as any);
return this.skip();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export default function dom(
}
});

component.rewrite_props(({ name, reassigned, export_name }) => {
component.rewrite_props_and_add_subscriptions(({ name, reassigned, export_name }) => {
const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index;

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default function ssr(
});
}

component.rewrite_props(({ name, reassigned }) => {
component.rewrite_props_and_add_subscriptions(({ name, reassigned }) => {
const value = `$${name}`;

let insert = reassigned
Expand Down
46 changes: 46 additions & 0 deletions test/js/samples/var-in-block/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
detach,
init,
insert,
noop,
safe_not_equal,
text
} from "svelte/internal";

function create_fragment(ctx) {
let t;

return {
c() {
t = text(/*one*/ ctx[0]);
},
m(target, anchor) {
insert(target, t, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(t);
}
};
}

function instance($$self) {
{
var one = 1;
}

return [one];
}

class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}

export default Component;
7 changes: 7 additions & 0 deletions test/js/samples/var-in-block/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
{
var one = 1;
}
</script>

{one}
20 changes: 20 additions & 0 deletions test/js/samples/variable-declaration-in-switch-case/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* generated by Svelte vX.Y.Z */
import { SvelteComponent, init, safe_not_equal } from "svelte/internal";

function instance($$self) {
switch (1) {
case 1:
const value = Math.random();
}

return [];
}

class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, null, safe_not_equal, {});
}
}

export default Component;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
switch (1) {
case 1:
const value = Math.random();
}
</script>

3 changes: 3 additions & 0 deletions test/runtime/samples/var-in-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: '<p>12345</p><p>67890</p>'
};
9 changes: 9 additions & 0 deletions test/runtime/samples/var-in-block/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
{
var foo = 12345;
var bar = 67890;
}
</script>

<p>{foo}</p>
<p>{bar}</p>
7 changes: 7 additions & 0 deletions test/runtime/samples/var-in-do-while-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
props: {
a: 13
},

html: '<p>169</p>'
};
9 changes: 9 additions & 0 deletions test/runtime/samples/var-in-do-while-block/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
export let a;
let i = 0;
do {
var b = a * i;
} while (++i <= a);
</script>

<p>{b}</p>
3 changes: 3 additions & 0 deletions test/runtime/samples/var-in-for-in-loop/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: '<p>0</p><p>2</p><p>4</p><p>6</p><p>8</p>'
};
10 changes: 10 additions & 0 deletions test/runtime/samples/var-in-for-in-loop/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
var array = [0, 1, 2, 3, 4];
for (var i in array) {
array[i] = 2 * i;
}
</script>

{#each array as a}
<p>{a}</p>
{/each}
3 changes: 3 additions & 0 deletions test/runtime/samples/var-in-for-loop/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: '<p>0</p><p>1</p><p>2</p><p>3</p><p>4</p>'
};
10 changes: 10 additions & 0 deletions test/runtime/samples/var-in-for-loop/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
var array = [];
for (var i = 0; i < 5; i++) {
array[i] = i;
}
</script>

{#each array as a}
<p>{a}</p>
{/each}
11 changes: 11 additions & 0 deletions test/runtime/samples/var-in-function/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
html: '<button></button><p>0</p>',

async test({ assert, target, window }) {
const button = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');

await button.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, '<button></button><p>4</p>');
}
};
10 changes: 10 additions & 0 deletions test/runtime/samples/var-in-function/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let foo = 0;
function handleClick() {
var random = 4;
foo += random;
}
</script>

<button on:click={handleClick} />
<p>{foo}</p>
13 changes: 13 additions & 0 deletions test/runtime/samples/var-in-if-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
props: {
condition: true
},

html: '<p>true</p><p>123</p><p>0</p>',

test({ assert, component, target }) {
component.condition = false;

assert.htmlEqual(target.innerHTML, '<p>false</p><p>123</p><p>0</p>');
}
};
15 changes: 15 additions & 0 deletions test/runtime/samples/var-in-if-block/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
export var condition;

if (condition) {
var b = 123;
var c = 0;
} else {
var b = 0;
var c = 456;
}
</script>

<p>{condition}</p>
<p>{b}</p>
<p>{c}</p>
7 changes: 7 additions & 0 deletions test/runtime/samples/var-in-while-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
props: {
a: 13
},

html: '<p>169</p>'
};
9 changes: 9 additions & 0 deletions test/runtime/samples/var-in-while-block/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
export let a;
let i = 0;
while (++i <= a) {
var b = a * i;
}
</script>

<p>{b}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: '<p>3</p><p>5</p><p>4</p>'
};
Loading