add error preventing setting descriptors that are complex

pull/12847/head
Dominic Gannaway 2 years ago
parent 67e20a17cf
commit a27b573a2f

@ -64,6 +64,10 @@
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
## state_descriptors_fixed
> Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
## state_prototype_fixed
> Cannot set prototype of `$state` object

@ -294,6 +294,22 @@ export function state_prototype_fixed() {
}
}
/**
* Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
* @returns {never}
*/
export function state_descriptors_fixed() {
if (DEV) {
const error = new Error(`state_descriptors_fixed\nProperty descriptors defined on \`$state\` objects must contain \`value\` and always be \`enumerable\`, \`configurable\` and \`writable\`.`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("state_descriptors_fixed");
}
}
/**
* Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}

@ -27,7 +27,7 @@ export function proxy(value, parent = null, prev) {
if (typeof value === 'object' && value != null && !is_frozen(value)) {
// If we have an existing proxy, return it...
if (STATE_SYMBOL in value) {
const metadata = /** @type {ProxyMetadata<T>} */ (value[STATE_SYMBOL]);
var metadata = /** @type {ProxyMetadata<T>} */ (value[STATE_SYMBOL]);
// ...unless the proxy belonged to a different object, because
// someone copied the state symbol using `Reflect.ownKeys(...)`
@ -43,10 +43,10 @@ export function proxy(value, parent = null, prev) {
}
}
const prototype = get_prototype_of(value);
var prototype = get_prototype_of(value);
if (prototype === object_prototype || prototype === array_prototype) {
const proxy = new Proxy(value, state_proxy_handler);
var proxy = new Proxy(value, state_proxy_handler);
define_property(value, STATE_SYMBOL, {
value: /** @type {ProxyMetadata} */ ({
@ -68,7 +68,7 @@ export function proxy(value, parent = null, prev) {
// Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context.
// If no previous proxy exists we play it safe and assume ownerless state
// @ts-expect-error
const prev_owners = prev?.v?.[STATE_SYMBOL]?.owners;
var prev_owners = prev?.v?.[STATE_SYMBOL]?.owners;
// @ts-expect-error
value[STATE_SYMBOL].owners = prev_owners ? new Set(prev_owners) : null;
} else {
@ -101,22 +101,35 @@ function update_version(signal, d = 1) {
const state_proxy_handler = {
// TODO needs fixing for non mutations
defineProperty(target, prop, descriptor) {
if (descriptor.value) {
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
if (
!('value' in descriptor) ||
descriptor.configurable === false ||
descriptor.enumerable === false ||
descriptor.writable === false
) {
e.state_descriptors_fixed();
}
const s = metadata.s.get(prop);
if (s !== undefined) set(s, proxy(descriptor.value, metadata));
/** @type {ProxyMetadata} */
var metadata = target[STATE_SYMBOL];
var value = descriptor.value;
var s = metadata.s.get(prop);
if (s === undefined) {
s = source(value);
metadata.s.set(prop, s);
} else {
set(s, proxy(value, metadata));
}
return Reflect.defineProperty(target, prop, descriptor);
return true;
},
deleteProperty(target, prop) {
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
const s = metadata.s.get(prop);
const exists = s !== undefined ? s.v !== UNINITIALIZED : prop in target;
var metadata = target[STATE_SYMBOL];
var s = metadata.s.get(prop);
var exists = s !== undefined ? s.v !== UNINITIALIZED : prop in target;
if (s !== undefined) {
set(s, UNINITIALIZED);
@ -135,8 +148,8 @@ const state_proxy_handler = {
}
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
let s = metadata.s.get(prop);
var metadata = target[STATE_SYMBOL];
var s = metadata.s.get(prop);
var exists = prop in target;
// create a source, but only if it's an own property and not a prototype property
@ -146,7 +159,7 @@ const state_proxy_handler = {
}
if (s !== undefined) {
const value = get(s);
var value = get(s);
return value === UNINITIALIZED ? undefined : value;
}
@ -154,12 +167,12 @@ const state_proxy_handler = {
},
getOwnPropertyDescriptor(target, prop) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
var descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
var metadata = target[STATE_SYMBOL];
if (descriptor && 'value' in descriptor) {
const s = metadata.s.get(prop);
var s = metadata.s.get(prop);
if (s) {
descriptor.value = get(s);
@ -186,9 +199,9 @@ const state_proxy_handler = {
return true;
}
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
let s = metadata.s.get(prop);
const has = (s !== undefined && s.v !== UNINITIALIZED) || Reflect.has(target, prop);
var metadata = target[STATE_SYMBOL];
var s = metadata.s.get(prop);
var has = (s !== undefined && s.v !== UNINITIALIZED) || Reflect.has(target, prop);
if (
s !== undefined ||
@ -198,7 +211,7 @@ const state_proxy_handler = {
s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED);
metadata.s.set(prop, s);
}
const value = get(s);
var value = get(s);
if (value === UNINITIALIZED) {
return false;
}
@ -208,9 +221,9 @@ const state_proxy_handler = {
set(target, prop, value, receiver) {
/** @type {ProxyMetadata} */
const metadata = target[STATE_SYMBOL];
let s = metadata.s.get(prop);
let has = prop in target;
var metadata = target[STATE_SYMBOL];
var s = metadata.s.get(prop);
var has = prop in target;
// If we haven't yet created a source for this property, we need to ensure
// we do so otherwise if we read it later, then the write won't be tracked and
@ -226,11 +239,11 @@ const state_proxy_handler = {
has = s.v !== UNINITIALIZED;
set(s, proxy(value, metadata));
}
const is_array = metadata.a;
var is_array = metadata.a;
if (DEV) {
/** @type {ProxyMetadata | undefined} */
const prop_metadata = value?.[STATE_SYMBOL];
var prop_metadata = value?.[STATE_SYMBOL];
if (prop_metadata && prop_metadata?.parent !== metadata) {
widen_ownership(metadata, prop_metadata);
}
@ -239,9 +252,9 @@ const state_proxy_handler = {
// variable.length = value -> clear all signals with index >= value
if (is_array && prop === 'length') {
for (let i = value; i < target.length; i += 1) {
const s = metadata.s.get(i + '');
if (s !== undefined) set(s, UNINITIALIZED);
for (var i = value; i < target.length; i += 1) {
var other_s = metadata.s.get(i + '');
if (other_s !== undefined) set(other_s, UNINITIALIZED);
}
}

@ -24,7 +24,9 @@ test('preserves getters', () => {
const state = proxy(original);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
state.x;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
state.x;
assert.equal(original.count, 0);
@ -34,21 +36,23 @@ test('preserves getters', () => {
test('defines a property', () => {
const original = {};
const state = proxy(original);
const state = proxy<any>(original);
let value = 0;
Object.defineProperty(state, 'x', {
get: () => value,
set: (v) => (value = v)
value: 1
});
// @ts-ignore
state.x = 1;
// @ts-ignore
assert.equal(state.x, 1);
assert.equal(value, 1);
assert.ok(!('x' in original));
assert.throws(
() =>
Object.defineProperty(state, 'x', {
get: () => value,
set: (v) => (value = v)
}),
/state_descriptors_fixed/
);
});

Loading…
Cancel
Save