make it a dev-time validation error that also deals with ...rest props

props-bindable
Simon Holthausen 7 months ago
parent ed670ebdd2
commit 84e2dd3130

@ -239,7 +239,7 @@ export function client_component(source, analysis, options) {
);
});
const properties = analysis.exports.map(({ name, alias }) => {
const component_returned_object = analysis.exports.map(({ name, alias }) => {
const expression = serialize_get_binding(b.id(name), instance_state);
if (expression.type === 'Identifier' && !options.dev) {
@ -249,11 +249,26 @@ export function client_component(source, analysis, options) {
return b.get(alias ?? name, [b.return(expression)]);
});
if (analysis.accessors) {
for (const [name, binding] of analysis.instance.scope.declarations) {
if ((binding.kind !== 'prop' && binding.kind !== 'bindable_prop') || name.startsWith('$$'))
continue;
const properties = [...analysis.instance.scope.declarations].filter(
([name, binding]) =>
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && !name.startsWith('$$')
);
if (analysis.runes && options.dev) {
/** @type {import('estree').Literal[]} */
const bindable = [];
for (const [name, binding] of properties) {
if (binding.kind === 'bindable_prop') {
bindable.push(b.literal(binding.prop_alias ?? name));
}
}
instance.body.unshift(
b.stmt(b.call('$.validate_prop_bindings', b.id('$$props'), b.array(bindable)))
);
}
if (analysis.accessors) {
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;
if (
binding.kind === 'prop' &&
@ -265,7 +280,7 @@ export function client_component(source, analysis, options) {
continue;
}
properties.push(
component_returned_object.push(
b.get(key, [b.return(b.call(b.id(name)))]),
b.set(key, [b.stmt(b.call(b.id(name), b.id('$$value'))), b.stmt(b.call('$.flushSync'))])
);
@ -273,7 +288,7 @@ export function client_component(source, analysis, options) {
}
if (options.legacy.componentApi) {
properties.push(
component_returned_object.push(
b.init('$set', b.id('$.update_legacy_props')),
b.init(
'$on',
@ -289,7 +304,7 @@ export function client_component(source, analysis, options) {
)
);
} else if (options.dev) {
properties.push(
component_returned_object.push(
b.init(
'$set',
b.thunk(
@ -357,8 +372,8 @@ export function client_component(source, analysis, options) {
append_styles();
component_block.body.push(
properties.length > 0
? b.return(b.call('$.pop', b.object(properties)))
component_returned_object.length > 0
? b.return(b.call('$.pop', b.object(component_returned_object)))
: b.stmt(b.call('$.pop'))
);

@ -5,8 +5,7 @@ import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES,
PROPS_IS_UPDATED,
PROPS_IS_BINDABLE
PROPS_IS_UPDATED
} from '../../../../constants.js';
/**
@ -640,19 +639,6 @@ export function get_prop_source(binding, state, name, initial) {
flags |= PROPS_IS_RUNES;
}
if (
binding.kind === 'bindable_prop' ||
// Make sure that
// let { foo: _, ...rest } = $props();
// let { foo } = $props.bindable();
// marks both `foo` and `_` as bindable to prevent false-positive runtime validation errors
[...state.scope.declarations.values()].some(
(d) => d.kind === 'bindable_prop' && d.prop_alias === name
)
) {
flags |= PROPS_IS_BINDABLE;
}
if (
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated)

@ -11,7 +11,6 @@ export const PROPS_IS_IMMUTABLE = 1;
export const PROPS_IS_RUNES = 1 << 1;
export const PROPS_IS_UPDATED = 1 << 2;
export const PROPS_IS_LAZY_INITIAL = 1 << 3;
export const PROPS_IS_BINDABLE = 1 << 4;
/** List of Element events that will be delegated */
export const DelegatedEvents = [

@ -1,6 +1,5 @@
import { DEV } from 'esm-env';
import {
PROPS_IS_BINDABLE,
PROPS_IS_IMMUTABLE,
PROPS_IS_LAZY_INITIAL,
PROPS_IS_RUNES,
@ -143,15 +142,6 @@ export function prop(props, key, flags, initial) {
var prop_value = /** @type {V} */ (props[key]);
var setter = get_descriptor(props, key)?.set;
if ((flags & PROPS_IS_BINDABLE) === 0 && setter) {
throw new Error(
'ERR_SVELTE_NOT_BINDABLE' +
(DEV
? `: Cannot bind:${key} because the property was not declared as bindable. To mark a property as bindable, use let \`{ ${key} } = $props.bindable()\` within the component.`
: '')
);
}
if (prop_value === undefined && initial !== undefined) {
if (setter && runes) {
// TODO consolidate all these random runtime errors

@ -1,5 +1,5 @@
import { untrack } from './runtime.js';
import { is_array } from './utils.js';
import { get_descriptor, is_array } from './utils.js';
/** regex of all html void element names */
const void_element_names =
@ -137,3 +137,22 @@ export function validate_component(component_fn) {
}
return component_fn;
}
/**
* @param {Record<string, any>} $$props
* @param {string[]} bindable
*/
export function validate_prop_bindings($$props, bindable) {
for (const key in $$props) {
if (!bindable.includes(key)) {
var setter = get_descriptor($$props, key)?.set;
if (setter) {
throw new Error(
`Cannot use bind:${key} on this component because the property was not declared as bindable. ` +
`To mark a property as bindable, use let \`{ ${key} } = $props.bindable()\` within the component.`
);
}
}
}
}

@ -0,0 +1,5 @@
<script>
let { ...rest } = $props();
</script>
{rest.count}

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
error:
'Cannot use bind:count on this component because the property was not declared as bindable. To mark a property as bindable, use let `{ count } = $props.bindable()` within the component.',
html: `0`
});

@ -0,0 +1,7 @@
<script>
import Counter from './Counter.svelte';
let count = $state(0);
</script>
<Counter bind:count />

@ -1,7 +1,10 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
error:
'ERR_SVELTE_NOT_BINDABLE: Cannot bind:count because the property was not declared as bindable. To mark a property as bindable, use let `{ count } = $props.bindable()` within the component.',
'Cannot use bind:count on this component because the property was not declared as bindable. To mark a property as bindable, use let `{ count } = $props.bindable()` within the component.',
html: `0`
});

Loading…
Cancel
Save