svelte
Advanced tools
Comparing version 5.0.0-next.105 to 5.0.0-next.106
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "5.0.0-next.105", | ||
"version": "5.0.0-next.106", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "types": "./types/index.d.ts", |
@@ -47,2 +47,3 @@ import { getLocator } from 'locate-character'; | ||
const result = transform_component(analysis, source, combined_options); | ||
result.ast = to_public_ast(source, parsed, options.modernAst); | ||
return result; | ||
@@ -125,3 +126,12 @@ } catch (e) { | ||
if (options.modern) { | ||
return to_public_ast(source, ast, options.modern); | ||
} | ||
/** | ||
* @param {string} source | ||
* @param {import('#compiler').Root} ast | ||
* @param {boolean | undefined} modern | ||
*/ | ||
function to_public_ast(source, ast, modern) { | ||
if (modern) { | ||
// remove things that we don't want to treat as public API | ||
@@ -128,0 +138,0 @@ return walk(ast, null, { |
@@ -144,7 +144,7 @@ import { walk } from 'zimmerframe'; | ||
if (node.pending) { | ||
const first = /** @type {import('#compiler').BaseNode} */ (node.pending.nodes.at(0)); | ||
const last = /** @type {import('#compiler').BaseNode} */ (node.pending.nodes.at(-1)); | ||
const first = node.pending.nodes.at(0); | ||
const last = node.pending.nodes.at(-1); | ||
pendingblock.start = first.start; | ||
pendingblock.end = last.end; | ||
pendingblock.start = first?.start ?? source.indexOf('}', node.expression.end) + 1; | ||
pendingblock.end = last?.end ?? pendingblock.start; | ||
pendingblock.skip = false; | ||
@@ -154,7 +154,9 @@ } | ||
if (node.then) { | ||
const first = /** @type {import('#compiler').BaseNode} */ (node.then.nodes.at(0)); | ||
const last = /** @type {import('#compiler').BaseNode} */ (node.then.nodes.at(-1)); | ||
const first = node.then.nodes.at(0); | ||
const last = node.then.nodes.at(-1); | ||
thenblock.start = pendingblock.end ?? first.start; | ||
thenblock.end = last.end; | ||
thenblock.start = | ||
pendingblock.end ?? first?.start ?? source.indexOf('}', node.expression.end) + 1; | ||
thenblock.end = | ||
last?.end ?? source.lastIndexOf('}', pendingblock.end ?? node.expression.end) + 1; | ||
thenblock.skip = false; | ||
@@ -164,7 +166,13 @@ } | ||
if (node.catch) { | ||
const first = /** @type {import('#compiler').BaseNode} */ (node.catch.nodes.at(0)); | ||
const last = /** @type {import('#compiler').BaseNode} */ (node.catch.nodes.at(-1)); | ||
const first = node.catch.nodes.at(0); | ||
const last = node.catch.nodes.at(-1); | ||
catchblock.start = thenblock.end ?? pendingblock.end ?? first.start; | ||
catchblock.end = last.end; | ||
catchblock.start = | ||
thenblock.end ?? | ||
pendingblock.end ?? | ||
first?.start ?? | ||
source.indexOf('}', node.expression.end) + 1; | ||
catchblock.end = | ||
last?.end ?? | ||
source.lastIndexOf('}', thenblock.end ?? pendingblock.end ?? node.expression.end) + 1; | ||
catchblock.skip = false; | ||
@@ -171,0 +179,0 @@ } |
@@ -568,3 +568,3 @@ import read_pattern from '../read/context.js'; | ||
declarations: [{ type: 'VariableDeclarator', id, init }], | ||
start: start + 1, | ||
start: start + 2, // start at const, not at @const | ||
end: parser.index - 1 | ||
@@ -571,0 +571,0 @@ } |
@@ -171,2 +171,23 @@ import { get_rune } from '../../../scope.js'; | ||
if (state.options.dev && public_state.size > 0) { | ||
// add an `[$.ADD_OWNER]` method so that a class with state fields can widen ownership | ||
body.push( | ||
b.method( | ||
'method', | ||
b.id('$.ADD_OWNER'), | ||
[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') | ||
) | ||
) | ||
), | ||
true | ||
) | ||
); | ||
} | ||
return { ...node, body }; | ||
@@ -173,0 +194,0 @@ }, |
@@ -23,3 +23,4 @@ import { print } from 'esrap'; | ||
runes: analysis.runes | ||
} | ||
}, | ||
ast: /** @type {any} */ (null) // set afterwards | ||
}; | ||
@@ -66,3 +67,4 @@ } | ||
runes: analysis.runes | ||
} | ||
}, | ||
ast: /** @type {any} */ (null) // set afterwards | ||
}; | ||
@@ -85,3 +87,4 @@ } | ||
runes: true | ||
} | ||
}, | ||
ast: /** @type {any} */ (null) // set afterwards | ||
}; | ||
@@ -111,3 +114,4 @@ } | ||
runes: true | ||
} | ||
}, | ||
ast: /** @type {any} */ (null) // set afterwards | ||
}; | ||
@@ -114,0 +118,0 @@ } |
@@ -49,2 +49,4 @@ import type { | ||
}; | ||
/** The AST */ | ||
ast: any; | ||
} | ||
@@ -188,2 +190,9 @@ | ||
hmr?: boolean; | ||
/** | ||
* If `true`, returns the modern version of the AST. | ||
* Will become `true` by default in Svelte 6, and the option will be removed in Svelte 7. | ||
* | ||
* @default false | ||
*/ | ||
modernAst?: boolean; | ||
} | ||
@@ -190,0 +199,0 @@ |
@@ -13,5 +13,7 @@ export const DERIVED = 1 << 1; | ||
export const DESTROYED = 1 << 12; | ||
export const IS_ELSEIF = 1 << 13; | ||
export const EFFECT_RAN = 1 << 14; | ||
export const EFFECT_RAN = 1 << 13; | ||
/** 'Transparent' effects do not create a transition boundary */ | ||
export const EFFECT_TRANSPARENT = 1 << 14; | ||
export const STATE_SYMBOL = Symbol('$state'); |
/** @typedef {{ file: string, line: number, column: number }} Location */ | ||
import { STATE_SYMBOL } from '../constants.js'; | ||
import { untrack } from '../runtime.js'; | ||
import { render_effect } from '../reactivity/effects.js'; | ||
import { current_component_context, untrack } from '../runtime.js'; | ||
import { get_prototype_of } from '../utils.js'; | ||
@@ -66,2 +68,4 @@ /** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */ | ||
export const ADD_OWNER = Symbol('ADD_OWNER'); | ||
/** | ||
@@ -102,23 +106,46 @@ * Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file, | ||
/** | ||
* | ||
* @param {any} object | ||
* @param {any} owner | ||
* @param {boolean} [global] | ||
*/ | ||
export function add_owner(object, owner) { | ||
untrack(() => { | ||
add_owner_to_object(object, owner); | ||
}); | ||
export function add_owner(object, owner, global = false) { | ||
if (object && !global) { | ||
// @ts-expect-error | ||
const component = current_component_context.function; | ||
const metadata = object[STATE_SYMBOL]; | ||
if (metadata && !has_owner(metadata, component)) { | ||
let original = get_owner(metadata); | ||
if (owner.filename !== component.filename) { | ||
let message = `${component.filename} passed a value to ${owner.filename} with \`bind:\`, but the value is owned by ${original.filename}. Consider creating a binding between ${original.filename} and ${component.filename}`; | ||
// eslint-disable-next-line no-console | ||
console.warn(message); | ||
} | ||
} | ||
} | ||
add_owner_to_object(object, owner, new Set()); | ||
} | ||
/** | ||
* @param {any} object | ||
* @param {Function} owner | ||
* @param {import('#client').ProxyMetadata | null} from | ||
* @param {import('#client').ProxyMetadata} to | ||
*/ | ||
function add_owner_to_object(object, owner) { | ||
if (object?.[STATE_SYMBOL]?.o && !object[STATE_SYMBOL].o.has(owner)) { | ||
object[STATE_SYMBOL].o.add(owner); | ||
export function widen_ownership(from, to) { | ||
if (to.owners === null) { | ||
return; | ||
} | ||
for (const key in object) { | ||
add_owner_to_object(object[key], owner); | ||
while (from) { | ||
if (from.owners === null) { | ||
to.owners = null; | ||
break; | ||
} | ||
for (const owner of from.owners) { | ||
to.owners.add(owner); | ||
} | ||
from = from.parent; | ||
} | ||
@@ -129,30 +156,77 @@ } | ||
* @param {any} object | ||
* @param {Function} owner | ||
* @param {Set<any>} seen | ||
*/ | ||
export function strip_owner(object) { | ||
untrack(() => { | ||
strip_owner_from_object(object); | ||
}); | ||
function add_owner_to_object(object, owner, seen) { | ||
const metadata = /** @type {import('#client').ProxyMetadata} */ (object?.[STATE_SYMBOL]); | ||
if (metadata) { | ||
// this is a state proxy, add owner directly, if not globally shared | ||
if (metadata.owners !== null) { | ||
metadata.owners.add(owner); | ||
} | ||
} else if (object && typeof object === 'object') { | ||
if (seen.has(object)) return; | ||
seen.add(object); | ||
if (object[ADD_OWNER]) { | ||
// this is a class with state fields. we put this in a render effect | ||
// so that if state is replaced (e.g. `instance.name = { first, last }`) | ||
// the new state is also co-owned by the caller of `getContext` | ||
render_effect(() => { | ||
object[ADD_OWNER](owner); | ||
}); | ||
} else { | ||
var proto = get_prototype_of(object); | ||
if (proto === Object.prototype) { | ||
// recurse until we find a state proxy | ||
for (const key in object) { | ||
add_owner_to_object(object[key], owner, seen); | ||
} | ||
} else if (proto === Array.prototype) { | ||
// recurse until we find a state proxy | ||
for (let i = 0; i < object.length; i += 1) { | ||
add_owner_to_object(object[i], owner, seen); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @param {any} object | ||
* @param {import('#client').ProxyMetadata} metadata | ||
* @param {Function} component | ||
* @returns {boolean} | ||
*/ | ||
function strip_owner_from_object(object) { | ||
if (object?.[STATE_SYMBOL]?.o) { | ||
object[STATE_SYMBOL].o = null; | ||
function has_owner(metadata, component) { | ||
if (metadata.owners === null) { | ||
return true; | ||
} | ||
for (const key in object) { | ||
strip_owner(object[key]); | ||
} | ||
} | ||
return ( | ||
metadata.owners.has(component) || | ||
(metadata.parent !== null && has_owner(metadata.parent, component)) | ||
); | ||
} | ||
/** | ||
* @param {Set<Function>} owners | ||
* @param {import('#client').ProxyMetadata} metadata | ||
* @returns {any} | ||
*/ | ||
export function check_ownership(owners) { | ||
function get_owner(metadata) { | ||
return ( | ||
metadata?.owners?.values().next().value ?? | ||
get_owner(/** @type {import('#client').ProxyMetadata} */ (metadata.parent)) | ||
); | ||
} | ||
/** | ||
* @param {import('#client').ProxyMetadata} metadata | ||
*/ | ||
export function check_ownership(metadata) { | ||
const component = get_component(); | ||
if (component && !owners.has(component)) { | ||
let original = [...owners][0]; | ||
if (component && !has_owner(metadata, component)) { | ||
let original = get_owner(metadata); | ||
@@ -159,0 +233,0 @@ let message = |
@@ -1,2 +0,2 @@ | ||
import { IS_ELSEIF } from '../../constants.js'; | ||
import { EFFECT_TRANSPARENT } from '../../constants.js'; | ||
import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; | ||
@@ -82,4 +82,4 @@ import { remove } from '../reconciler.js'; | ||
if (elseif) { | ||
effect.f |= IS_ELSEIF; | ||
effect.f |= EFFECT_TRANSPARENT; | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { EFFECT_TRANSPARENT } from '../../constants.js'; | ||
import { branch, render_effect } from '../../reactivity/effects.js'; | ||
@@ -14,3 +15,3 @@ | ||
render_effect(() => { | ||
var effect = render_effect(() => { | ||
if (snippet === (snippet = get_snippet())) return; | ||
@@ -22,2 +23,4 @@ | ||
}); | ||
effect.f |= EFFECT_TRANSPARENT; | ||
} |
@@ -10,3 +10,3 @@ import { noop } from '../../../shared/utils.js'; | ||
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; | ||
import { EFFECT_RAN } from '../../constants.js'; | ||
import { BLOCK_EFFECT, EFFECT_RAN } from '../../constants.js'; | ||
@@ -216,2 +216,7 @@ /** | ||
// e.g snippets are implemented as render effects — keep going until we find the parent block | ||
while ((parent.f & BLOCK_EFFECT) === 0 && parent.parent) { | ||
parent = parent.parent; | ||
} | ||
if (is_global || (parent.f & EFFECT_RAN) !== 0) { | ||
@@ -218,0 +223,0 @@ effect(() => { |
export { hmr } from './dev/hmr.js'; | ||
export { add_owner, mark_module_start, mark_module_end } from './dev/ownership.js'; | ||
export { ADD_OWNER, add_owner, mark_module_start, mark_module_end } from './dev/ownership.js'; | ||
export { await_block as await } from './dom/blocks/await.js'; | ||
@@ -140,3 +140,3 @@ export { if_block as if } from './dom/blocks/if.js'; | ||
} from './dom/operations.js'; | ||
export { noop, call_once } from '../shared/utils.js'; | ||
export { noop } from '../shared/utils.js'; | ||
export { | ||
@@ -143,0 +143,0 @@ add_snippet_symbol, |
@@ -19,3 +19,3 @@ import { DEV } from 'esm-env'; | ||
} from './utils.js'; | ||
import { add_owner, check_ownership, strip_owner } from './dev/ownership.js'; | ||
import { check_ownership, widen_ownership } from './dev/ownership.js'; | ||
import { mutable_source, source, set } from './reactivity/sources.js'; | ||
@@ -29,6 +29,6 @@ import { STATE_SYMBOL } from './constants.js'; | ||
* @param {boolean} [immutable] | ||
* @param {Set<Function> | null} [owners] | ||
* @param {import('#client').ProxyMetadata | null} [parent] | ||
* @returns {import('#client').ProxyStateObject<T> | T} | ||
*/ | ||
export function proxy(value, immutable = true, owners) { | ||
export function proxy(value, immutable = true, parent = null) { | ||
if (typeof value === 'object' && value != null && !is_frozen(value)) { | ||
@@ -38,2 +38,3 @@ // If we have an existing proxy, return it... | ||
const metadata = /** @type {import('#client').ProxyMetadata<T>} */ (value[STATE_SYMBOL]); | ||
// ...unless the proxy belonged to a different object, because | ||
@@ -43,10 +44,3 @@ // someone copied the state symbol using `Reflect.ownKeys(...)` | ||
if (DEV) { | ||
// update ownership | ||
if (owners) { | ||
for (const owner of owners) { | ||
add_owner(value, owner); | ||
} | ||
} else { | ||
strip_owner(value); | ||
} | ||
metadata.parent = parent; | ||
} | ||
@@ -77,12 +71,13 @@ | ||
if (DEV) { | ||
// set ownership — either of the parent proxy's owners (if provided) or, | ||
// when calling `$.proxy(...)`, to the current component if such there be | ||
// @ts-expect-error | ||
value[STATE_SYMBOL].o = | ||
owners === undefined | ||
? current_component_context | ||
value[STATE_SYMBOL].parent = parent; | ||
// @ts-expect-error | ||
value[STATE_SYMBOL].owners = | ||
parent === null | ||
? current_component_context !== null | ||
? // @ts-expect-error | ||
new Set([current_component_context.function]) | ||
: null | ||
: owners && new Set(owners); | ||
: new Set(); | ||
} | ||
@@ -170,3 +165,3 @@ | ||
const s = metadata.s.get(prop); | ||
if (s !== undefined) set(s, proxy(descriptor.value, metadata.i, metadata.o)); | ||
if (s !== undefined) set(s, proxy(descriptor.value, metadata.i, metadata)); | ||
} | ||
@@ -217,3 +212,3 @@ | ||
if (s === undefined && (!(prop in target) || get_descriptor(target, prop)?.writable)) { | ||
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i, metadata.o)); | ||
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i, metadata)); | ||
metadata.s.set(prop, s); | ||
@@ -265,3 +260,3 @@ } | ||
s = (metadata.i ? source : mutable_source)( | ||
has ? proxy(target[prop], metadata.i, metadata.o) : UNINITIALIZED | ||
has ? proxy(target[prop], metadata.i, metadata) : UNINITIALIZED | ||
); | ||
@@ -292,3 +287,3 @@ metadata.s.set(prop, s); | ||
if (s !== undefined) { | ||
set(s, proxy(value, metadata.i, metadata.o)); | ||
set(s, proxy(value, metadata.i, metadata)); | ||
} | ||
@@ -299,13 +294,8 @@ const is_array = metadata.a; | ||
if (DEV) { | ||
// First check ownership of the object that is assigned to. | ||
// Then, if the new object has owners, widen them with the ones from the current object. | ||
// If it doesn't have owners that means it's ownerless, and so the assigned object should be, too. | ||
if (metadata.o) { | ||
check_ownership(metadata.o); | ||
for (const owner in metadata.o) { | ||
add_owner(value, owner); | ||
} | ||
} else { | ||
strip_owner(value); | ||
/** @type {import('#client').ProxyMetadata | undefined} */ | ||
const prop_metadata = value?.[STATE_SYMBOL]; | ||
if (prop_metadata && prop_metadata?.parent !== metadata) { | ||
widen_ownership(metadata, prop_metadata); | ||
} | ||
check_ownership(metadata); | ||
} | ||
@@ -312,0 +302,0 @@ |
@@ -27,3 +27,3 @@ import { DEV } from 'esm-env'; | ||
ROOT_EFFECT, | ||
IS_ELSEIF | ||
EFFECT_TRANSPARENT | ||
} from '../constants.js'; | ||
@@ -349,3 +349,3 @@ import { set } from './sources.js'; | ||
var sibling = child.next; | ||
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0; | ||
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0; | ||
// TODO we don't need to call pause_children recursively with a linked list in place | ||
@@ -386,3 +386,3 @@ // it's slightly more involved though as we have to account for `transparent` changing | ||
var sibling = child.next; | ||
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0; | ||
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0; | ||
// TODO we don't need to call resume_children recursively with a linked list in place | ||
@@ -389,0 +389,0 @@ // it's slightly more involved though as we have to account for `transparent` changing |
@@ -897,3 +897,3 @@ import { DEV } from 'esm-env'; | ||
if (fn) { | ||
add_owner(result, fn); | ||
add_owner(result, fn, true); | ||
} | ||
@@ -954,3 +954,3 @@ } | ||
for (const value of context_map.values()) { | ||
add_owner(value, fn); | ||
add_owner(value, fn, true); | ||
} | ||
@@ -1157,3 +1157,3 @@ } | ||
} | ||
const proto = Object.getPrototypeOf(value); | ||
const proto = get_prototype_of(value); | ||
if ( | ||
@@ -1160,0 +1160,0 @@ proto !== Object.prototype && |
@@ -168,4 +168,6 @@ import type { Store } from '#shared'; | ||
t: T; | ||
/** Dev-only — the components that 'own' this state, if any */ | ||
o: null | Set<Function>; | ||
/** Dev-only — the components that 'own' this state, if any. `null` means no owners, i.e. everyone can mutate this state. */ | ||
owners: null | Set<Function>; | ||
/** Dev-only — the parent metadata object */ | ||
parent: null | ProxyMetadata; | ||
} | ||
@@ -172,0 +174,0 @@ |
@@ -26,17 +26,1 @@ export const noop = () => {}; | ||
} | ||
/** | ||
* @param {Function} fn | ||
*/ | ||
export function call_once(fn) { | ||
let called = false; | ||
/** @type {unknown} */ | ||
let result; | ||
return function () { | ||
if (!called) { | ||
called = true; | ||
result = fn(); | ||
} | ||
return result; | ||
}; | ||
} |
@@ -9,3 +9,3 @@ // generated during release, do not modify | ||
*/ | ||
export const VERSION = '5.0.0-next.105'; | ||
export const VERSION = '5.0.0-next.106'; | ||
export const PUBLIC_VERSION = '5'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1940191
41810