From 24bf1af832f2866cf88b2954fc2d881ceeeb3a01 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 16 Nov 2024 14:01:51 +0000 Subject: [PATCH 1/4] fix: ensure inline object literals are correctly serialised --- .changeset/wicked-readers-knock.md | 5 +++++ .../compiler/phases/2-analyze/visitors/shared/utils.js | 3 +++ .../samples/inline-expressions-2/_config.js | 10 ++++++++++ .../samples/inline-expressions-2/main.svelte | 5 +++++ 4 files changed, 23 insertions(+) create mode 100644 .changeset/wicked-readers-knock.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte diff --git a/.changeset/wicked-readers-knock.md b/.changeset/wicked-readers-knock.md new file mode 100644 index 000000000000..fd828b003cbc --- /dev/null +++ b/.changeset/wicked-readers-knock.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure inline object literals are correctly serialised diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 82356ea619c1..0c297bb20344 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -184,6 +184,9 @@ export function is_pure(node, context) { if (node.type !== 'Identifier' && node.type !== 'MemberExpression') { return false; } + if (node.type === 'MemberExpression' && node.object.type === 'Literal') { + return true; + } const left = object(node); if (!left) return false; diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js new file mode 100644 index 000000000000..3f9859277770 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

Without text expression: 7.36

+

With text expression: 7.36

+

With text expression and function call: 7.36

+

With text expression and property access: 4

+

4

` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte new file mode 100644 index 000000000000..ec25943c759e --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte @@ -0,0 +1,5 @@ +

Without text expression: 7.36

+

With text expression: {7.36}

+

With text expression and function call: {(7.36).toLocaleString()}

+

With text expression and property access: {"test".length}

+

{"test".length}

From 6ea44e057910472e3840d21a7be30efb4bfccebd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 16 Nov 2024 11:16:19 -0500 Subject: [PATCH 2/4] Apply suggestions from code review --- .changeset/wicked-readers-knock.md | 2 +- .../src/compiler/phases/2-analyze/visitors/shared/utils.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/wicked-readers-knock.md b/.changeset/wicked-readers-knock.md index fd828b003cbc..4ec417402f20 100644 --- a/.changeset/wicked-readers-knock.md +++ b/.changeset/wicked-readers-knock.md @@ -2,4 +2,4 @@ 'svelte': patch --- -fix: ensure inline object literals are correctly serialised +fix: treat property accesses of literals as pure diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 0c297bb20344..3dd5fafb95c1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -184,6 +184,7 @@ export function is_pure(node, context) { if (node.type !== 'Identifier' && node.type !== 'MemberExpression') { return false; } + if (node.type === 'MemberExpression' && node.object.type === 'Literal') { return true; } From 3b1b2847b03c1675b18301f0b44ab3078de6b047 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 16 Nov 2024 16:46:07 +0000 Subject: [PATCH 3/4] address feedback --- .../phases/2-analyze/visitors/shared/utils.js | 27 +++++++++++++++---- .../samples/inline-expressions-2/_config.js | 1 + .../samples/inline-expressions-2/main.svelte | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 3dd5fafb95c1..6f62458bcc02 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -1,4 +1,4 @@ -/** @import { AssignmentExpression, Expression, Identifier, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ +/** @import { AssignmentExpression, Expression, Literal, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ /** @import { AST, Binding } from '#compiler' */ /** @import { AnalysisState, Context } from '../../types' */ /** @import { Scope } from '../../../scope' */ @@ -176,25 +176,42 @@ export function is_safe_identifier(expression, scope) { } /** - * @param {Expression | Super} node + * @param {Expression | Literal | Super} node * @param {Context} context * @returns {boolean} */ export function is_pure(node, context) { + if (node.type === 'Literal') { + return true; + } + if (node.type === 'CallExpression') { + if (!is_pure(node.callee, context)) { + return false; + } + for (let arg of node.arguments) { + if (!is_pure(arg.type === 'SpreadElement' ? arg.argument : arg, context)) { + return false; + } + } + return true; + } if (node.type !== 'Identifier' && node.type !== 'MemberExpression') { return false; } - if (node.type === 'MemberExpression' && node.object.type === 'Literal') { - return true; + /** @type {Expression | Super | null} */ + let left = node; + while (left.type === 'MemberExpression' && !left.computed) { + left = left.object; } - const left = object(node); if (!left) return false; if (left.type === 'Identifier') { const binding = context.state.scope.get(left.name); if (binding === null) return true; // globals are assumed to be safe + } else if (is_pure(left, context)) { + return true; } // TODO add more cases (safe Svelte imports, etc) diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js index 3f9859277770..1869540b2c55 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js @@ -6,5 +6,6 @@ export default test({

With text expression: 7.36

With text expression and function call: 7.36

With text expression and property access: 4

+

Hello name!

4

` }); diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte index ec25943c759e..78a325dcc420 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte @@ -2,4 +2,5 @@

With text expression: {7.36}

With text expression and function call: {(7.36).toLocaleString()}

With text expression and property access: {"test".length}

+

Hello {('name').toUpperCase().toLowerCase()}!

{"test".length}

From 1c4596de2f4f46c6d4fbba12596080717b148d27 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 16 Nov 2024 16:50:36 +0000 Subject: [PATCH 4/4] address feedback --- .../src/compiler/phases/2-analyze/visitors/shared/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 6f62458bcc02..2dec4361c8a0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -201,7 +201,7 @@ export function is_pure(node, context) { /** @type {Expression | Super | null} */ let left = node; - while (left.type === 'MemberExpression' && !left.computed) { + while (left.type === 'MemberExpression') { left = left.object; }