Skip to content

Commit a36b2cf

Browse files
committed
init
1 parent 3c4a8d4 commit a36b2cf

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { is_ignored } from '../../../../../state.js';
66
import { is_event_attribute } from '../../../../../utils/ast.js';
77
import * as b from '../../../../../utils/builders.js';
88
import { build_getter } from '../../utils.js';
9-
import { build_template_chunk, get_expression_id } from './utils.js';
9+
import { build_template_chunk, get_expression_id, DYNAMIC, evaluate_static_expression } from './utils.js';
1010

1111
/**
1212
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
@@ -186,7 +186,10 @@ export function build_attribute_value(value, context, memoize = (value) => value
186186
}
187187

188188
let expression = /** @type {Expression} */ (context.visit(chunk.expression));
189-
189+
let evaluated = evaluate_static_expression(expression, context.state);
190+
if (evaluated !== DYNAMIC) {
191+
return { value: b.literal(evaluated), has_state: false};
192+
}
190193
return {
191194
value: memoize(expression, chunk.metadata.expression),
192195
has_state: chunk.metadata.expression.has_state

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

Lines changed: 201 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */
1+
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super, Node, UnaryExpression, TemplateLiteral, BinaryExpression, LogicalExpression, ConditionalExpression } from 'estree' */
22
/** @import { AST, ExpressionMetadata } from '#compiler' */
33
/** @import { ComponentClientTransformState } from '../../types' */
44
import { walk } from 'zimmerframe';
@@ -74,6 +74,199 @@ function compare_expressions(a, b) {
7474

7575
return true;
7676
}
77+
export const DYNAMIC = Symbol('DYNAMIC');
78+
79+
/**
80+
*
81+
* @param {Expression | Node} node
82+
* @param {ComponentClientTransformState} state
83+
* @returns {any}
84+
*/
85+
export function evaluate_static_expression(node, state) {
86+
if (node == undefined) return DYNAMIC;
87+
/**
88+
*
89+
* @param {BinaryExpression | LogicalExpression} node
90+
*/
91+
function handle_left_right_node(node) {
92+
let left = evaluate_static_expression(node?.left, state);
93+
let right = evaluate_static_expression(node?.right, state);
94+
if (left === DYNAMIC || right === DYNAMIC) {
95+
return DYNAMIC;
96+
}
97+
switch (node.operator) {
98+
case '+':
99+
return left + right;
100+
case '-':
101+
return left - right;
102+
case '&':
103+
return left & right;
104+
case '|':
105+
return left | right;
106+
case '<<':
107+
return left << right;
108+
case '>>':
109+
return left >> right;
110+
case '>':
111+
return left > right;
112+
case '<':
113+
return left < right;
114+
case '>=':
115+
return left >= right;
116+
case '<=':
117+
return left <= right;
118+
case '==':
119+
return left == right;
120+
case '===':
121+
return left === right;
122+
case '||':
123+
return left || right;
124+
case '??':
125+
return left ?? right;
126+
case '&&':
127+
return left && right;
128+
case '%':
129+
return left % right;
130+
case '>>>':
131+
return left >>> right;
132+
case '^':
133+
return left ^ right;
134+
case '**':
135+
return left ** right;
136+
case '*':
137+
return left * right;
138+
case '/':
139+
return left / right;
140+
case '!=':
141+
return left != right;
142+
case '!==':
143+
return left !== right;
144+
default:
145+
return DYNAMIC;
146+
}
147+
}
148+
/**
149+
*
150+
* @param {UnaryExpression} node
151+
*/
152+
function handle_unary_node(node) {
153+
let argument = evaluate_static_expression(node?.argument, state);
154+
if (argument === DYNAMIC) return DYNAMIC;
155+
/**
156+
*
157+
* @param {Expression} argument
158+
*/
159+
function handle_void(argument) {
160+
//@ts-ignore
161+
let evaluated = evaluate_static_expression(argument);
162+
if (evaluated !== DYNAMIC) {
163+
return undefined;
164+
}
165+
return DYNAMIC;
166+
}
167+
switch (node.operator) {
168+
case '!':
169+
return !argument;
170+
case '-':
171+
return -argument;
172+
case 'typeof':
173+
return typeof argument;
174+
case '~':
175+
return ~argument;
176+
case '+':
177+
return +argument;
178+
case 'void':
179+
return handle_void(argument);
180+
default:
181+
// `delete` is ignored, since it may have side effects
182+
return DYNAMIC;
183+
}
184+
}
185+
/**
186+
* @param {SequenceExpression} node
187+
*/
188+
function handle_sequence(node) {
189+
let is_static = node.expressions.reduce((a, b) => a && evaluate_static_expression(b, state) !== DYNAMIC, true);
190+
if (is_static) {
191+
//@ts-ignore
192+
return evaluate_static_expression(node.expressions.at(-1), state);
193+
}
194+
return DYNAMIC;
195+
}
196+
/**
197+
* @param {string} name
198+
*/
199+
function handle_ident(name) {
200+
let scope = state.scope.get(name);
201+
if (scope?.kind === 'normal') {
202+
if (scope.initial && !scope.mutated && !scope.reassigned && !scope.updated) {
203+
//@ts-ignore
204+
let evaluated = evaluate_static_expression(scope.initial, state);
205+
return evaluated;
206+
}
207+
}
208+
return DYNAMIC;
209+
}
210+
/**
211+
* @param {TemplateLiteral} node
212+
*/
213+
function handle_template(node) {
214+
const expressions = node.expressions;
215+
const is_static = expressions.reduce((a, b) => a && evaluate_static_expression(b, state) !== DYNAMIC, true);
216+
if (is_static) {
217+
let res = '';
218+
let quasis = node.quasis;
219+
let last_was_quasi = false;
220+
let expr_index = 0;
221+
let quasi_index = 0;
222+
for (let index = 0; index < quasis.length + expressions.length; index++) {
223+
if (last_was_quasi) {
224+
res += evaluate_static_expression(expressions[expr_index++], state);
225+
last_was_quasi = false;
226+
} else {
227+
res += quasis[quasi_index++].value.cooked;
228+
last_was_quasi = true;
229+
}
230+
}
231+
return res;
232+
}
233+
return DYNAMIC;
234+
}
235+
/**
236+
* @param {ConditionalExpression} node
237+
*/
238+
function handle_ternary(node) {
239+
let test = evaluate_static_expression(node.test, state);
240+
if (test !== DYNAMIC) {
241+
if (test) {
242+
return evaluate_static_expression(node.consequent, state);
243+
} else {
244+
return evaluate_static_expression(node.alternate, state);
245+
}
246+
}
247+
return DYNAMIC;
248+
}
249+
switch (node.type) {
250+
case 'Literal':
251+
return node.value;
252+
case 'BinaryExpression':
253+
return handle_left_right_node(node);
254+
case 'LogicalExpression':
255+
return handle_left_right_node(node);
256+
case 'UnaryExpression':
257+
return handle_unary_node(node);
258+
case 'Identifier':
259+
return handle_ident(node.name);
260+
case 'SequenceExpression':
261+
return handle_sequence(node);
262+
case 'TemplateLiteral':
263+
return handle_template(node);
264+
case 'ConditionalExpression':
265+
return handle_ternary(node);
266+
default:
267+
return DYNAMIC;
268+
}
269+
}
77270

78271
/**
79272
* @param {Array<AST.Text | AST.ExpressionTag>} values
@@ -110,6 +303,13 @@ export function build_template_chunk(
110303
node.expression.name !== 'undefined' ||
111304
state.scope.get('undefined')
112305
) {
306+
let evaluated = evaluate_static_expression(node.expression, state);
307+
if (evaluated !== DYNAMIC) {
308+
if (evaluated != null) {
309+
quasi.value.cooked += evaluated + '';
310+
}
311+
continue;
312+
}
113313
let value = memoize(
114314
/** @type {Expression} */ (visit(node.expression, state)),
115315
node.metadata.expression

0 commit comments

Comments
 (0)