Comparing version 5.5.2 to 5.5.3
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "5.5.2", | ||
"version": "5.5.3", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "types": "./types/index.d.ts", |
@@ -1,6 +0,12 @@ | ||
/** @import { AssignmentExpression, AssignmentOperator, Expression, Pattern } from 'estree' */ | ||
/** @import { Location } from 'locate-character' */ | ||
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Literal, MemberExpression, Pattern } from 'estree' */ | ||
/** @import { AST } from '#compiler' */ | ||
/** @import { Context } from '../types.js' */ | ||
import * as b from '../../../../utils/builders.js'; | ||
import { build_assignment_value } from '../../../../utils/ast.js'; | ||
import { is_ignored } from '../../../../state.js'; | ||
import { | ||
build_assignment_value, | ||
get_attribute_expression, | ||
is_event_attribute | ||
} from '../../../../utils/ast.js'; | ||
import { dev, filename, is_ignored, locator } from '../../../../state.js'; | ||
import { build_proxy_reassignment, should_proxy } from '../utils.js'; | ||
@@ -24,3 +30,21 @@ import { visit_assignment_expression } from '../../shared/assignments.js'; | ||
/** | ||
* Determines whether the value will be coerced on assignment (as with e.g. `+=`). | ||
* If not, we may need to proxify the value, or warn that the value will not be | ||
* proxified in time | ||
* @param {AssignmentOperator} operator | ||
*/ | ||
function is_non_coercive_operator(operator) { | ||
return ['=', '||=', '&&=', '??='].includes(operator); | ||
} | ||
/** @type {Record<string, string>} */ | ||
const callees = { | ||
'=': '$.assign', | ||
'&&=': '$.assign_and', | ||
'||=': '$.assign_or', | ||
'??=': '$.assign_nullish' | ||
}; | ||
/** | ||
* @param {AssignmentOperator} operator | ||
* @param {Pattern} left | ||
@@ -45,3 +69,7 @@ * @param {Expression} right | ||
if (private_state.kind !== 'raw_state' && should_proxy(value, context.state.scope)) { | ||
if ( | ||
private_state.kind === 'state' && | ||
is_non_coercive_operator(operator) && | ||
should_proxy(value, context.state.scope) | ||
) { | ||
value = build_proxy_reassignment(value, b.member(b.this, private_state.id)); | ||
@@ -78,4 +106,10 @@ } | ||
const path = context.path.map((node) => node.type); | ||
// reassignment | ||
if (object === left && transform?.assign) { | ||
// special case — if an element binding, we know it's a primitive | ||
const is_primitive = path.at(-1) === 'BindDirective' && path.at(-2) === 'RegularElement'; | ||
let value = /** @type {Expression} */ ( | ||
@@ -85,6 +119,2 @@ context.visit(build_assignment_value(operator, left, right)) | ||
// special case — if an element binding, we know it's a primitive | ||
const path = context.path.map((node) => node.type); | ||
const is_primitive = path.at(-1) === 'BindDirective' && path.at(-2) === 'RegularElement'; | ||
if ( | ||
@@ -94,6 +124,8 @@ !is_primitive && | ||
binding.kind !== 'bindable_prop' && | ||
binding.kind !== 'raw_state' && | ||
context.state.analysis.runes && | ||
should_proxy(value, context.state.scope) | ||
should_proxy(right, context.state.scope) && | ||
is_non_coercive_operator(operator) | ||
) { | ||
value = binding.kind === 'raw_state' ? value : build_proxy_reassignment(value, object); | ||
value = build_proxy_reassignment(value, object); | ||
} | ||
@@ -116,3 +148,55 @@ | ||
// in cases like `(object.items ??= []).push(value)`, we may need to warn | ||
// if the value gets proxified, since the proxy _isn't_ the thing that | ||
// will be pushed to. we do this by transforming it to something like | ||
// `$.assign_nullish(object, 'items', [])` | ||
let should_transform = | ||
dev && path.at(-1) !== 'ExpressionStatement' && is_non_coercive_operator(operator); | ||
// special case — ignore `onclick={() => (...)}` | ||
if ( | ||
path.at(-1) === 'ArrowFunctionExpression' && | ||
(path.at(-2) === 'RegularElement' || path.at(-2) === 'SvelteElement') | ||
) { | ||
const element = /** @type {AST.RegularElement} */ (context.path.at(-2)); | ||
const attribute = element.attributes.find((attribute) => { | ||
if (attribute.type !== 'Attribute' || !is_event_attribute(attribute)) { | ||
return false; | ||
} | ||
const expression = get_attribute_expression(attribute); | ||
return expression === context.path.at(-1); | ||
}); | ||
if (attribute) { | ||
should_transform = false; | ||
} | ||
} | ||
if (left.type === 'MemberExpression' && should_transform) { | ||
const callee = callees[operator]; | ||
const loc = /** @type {Location} */ (locator(/** @type {number} */ (left.start))); | ||
const location = `${filename}:${loc.line}:${loc.column}`; | ||
return /** @type {Expression} */ ( | ||
context.visit( | ||
b.call( | ||
callee, | ||
/** @type {Expression} */ (left.object), | ||
/** @type {Expression} */ ( | ||
left.computed | ||
? left.property | ||
: b.literal(/** @type {Identifier} */ (left.property).name) | ||
), | ||
right, | ||
b.literal(location) | ||
) | ||
) | ||
); | ||
} | ||
return null; | ||
} |
@@ -187,13 +187,18 @@ /** @import { ClassBody, Expression, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition } from 'estree' */ | ||
[b.id('owner')], | ||
Array.from(public_state.keys()).map((name) => | ||
b.stmt( | ||
b.call( | ||
'$.add_owner', | ||
b.call('$.get', b.member(b.this, b.private_id(name))), | ||
b.id('owner'), | ||
b.literal(false), | ||
is_ignored(node, 'ownership_invalid_binding') && b.true | ||
Array.from(public_state) | ||
// Only run ownership addition on $state fields. | ||
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, | ||
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. | ||
.filter(([_, { kind }]) => kind === 'state') | ||
.map(([name]) => | ||
b.stmt( | ||
b.call( | ||
'$.add_owner', | ||
b.call('$.get', b.member(b.this, b.private_id(name))), | ||
b.id('owner'), | ||
b.literal(false), | ||
is_ignored(node, 'ownership_invalid_binding') && b.true | ||
) | ||
) | ||
) | ||
), | ||
), | ||
true | ||
@@ -200,0 +205,0 @@ ) |
@@ -5,3 +5,3 @@ /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Property, Statement } from 'estree' */ | ||
import { dev, is_ignored } from '../../../../../state.js'; | ||
import { get_attribute_chunks } from '../../../../../utils/ast.js'; | ||
import { get_attribute_chunks, object } from '../../../../../utils/ast.js'; | ||
import * as b from '../../../../../utils/builders.js'; | ||
@@ -180,12 +180,22 @@ import { create_derived } from '../../utils.js'; | ||
if (dev) { | ||
binding_initializers.push( | ||
b.stmt( | ||
b.call( | ||
b.id('$.add_owner_effect'), | ||
b.thunk(expression), | ||
b.id(component_name), | ||
is_ignored(node, 'ownership_invalid_binding') && b.true | ||
const left = object(attribute.expression); | ||
let binding; | ||
if (left?.type === 'Identifier') { | ||
binding = context.state.scope.get(left.name); | ||
} | ||
// Only run ownership addition on $state fields. | ||
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, | ||
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. | ||
if (binding?.kind !== 'derived' && binding?.kind !== 'raw_state') { | ||
binding_initializers.push( | ||
b.stmt( | ||
b.call( | ||
b.id('$.add_owner_effect'), | ||
b.thunk(expression), | ||
b.id(component_name), | ||
is_ignored(node, 'ownership_invalid_binding') && b.true | ||
) | ||
) | ||
) | ||
); | ||
); | ||
} | ||
} | ||
@@ -192,0 +202,0 @@ |
export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; | ||
export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js'; | ||
export { cleanup_styles } from './dev/css.js'; | ||
@@ -3,0 +4,0 @@ export { add_locations } from './dev/elements.js'; |
@@ -51,2 +51,5 @@ /** @import { ComponentContext, Derived, Effect, Reaction, Signal, Source, Value } from '#client' */ | ||
/** @type {Effect | null} */ | ||
let last_scheduled_effect = null; | ||
export let is_flushing_effect = false; | ||
@@ -536,9 +539,18 @@ export let is_destroying_effect = false; | ||
function log_effect_stack() { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
'Last ten effects were: ', | ||
dev_effect_stack.slice(-10).map((d) => d.fn) | ||
); | ||
dev_effect_stack = []; | ||
} | ||
function infinite_loop_guard() { | ||
if (flush_count > 1000) { | ||
flush_count = 0; | ||
if (DEV) { | ||
try { | ||
e.effect_update_depth_exceeded(); | ||
} catch (error) { | ||
try { | ||
e.effect_update_depth_exceeded(); | ||
} catch (error) { | ||
if (DEV) { | ||
// stack is garbage, ignore. Instead add a console.error message. | ||
@@ -548,12 +560,23 @@ define_property(error, 'stack', { | ||
}); | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
'Last ten effects were: ', | ||
dev_effect_stack.slice(-10).map((d) => d.fn) | ||
); | ||
dev_effect_stack = []; | ||
} | ||
// Try and handle the error so it can be caught at a boundary, that's | ||
// if there's an effect available from when it was last scheduled | ||
if (last_scheduled_effect !== null) { | ||
if (DEV) { | ||
try { | ||
handle_error(error, last_scheduled_effect, null, null); | ||
} catch (e) { | ||
// Only log the effect stack if the error is re-thrown | ||
log_effect_stack(); | ||
throw e; | ||
} | ||
} else { | ||
handle_error(error, last_scheduled_effect, null, null); | ||
} | ||
} else { | ||
if (DEV) { | ||
log_effect_stack(); | ||
} | ||
throw error; | ||
} | ||
} else { | ||
e.effect_update_depth_exceeded(); | ||
} | ||
@@ -643,4 +666,6 @@ } | ||
flush_queued_root_effects(previous_queued_root_effects); | ||
if (!is_micro_task_queued) { | ||
flush_count = 0; | ||
last_scheduled_effect = null; | ||
if (DEV) { | ||
@@ -664,2 +689,4 @@ dev_effect_stack = []; | ||
last_scheduled_effect = signal; | ||
var effect = signal; | ||
@@ -784,2 +811,3 @@ | ||
flush_count = 0; | ||
last_scheduled_effect = null; | ||
if (DEV) { | ||
@@ -786,0 +814,0 @@ dev_effect_stack = []; |
@@ -9,2 +9,16 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ | ||
/** | ||
* Assignment to `%property%` property (%location%) will evaluate to the right-hand side, not the value of `%property%` following the assignment. This may result in unexpected behaviour. | ||
* @param {string} property | ||
* @param {string} location | ||
*/ | ||
export function assignment_value_stale(property, location) { | ||
if (DEV) { | ||
console.warn(`%c[svelte] assignment_value_stale\n%cAssignment to \`${property}\` property (${location}) will evaluate to the right-hand side, not the value of \`${property}\` following the assignment. This may result in unexpected behaviour.`, bold, normal); | ||
} else { | ||
// TODO print a link to the documentation | ||
console.warn("assignment_value_stale"); | ||
} | ||
} | ||
/** | ||
* `%binding%` (%location%) is binding to a non-reactive property | ||
@@ -11,0 +25,0 @@ * @param {string} binding |
@@ -9,3 +9,3 @@ // generated during release, do not modify | ||
*/ | ||
export const VERSION = '5.5.2'; | ||
export const VERSION = '5.5.3'; | ||
export const PUBLIC_VERSION = '5'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2358121
374
52105