fix: improve prop binding warning validation for stores (#12745)

* fix: improve prop binding warning validation for stores

* ts

* address feedback

* add comment

* failing test

* fix/simplify

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12752/head
Dominic Gannaway 5 months ago committed by GitHub
parent d06174e461
commit 1942f87ed9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve prop binding warning validation for stores

@ -7,7 +7,7 @@ import * as b from '../../../../utils/builders.js';
import { binding_properties } from '../../../bindings.js'; import { binding_properties } from '../../../bindings.js';
import { build_setter } from '../utils.js'; import { build_setter } from '../utils.js';
import { build_attribute_value } from './shared/element.js'; import { build_attribute_value } from './shared/element.js';
import { build_bind_this, build_validate_binding } from './shared/utils.js'; import { build_bind_this, validate_binding } from './shared/utils.js';
/** /**
* @param {BindDirective} node * @param {BindDirective} node
@ -30,12 +30,10 @@ export function BindDirective(node, context) {
)) && )) &&
!is_ignored(node, 'binding_property_non_reactive') !is_ignored(node, 'binding_property_non_reactive')
) { ) {
context.state.init.push( validate_binding(
build_validate_binding( context.state,
context.state, node,
node, /**@type {MemberExpression} */ (context.visit(expression))
/**@type {MemberExpression} */ (context.visit(expression))
)
); );
} }

@ -6,7 +6,7 @@ import { get_attribute_chunks } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js'; import * as b from '../../../../../utils/builders.js';
import { is_element_node } from '../../../../nodes.js'; import { is_element_node } from '../../../../nodes.js';
import { create_derived, build_setter } from '../../utils.js'; import { create_derived, build_setter } from '../../utils.js';
import { build_bind_this, build_validate_binding } from '../shared/utils.js'; import { build_bind_this, validate_binding } from '../shared/utils.js';
import { build_attribute_value } from '../shared/element.js'; import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js'; import { build_event_handler } from './events.js';
@ -151,7 +151,7 @@ export function build_component(node, component_name, context, anchor = context.
context.state.analysis.runes && context.state.analysis.runes &&
!is_ignored(node, 'binding_property_non_reactive') !is_ignored(node, 'binding_property_non_reactive')
) { ) {
context.state.init.push(build_validate_binding(context.state, attribute, expression)); validate_binding(context.state, attribute, expression);
} }
if (attribute.name === 'this') { if (attribute.name === 'this') {

@ -204,28 +204,30 @@ export function build_bind_this(expression, value, { state, visit }) {
* @param {BindDirective} binding * @param {BindDirective} binding
* @param {MemberExpression} expression * @param {MemberExpression} expression
*/ */
export function build_validate_binding(state, binding, expression) { export function validate_binding(state, binding, expression) {
const string = state.analysis.source.slice(binding.start, binding.end); // If we are referencing a $store.foo then we don't need to add validation
const left = object(binding.expression);
const get_object = b.thunk(/** @type {Expression} */ (expression.object)); const left_binding = left && state.scope.get(left.name);
const get_property = b.thunk( if (left_binding?.kind === 'store_sub') return;
/** @type {Expression} */ (
expression.computed
? expression.property
: b.literal(/** @type {Identifier} */ (expression.property).name)
)
);
const loc = locator(binding.start); const loc = locator(binding.start);
return b.stmt( state.init.push(
b.call( b.stmt(
'$.validate_binding', b.call(
b.literal(string), '$.validate_binding',
get_object, b.literal(state.analysis.source.slice(binding.start, binding.end)),
get_property, b.thunk(/** @type {Expression} */ (expression.object)),
loc && b.literal(loc.line), b.thunk(
loc && b.literal(loc.column) /** @type {Expression} */ (
expression.computed
? expression.property
: b.literal(/** @type {Identifier} */ (expression.property).name)
)
),
loc && b.literal(loc.line),
loc && b.literal(loc.column)
)
) )
); );
} }

@ -0,0 +1,5 @@
<script>
let { value = $bindable() } = $props();
</script>
<input type="number" bind:value />

@ -0,0 +1,11 @@
import { test } from '../../test';
export default test({
mode: ['client'],
compileOptions: {
dev: true
},
async test({ warnings, assert }) {
assert.deepEqual(warnings, []);
}
});

@ -0,0 +1,12 @@
<script>
import { writable } from 'svelte/store';
import Child from './Child.svelte';
let a = writable({ value: 0 });
let b = writable({ nested: { value: 0 } });
</script>
<Child bind:value={$a.value} />
<Child bind:value={$b.nested.value} />
<p>{$a.value}</p>
<p>{$b.nested.value}</p>
Loading…
Cancel
Save