Skip to content

Commit 44c9c89

Browse files
committed
start reassignments and if blocks, more ts annotations
1 parent 6917011 commit 44c9c89

File tree

5 files changed

+749
-453
lines changed

5 files changed

+749
-453
lines changed

packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const visitors = {
4343
delete n.typeParameters;
4444
delete n.typeArguments;
4545
delete n.returnType;
46+
// TODO figure out what this is exactly, and if it should be added to `type_information`
4647
delete n.accessibility;
4748
},
4849
Decorator(node) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/** @import { BlockStatement, Expression } from 'estree' */
2+
/** @import { AST } from '#compiler' */
3+
/** @import { ComponentContext } from '../types' */
4+
import * as b from '#compiler/builders';
5+
6+
/**
7+
* @param {AST.IfBlock} node
8+
* @param {ComponentContext} context
9+
*/
10+
export function IfBlock(node, context) {
11+
const test = /** @type {Expression} */ (context.visit(node.test));
12+
const evaluated = context.state.scope.evaluate(test);
13+
14+
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
15+
context.state.template.push('<!>');
16+
if (evaluated.is_truthy) {
17+
context.state.init.push(b.stmt(b.call(b.arrow([b.id('$$anchor')], consequent), context.state.node)));
18+
} else {
19+
const statements = [];
20+
const consequent_id = context.state.scope.generate('consequent');
21+
statements.push(b.var(b.id(consequent_id), b.arrow([b.id('$$anchor')], consequent)));
22+
23+
let alternate_id;
24+
25+
if (node.alternate) {
26+
alternate_id = context.state.scope.generate('alternate');
27+
const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate));
28+
const nodes = node.alternate.nodes;
29+
30+
let alternate_args = [b.id('$$anchor')];
31+
if (nodes.length === 1 && nodes[0].type === 'IfBlock' && nodes[0].elseif) {
32+
alternate_args.push(b.id('$$elseif'));
33+
}
34+
35+
statements.push(b.var(b.id(alternate_id), b.arrow(alternate_args, alternate)));
36+
}
37+
38+
/** @type {Expression[]} */
39+
const args = [
40+
node.elseif ? b.id('$$anchor') : context.state.node,
41+
b.arrow(
42+
[b.id('$$render')],
43+
b.block([
44+
b.if(
45+
test,
46+
b.stmt(b.call(b.id('$$render'), b.id(consequent_id))),
47+
alternate_id ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.false)) : undefined
48+
)
49+
])
50+
)
51+
];
52+
53+
if (node.elseif) {
54+
// We treat this...
55+
//
56+
// {#if x}
57+
// ...
58+
// {:else}
59+
// {#if y}
60+
// <div transition:foo>...</div>
61+
// {/if}
62+
// {/if}
63+
//
64+
// ...slightly differently to this...
65+
//
66+
// {#if x}
67+
// ...
68+
// {:else if y}
69+
// <div transition:foo>...</div>
70+
// {/if}
71+
//
72+
// ...even though they're logically equivalent. In the first case, the
73+
// transition will only play when `y` changes, but in the second it
74+
// should play when `x` or `y` change — both are considered 'local'
75+
args.push(b.id('$$elseif'));
76+
}
77+
78+
statements.push(b.stmt(b.call('$.if', ...args)));
79+
80+
context.state.init.push(b.block(statements));
81+
}
82+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/** @import { BlockStatement, Expression } from 'estree' */
2+
/** @import { AST } from '#compiler' */
3+
/** @import { ComponentContext } from '../types.js' */
4+
import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js';
5+
import * as b from '#compiler/builders';
6+
import { block_close, block_open } from './shared/utils.js';
7+
import { needs_new_scope } from '../../utils.js';
8+
9+
/**
10+
* @param {AST.IfBlock} node
11+
* @param {ComponentContext} context
12+
*/
13+
export function IfBlock(node, context) {
14+
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
15+
const test = /** @type {Expression} */ (context.visit(node.test));
16+
const evaluated = context.state.scope.evaluate(test);
17+
if (evaluated.is_truthy) {
18+
if (needs_new_scope(consequent)) {
19+
context.state.template.push(consequent);
20+
} else {
21+
context.state.template.push(...consequent.body);
22+
}
23+
} else {
24+
consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open)));
25+
let if_statement = b.if(test, consequent);
26+
27+
context.state.template.push(if_statement, block_close);
28+
29+
let index = 1;
30+
let alt = node.alternate;
31+
while (
32+
alt &&
33+
alt.nodes.length === 1 &&
34+
alt.nodes[0].type === 'IfBlock' &&
35+
alt.nodes[0].elseif
36+
) {
37+
const elseif = alt.nodes[0];
38+
const alternate = /** @type {BlockStatement} */ (context.visit(elseif.consequent));
39+
alternate.body.unshift(
40+
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(`<!--[${index++}-->`)))
41+
);
42+
if_statement = if_statement.alternate = b.if(
43+
/** @type {Expression} */ (context.visit(elseif.test)),
44+
alternate
45+
);
46+
alt = elseif.alternate;
47+
}
48+
49+
if_statement.alternate = alt ? /** @type {BlockStatement} */ (context.visit(alt)) : b.block([]);
50+
if_statement.alternate.body.unshift(
51+
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE)))
52+
);
53+
}
54+
}

packages/svelte/src/compiler/phases/3-transform/utils.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @import { Context } from 'zimmerframe' */
22
/** @import { TransformState } from './types.js' */
33
/** @import { AST, Binding, Namespace, ValidatedCompileOptions } from '#compiler' */
4-
/** @import { Node, Expression, CallExpression } from 'estree' */
4+
/** @import { Node, Expression, CallExpression, BlockStatement } from 'estree' */
55
import {
66
regex_ends_with_whitespaces,
77
regex_not_whitespace,
@@ -486,3 +486,14 @@ export function transform_inspect_rune(node, context) {
486486
return b.call('$.inspect', as_fn ? b.thunk(b.array(arg)) : b.array(arg));
487487
}
488488
}
489+
490+
/**
491+
* Whether a `BlockStatement` needs to be a block statement as opposed to just inlining all of its statements.
492+
* @param {BlockStatement} block
493+
*/
494+
export function needs_new_scope(block) {
495+
const has_vars = block.body.some(child => child.type === 'VariableDeclaration');
496+
const has_fns = block.body.some(child => child.type === 'FunctionDeclaration');
497+
const has_class = block.body.some(child => child.type === 'ClassDeclaration');
498+
return has_vars || has_fns || has_class;
499+
}

0 commit comments

Comments
 (0)