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' */
44import { 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 = [ ] ;
0 commit comments