Skip to content

Commit bcb6b72

Browse files
committed
bail out pure expressions
1 parent 1f51993 commit bcb6b72

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression } from 'estree' */
1+
/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern } from 'estree' */
22
/** @import { AST, ExpressionMetadata } from '#compiler' */
33
/** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */
44
import { walk } from 'zimmerframe';
@@ -361,6 +361,54 @@ export function validate_mutation(node, context, expression) {
361361
);
362362
}
363363

364+
/**
365+
* Checks whether the expression contains assignments, function calls, or member accesses
366+
* @param {Expression|Pattern} expression
367+
* @returns {boolean}
368+
*/
369+
function is_pure_expression(expression) {
370+
// It's supposed that values do not have custom @@toPrimitive() or toString(),
371+
// which may be implicitly called in expressions like `a + b`, `a & b`, `+a`, `str: ${a}`
372+
switch (expression.type) {
373+
case "ArrayExpression": return expression.elements.every((element) => element == null || (element.type === "SpreadElement" ? false : is_pure_expression(element)));
374+
case "BinaryExpression": return expression.left.type !== "PrivateIdentifier" && is_pure_expression(expression.left) && is_pure_expression(expression.right);
375+
case "ConditionalExpression": return is_pure_expression(expression.test) && is_pure_expression(expression.consequent) && is_pure_expression(expression.alternate);
376+
case "Identifier": return true;
377+
case "Literal": return true;
378+
case "LogicalExpression": return is_pure_expression(expression.left) && is_pure_expression(expression.right);
379+
case "MetaProperty": return true; // new.target
380+
case "ObjectExpression": return expression.properties.every((property) =>
381+
property.type !== "SpreadElement"
382+
&& property.key.type !== "PrivateIdentifier"
383+
&& is_pure_expression(property.key)
384+
&& is_pure_expression(property.value)
385+
);
386+
case "SequenceExpression": return expression.expressions.every(is_pure_expression);
387+
case "TemplateLiteral": return expression.expressions.every(is_pure_expression);
388+
case "ThisExpression": return true;
389+
case "UnaryExpression": return is_pure_expression(expression.argument);
390+
case "YieldExpression": return expression.argument == null || is_pure_expression(expression.argument);
391+
392+
case "ArrayPattern":
393+
case "ArrowFunctionExpression":
394+
case "AssignmentExpression":
395+
case "AssignmentPattern":
396+
case "AwaitExpression":
397+
case "CallExpression":
398+
case "ChainExpression":
399+
case "ClassExpression":
400+
case "FunctionExpression":
401+
case "ImportExpression":
402+
case "MemberExpression":
403+
case "NewExpression":
404+
case "ObjectPattern":
405+
case "RestElement":
406+
case "TaggedTemplateExpression":
407+
case "UpdateExpression":
408+
return false;
409+
}
410+
}
411+
364412
/**
365413
* Serializes an expression with reactivity like in Svelte 4
366414
* @param {Expression} expression
@@ -370,7 +418,7 @@ export function build_legacy_expression(expression, context) {
370418
// To recreate Svelte 4 behaviour, we track the dependencies
371419
// the compiler can 'see', but we untrack the effect itself
372420
const serialized_expression = /** @type {Expression} */ (context.visit(expression));
373-
if (expression.type === "Identifier") return serialized_expression;
421+
if (is_pure_expression(expression)) return serialized_expression;
374422

375423
/** @type {Expression[]} */
376424
const sequence = [];

packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function Each_index_non_null($$anchor) {
88
var fragment = $.comment();
99
var node = $.first_child(fragment);
1010

11-
$.each(node, 0, () => Array(10), $.index, ($$anchor, $$item, i) => {
11+
$.each(node, 0, () => [,,,,,], $.index, ($$anchor, $$item, i) => {
1212
var p = root_1();
1313

1414
p.textContent = `index: ${i}`;

packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/server/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as $ from 'svelte/internal/server';
22

33
export default function Each_index_non_null($$payload) {
4-
const each_array = $.ensure_array_like(Array(10));
4+
const each_array = $.ensure_array_like([,,,,,]);
55

66
$$payload.out += `<!--[-->`;
77

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
{#each Array(10), i}
1+
{#each [,,,,,], i}
22
<p>index: {i}</p>
33
{/each}

0 commit comments

Comments
 (0)