fix: treat new expression references in template as possibly dynamic

new-expression-tpl
Dominic Gannaway 4 months ago
parent ee4b1f2c75
commit d2859a6e16

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: treat new expression references in template as possibly dynamic

@ -997,26 +997,37 @@ const common_visitors = {
const binding = context.state.scope.get(node.name);
// if no binding, means some global variable
if (binding && binding.kind !== 'normal') {
if (context.state.expression) {
context.state.expression.metadata.dynamic = true;
}
if (binding) {
if (binding.kind === 'normal') {
const initial = binding.initial;
if (initial?.type === 'NewExpression' && !binding.reassigned && context.state.expression) {
// If the identifier comes from a new expression, then the object might have a custom
// toString() and thus be reactive. So let's treat the expression as dynamic, unless the
// binding has been re-assigned.
context.state.expression.metadata.dynamic = true;
}
} else {
if (context.state.expression) {
context.state.expression.metadata.dynamic = true;
}
if (
node !== binding.node &&
// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
// it's likely not using a primitive value and thus this warning isn't that helpful.
((binding.kind === 'state' &&
(binding.reassigned ||
(binding.initial?.type === 'CallExpression' &&
binding.initial.arguments.length === 1 &&
binding.initial.arguments[0].type !== 'SpreadElement' &&
!should_proxy_or_freeze(binding.initial.arguments[0], context.state.scope)))) ||
binding.kind === 'frozen_state' ||
binding.kind === 'derived') &&
context.state.function_depth === binding.scope.function_depth
) {
warn(context.state.analysis.warnings, node, context.path, 'static-state-reference');
if (
node !== binding.node &&
// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
// it's likely not using a primitive value and thus this warning isn't that helpful.
((binding.kind === 'state' &&
(binding.reassigned ||
(binding.initial?.type === 'CallExpression' &&
binding.initial.arguments.length === 1 &&
binding.initial.arguments[0].type !== 'SpreadElement' &&
!should_proxy_or_freeze(binding.initial.arguments[0], context.state.scope)))) ||
binding.kind === 'frozen_state' ||
binding.kind === 'derived') &&
context.state.function_depth === binding.scope.function_depth
) {
warn(context.state.analysis.warnings, node, context.path, 'static-state-reference');
}
}
}
},

@ -1,8 +1,24 @@
import { flushSync } from '../../../../src/main/main-client';
import { test } from '../../test';
const date_proto = Date.prototype;
let date_proto_to_string = date_proto.toString;
export default test({
html: `<div>getSeconds: 0</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700400000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`,
html: `<div>getSeconds: 0</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700400000</div><div>toDateString: Fri Feb 23 2024</div><div>date: [date: 0, 0, 15]</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`,
before_test() {
date_proto_to_string = date_proto.toString;
// This test will fail between different machines because of timezones, so we instead mock it to be a different toString().
date_proto.toString = function () {
return `[date: ${this.getSeconds()}, ${this.getMinutes()}, ${this.getHours()}]`;
};
},
after_test() {
date_proto.toString = date_proto_to_string;
},
test({ assert, target }) {
const [btn, btn2, btn3] = target.querySelectorAll('button');
@ -13,7 +29,7 @@ export default test({
assert.htmlEqual(
target.innerHTML,
`<div>getSeconds: 1</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700401000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
`<div>getSeconds: 1</div><div>getMinutes: 0</div><div>getHours: 15</div><div>getTime: 1708700401000</div><div>toDateString: Fri Feb 23 2024</div><div>date: [date: 1, 0, 15]</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
);
flushSync(() => {
@ -22,7 +38,7 @@ export default test({
assert.htmlEqual(
target.innerHTML,
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 15</div><div>getTime: 1708700461000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 15</div><div>getTime: 1708700461000</div><div>toDateString: Fri Feb 23 2024</div><div>date: [date: 1, 1, 15]</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
);
flushSync(() => {
@ -31,7 +47,7 @@ export default test({
assert.htmlEqual(
target.innerHTML,
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 16</div><div>getTime: 1708704061000</div><div>toDateString: Fri Feb 23 2024</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
`<div>getSeconds: 1</div><div>getMinutes: 1</div><div>getHours: 16</div><div>getTime: 1708704061000</div><div>toDateString: Fri Feb 23 2024</div><div>date: [date: 1, 1, 16]</div><button>1 second</button><button>1 minute</button><button>1 hour</button>`
);
}
});

@ -9,6 +9,7 @@
<div>getHours: {date.getUTCHours()}</div>
<div>getTime: {date.getTime()}</div>
<div>toDateString: {date.toDateString()}</div>
<div>date: {date}</div>
<button onclick={() => {
date.setSeconds(date.getSeconds() + 1);

Loading…
Cancel
Save