Merge branch 'main' into unstate

pull/9776/head
Rich Harris 3 years ago committed by GitHub
commit 183b1fb87f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: fix compiler errors test suite

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure transitions properly cancel on completion

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: apply event attribute validation to elements only

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle css nth-selector syntax

@ -24,9 +24,11 @@ const internal = {
const parse = { const parse = {
/** @param {string} name */ /** @param {string} name */
'unclosed-element': (name) => `<${name}> was left open`, 'unclosed-element': (name) => `<${name}> was left open`,
'unclosed-block': () => `block was left open`, 'unclosed-block': () => `Block was left open`,
'unexpected-block-close': () => `Unexpected block closing tag`, 'unexpected-block-close': () => `Unexpected block closing tag`,
'unexpected-eof': () => `Unexpected end of input`, /** @param {string} [expected] */
'unexpected-eof': (expected) =>
`Unexpected end of input` + (expected ? ` (expected ${expected})` : ''),
/** @param {string} message */ /** @param {string} message */
'js-parse-error': (message) => message, 'js-parse-error': (message) => message,
/** @param {string} token */ /** @param {string} token */
@ -39,17 +41,15 @@ const parse = {
'invalid-script-context': () => 'invalid-script-context': () =>
`If the context attribute is supplied, its value must be "module"`, `If the context attribute is supplied, its value must be "module"`,
'invalid-elseif': () => `'elseif' should be 'else if'`, 'invalid-elseif': () => `'elseif' should be 'else if'`,
/** 'invalid-continuing-block-placement': () =>
* @param {string} child `{:...} block is invalid at this position (did you forget to close the preceeding element or block?)`,
* @param {string} parent
*/
'invalid-block-parent': (child, parent) =>
`Expected to close ${parent} before seeing ${child} block`,
/** /**
* @param {string} child * @param {string} child
* @param {string} parent * @param {string} parent
*/ */
'invalid-block-missing-parent': (child, parent) => `${child} block must be a child of ${parent}`, 'invalid-block-missing-parent': (child, parent) => `${child} block must be a child of ${parent}`,
/** @param {string} name */
'duplicate-block-part': (name) => `${name} cannot appear more than once within a block`,
'expected-block-type': () => `Expected 'if', 'each', 'await', 'key' or 'snippet'`, 'expected-block-type': () => `Expected 'if', 'each', 'await', 'key' or 'snippet'`,
'expected-identifier': () => `Expected an identifier`, 'expected-identifier': () => `Expected an identifier`,
'invalid-debug': () => `{@debug ...} arguments must be identifiers, not arbitrary expressions`, 'invalid-debug': () => `{@debug ...} arguments must be identifiers, not arbitrary expressions`,
@ -98,12 +98,9 @@ const css = {
'invalid-css-empty-declaration': () => `Declaration cannot be empty`, 'invalid-css-empty-declaration': () => `Declaration cannot be empty`,
'invalid-css-global-placement': () => 'invalid-css-global-placement': () =>
`:global(...) can be at the start or end of a selector sequence, but not in the middle`, `:global(...) can be at the start or end of a selector sequence, but not in the middle`,
'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`, 'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`,
'invalid-css-global-selector-list': () => 'invalid-css-global-selector-list': () =>
`:global(...) cannot be used to modify a selector, or be modified by another selector`, `:global(...) cannot be used to modify a selector, or be modified by another selector`,
'invalid-css-selector': () => `Invalid selector`, 'invalid-css-selector': () => `Invalid selector`,
'invalid-css-identifier': () => 'Expected a valid CSS identifier' 'invalid-css-identifier': () => 'Expected a valid CSS identifier'
}; };

@ -77,8 +77,10 @@ export class Parser {
const current = this.current(); const current = this.current();
if (current.type === 'RegularElement') { if (current.type === 'RegularElement') {
current.end = current.start + 1;
error(current, 'unclosed-element', current.name); error(current, 'unclosed-element', current.name);
} else { } else {
current.end = current.start + 1;
error(current, 'unclosed-block'); error(current, 'unclosed-block');
} }
} }
@ -145,7 +147,7 @@ export class Parser {
if (required) { if (required) {
if (this.index === this.template.length) { if (this.index === this.template.length) {
error(this.index, 'unexpected-eof'); error(this.index, 'unexpected-eof', str);
} else { } else {
error(this.index, 'expected-token', str); error(this.index, 'expected-token', str);
} }

@ -6,6 +6,7 @@ const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today,
const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/; const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/;
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/; const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/; const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
const REGEX_NTH_OF = /^(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s+of\s+)?/;
const REGEX_WHITESPACE_OR_COLON = /[\s:]/; const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_BRACE_OR_SEMICOLON = /[{;]/; const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/; const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
@ -234,6 +235,8 @@ function read_selector(parser, inside_pseudo_class = false) {
if (parser.eat('(')) { if (parser.eat('(')) {
args = read_selector_list(parser, true); args = read_selector_list(parser, true);
parser.eat(')', true); parser.eat(')', true);
} else if (name === 'global') {
error(parser.index, 'invalid-css-global-selector');
} }
children.push({ children.push({
@ -291,6 +294,13 @@ function read_selector(parser, inside_pseudo_class = false) {
start, start,
end: parser.index end: parser.index
}); });
} else if (parser.match_regex(REGEX_NTH_OF)) {
children.push({
type: 'Nth',
value: /** @type {string} */ (parser.read(REGEX_NTH_OF)),
start,
end: parser.index
});
} else { } else {
let name = read_identifier(parser); let name = read_identifier(parser);
if (parser.match('|')) { if (parser.match('|')) {

@ -202,11 +202,13 @@ export default function tag(parser) {
let attribute; let attribute;
while ((attribute = read(parser))) { while ((attribute = read(parser))) {
if ( if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
(attribute.type === 'Attribute' || attribute.type === 'BindDirective') && if (unique_names.includes(attribute.name)) {
unique_names.includes(attribute.name)
) {
error(attribute.start, 'duplicate-attribute'); error(attribute.start, 'duplicate-attribute');
// <svelte:element bind:this this=..> is allowed
} else if (attribute.name !== 'this') {
unique_names.push(attribute.name);
}
} }
element.attributes.push(attribute); element.attributes.push(attribute);
@ -635,13 +637,14 @@ function read_attribute_value(parser) {
'in attribute value' 'in attribute value'
); );
} catch (/** @type {any} e */ e) { } catch (/** @type {any} e */ e) {
if (e.code === 'parse-error') { if (e.code === 'js-parse-error') {
// if the attribute value didn't close + self-closing tag // if the attribute value didn't close + self-closing tag
// eg: `<Component test={{a:1} />` // eg: `<Component test={{a:1} />`
// acorn may throw a `Unterminated regular expression` because of `/>` // acorn may throw a `Unterminated regular expression` because of `/>`
if (parser.template.slice(e.pos - 1, e.pos + 1) === '/>') { const pos = e.position?.[0];
parser.index = e.pos; if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
error(e.pos, 'unclosed-attribute-value', quote_mark || '}'); parser.index = pos;
error(pos, 'unclosed-attribute-value', quote_mark || '}');
} }
} }
throw e; throw e;

@ -315,7 +315,7 @@ function next(parser) {
const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad
if (block.type === 'IfBlock') { if (block.type === 'IfBlock') {
if (!parser.eat('else')) error(start, 'expected-token', 'else'); if (!parser.eat('else')) error(start, 'expected-token', '{:else} or {:else if}');
if (parser.eat('if')) error(start, 'invalid-elseif'); if (parser.eat('if')) error(start, 'invalid-elseif');
parser.allow_whitespace(); parser.allow_whitespace();
@ -359,7 +359,7 @@ function next(parser) {
} }
if (block.type === 'EachBlock') { if (block.type === 'EachBlock') {
if (!parser.eat('else')) error(start, 'expected-token', 'else'); if (!parser.eat('else')) error(start, 'expected-token', '{:else}');
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
@ -375,7 +375,7 @@ function next(parser) {
if (block.type === 'AwaitBlock') { if (block.type === 'AwaitBlock') {
if (parser.eat('then')) { if (parser.eat('then')) {
if (block.then) { if (block.then) {
error(start, 'TODO', 'duplicate then'); error(start, 'duplicate-block-part', '{:then}');
} }
if (!parser.eat('}')) { if (!parser.eat('}')) {
@ -394,7 +394,7 @@ function next(parser) {
if (parser.eat('catch')) { if (parser.eat('catch')) {
if (block.catch) { if (block.catch) {
error(start, 'TODO', 'duplicate catch'); error(start, 'duplicate-block-part', '{:catch}');
} }
if (!parser.eat('}')) { if (!parser.eat('}')) {
@ -413,6 +413,8 @@ function next(parser) {
error(start, 'expected-token', '{:then ...} or {:catch ...}'); error(start, 'expected-token', '{:then ...} or {:catch ...}');
} }
error(start, 'invalid-continuing-block-placement');
} }
/** @param {import('../index.js').Parser} parser */ /** @param {import('../index.js').Parser} parser */

@ -306,7 +306,7 @@ function block_might_apply_to_node(block, node) {
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
if (selector.type === 'Percentage') continue; if (selector.type === 'Percentage' || selector.type === 'Nth') continue;
const name = selector.name.replace(regex_backslash_and_following_character, '$1'); const name = selector.name.replace(regex_backslash_and_following_character, '$1');

@ -65,12 +65,24 @@ function validate_element(node, context) {
error(attribute, 'invalid-attribute-name', attribute.name); error(attribute, 'invalid-attribute-name', attribute.name);
} }
if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') { if (attribute.name.startsWith('on') && attribute.name.length > 2) {
warn(context.state.analysis.warnings, attribute, context.path, 'avoid-is'); if (
} else if (attribute.name === 'slot') { attribute.value === true ||
is_text_attribute(attribute) ||
attribute.value.length > 1
) {
error(attribute, 'invalid-event-attribute-value');
}
}
if (attribute.name === 'slot') {
/** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf | undefined} */ /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf | undefined} */
validate_slot_attribute(context, attribute); validate_slot_attribute(context, attribute);
} }
if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') {
warn(context.state.analysis.warnings, attribute, context.path, 'avoid-is');
}
} else if (attribute.type === 'AnimateDirective') { } else if (attribute.type === 'AnimateDirective') {
const parent = context.path.at(-2); const parent = context.path.at(-2);
if (parent?.type !== 'EachBlock') { if (parent?.type !== 'EachBlock') {
@ -316,13 +328,6 @@ function is_tag_valid_with_parent(tag, parent_tag) {
* @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>} * @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/ */
export const validation = { export const validation = {
Attribute(node) {
if (node.name.startsWith('on') && node.name.length > 2) {
if (node.value === true || is_text_attribute(node) || node.value.length > 1) {
error(node, 'invalid-event-attribute-value');
}
}
},
BindDirective(node, context) { BindDirective(node, context) {
validate_no_const_assignment(node, node.expression, context.state.scope, true); validate_no_const_assignment(node, node.expression, context.state.scope, true);

@ -1,6 +1,7 @@
import * as b from '../../../utils/builders.js'; import * as b from '../../../utils/builders.js';
import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; import { extract_paths, is_simple_expression } from '../../../utils/ast.js';
import { error } from '../../../errors.js'; import { error } from '../../../errors.js';
import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE } from '../../../../constants.js';
/** /**
* @template {import('./types').ClientTransformState} State * @template {import('./types').ClientTransformState} State
@ -359,29 +360,43 @@ export function get_props_method(binding, state, name, default_value) {
(state.analysis.immutable ? binding.reassigned : binding.mutated); (state.analysis.immutable ? binding.reassigned : binding.mutated);
if (needs_source) { if (needs_source) {
args.push(b.literal(state.analysis.immutable)); let flags = 0;
/** @type {import('estree').Expression | undefined} */
let arg;
if (state.analysis.immutable) {
flags |= PROPS_IS_IMMUTABLE;
} }
if (default_value) { if (default_value) {
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
if (is_simple_expression(default_value)) { if (is_simple_expression(default_value)) {
args.push(default_value); arg = default_value;
} else { } else {
if ( if (
default_value.type === 'CallExpression' && default_value.type === 'CallExpression' &&
default_value.callee.type === 'Identifier' && default_value.callee.type === 'Identifier' &&
default_value.arguments.length === 0 default_value.arguments.length === 0
) { ) {
args.push(default_value.callee); arg = default_value.callee;
} else { } else {
args.push(b.thunk(default_value)); arg = b.thunk(default_value);
}
flags |= PROPS_CALL_DEFAULT_VALUE;
}
} }
args.push(b.true); if (flags || arg) {
args.push(b.literal(flags));
if (arg) args.push(arg);
} }
return b.call('$.prop_source', ...args);
} }
return b.call(needs_source ? '$.prop_source' : '$.prop', ...args); return b.call('$.prop', ...args);
} }
/** /**

@ -795,25 +795,11 @@ function serialize_inline_component(node, component_name, context) {
push_prop( push_prop(
b.get(attribute.name, [ b.get(attribute.name, [
b.return( b.return(
b.call(
'$.exposable',
b.thunk(
/** @type {import('estree').Expression} */ (context.visit(attribute.expression)) /** @type {import('estree').Expression} */ (context.visit(attribute.expression))
) )
)
)
]) ])
); );
// If the binding is just a reference to a top level state variable
// we don't need a setter as the inner component can write to the signal directly
const binding =
attribute.expression.type !== 'Identifier'
? null
: context.state.scope.get(attribute.expression.name);
if (
binding === null ||
(binding.kind !== 'state' && binding.kind !== 'prop' && binding.kind !== 'rest_prop')
) {
const assignment = b.assignment('=', attribute.expression, b.id('$$value')); const assignment = b.assignment('=', attribute.expression, b.id('$$value'));
push_prop( push_prop(
b.set(attribute.name, [ b.set(attribute.name, [
@ -823,7 +809,6 @@ function serialize_inline_component(node, component_name, context) {
} }
} }
} }
}
if (Object.keys(events).length > 0) { if (Object.keys(events).length > 0) {
const events_expression = b.object( const events_expression = b.object(

@ -67,6 +67,11 @@ export interface Percentage extends BaseNode {
value: string; value: string;
} }
export interface Nth extends BaseNode {
type: 'Nth';
value: string;
}
export type SimpleSelector = export type SimpleSelector =
| TypeSelector | TypeSelector
| IdSelector | IdSelector
@ -74,7 +79,8 @@ export type SimpleSelector =
| AttributeSelector | AttributeSelector
| PseudoElementSelector | PseudoElementSelector
| PseudoClassSelector | PseudoClassSelector
| Percentage; | Percentage
| Nth;
export interface Combinator extends BaseNode { export interface Combinator extends BaseNode {
type: 'Combinator'; type: 'Combinator';

@ -5,6 +5,9 @@ export const EACH_IS_CONTROLLED = 1 << 3;
export const EACH_IS_ANIMATED = 1 << 4; export const EACH_IS_ANIMATED = 1 << 4;
export const EACH_IS_IMMUTABLE = 1 << 6; export const EACH_IS_IMMUTABLE = 1 << 6;
export const PROPS_IS_IMMUTABLE = 1;
export const PROPS_CALL_DEFAULT_VALUE = 1 << 1;
/** List of Element events that will be delegated */ /** List of Element events that will be delegated */
export const DelegatedEvents = [ export const DelegatedEvents = [
'beforeinput', 'beforeinput',

@ -34,7 +34,6 @@ import {
untrack, untrack,
effect, effect,
flushSync, flushSync,
expose,
safe_not_equal, safe_not_equal,
current_block, current_block,
source, source,
@ -1202,10 +1201,7 @@ export function bind_prop(props, prop, value) {
/** @param {V | null} value */ /** @param {V | null} value */
const update = (value) => { const update = (value) => {
const current_props = unwrap(props); const current_props = unwrap(props);
const signal = expose(() => current_props[prop]); if (get_descriptor(current_props, prop)?.set !== undefined) {
if (is_signal(signal)) {
set(signal, value);
} else if (get_descriptor(current_props, prop)?.set !== undefined) {
current_props[prop] = value; current_props[prop] = value;
} }
}; };

@ -1,7 +1,8 @@
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js'; import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC, run_all } from '../common.js'; import { EMPTY_FUNC, run_all } from '../common.js';
import { get_descriptors, is_array } from './utils.js'; import { get_descriptor, get_descriptors, is_array } from './utils.js';
import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE } from '../../constants.js';
export const SOURCE = 1; export const SOURCE = 1;
export const DERIVED = 1 << 1; export const DERIVED = 1 << 1;
@ -30,8 +31,7 @@ let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling // Used for handling scheduling
let is_micro_task_queued = false; let is_micro_task_queued = false;
let is_task_queued = false; let is_task_queued = false;
// Used for exposing signals
let is_signal_exposed = false;
// Handle effect queues // Handle effect queues
/** @type {import('./types.js').EffectSignal[]} */ /** @type {import('./types.js').EffectSignal[]} */
@ -63,8 +63,6 @@ export let current_untracking = false;
/** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */ /** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */
let ignore_mutation_validation = false; let ignore_mutation_validation = false;
/** @type {null | import('./types.js').Signal} */
let current_captured_signal = null;
// If we are working with a get() chain that has no active container, // If we are working with a get() chain that has no active container,
// to prevent memory leaks, we skip adding the consumer. // to prevent memory leaks, we skip adding the consumer.
let current_skip_consumer = false; let current_skip_consumer = false;
@ -800,23 +798,6 @@ export function unsubscribe_on_destroy(stores) {
}); });
} }
/**
* Wraps a function and marks execution context so that the last signal read from can be captured
* using the `expose` function.
* @template V
* @param {() => V} fn
* @returns {V}
*/
export function exposable(fn) {
const previous_is_signal_exposed = is_signal_exposed;
try {
is_signal_exposed = true;
return fn();
} finally {
is_signal_exposed = previous_is_signal_exposed;
}
}
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').Signal<V>} signal
@ -836,10 +817,6 @@ export function get(signal) {
return signal.v; return signal.v;
} }
if (is_signal_exposed && current_should_capture_signal) {
current_captured_signal = signal;
}
if (is_signals_recorded) { if (is_signals_recorded) {
captured_signals.add(signal); captured_signals.add(signal);
} }
@ -906,31 +883,6 @@ export function set_sync(signal, value) {
flushSync(() => set(signal, value)); flushSync(() => set(signal, value));
} }
/**
* Invokes a function and captures the last signal that is read during the invocation
* if that signal is read within the `exposable` function context.
* If a signal is captured, it returns the signal instead of the read value.
* @template V
* @param {() => V} possible_signal_fn
* @returns {any}
*/
export function expose(possible_signal_fn) {
const previous_captured_signal = current_captured_signal;
const previous_should_capture_signal = current_should_capture_signal;
current_captured_signal = null;
current_should_capture_signal = true;
try {
const value = possible_signal_fn();
if (current_captured_signal === null) {
return value;
}
return current_captured_signal;
} finally {
current_captured_signal = previous_captured_signal;
current_should_capture_signal = previous_should_capture_signal;
}
}
/** /**
* Invokes a function and captures all signals that are read during the invocation, * Invokes a function and captures all signals that are read during the invocation,
* then invalidates them. * then invalidates them.
@ -1463,35 +1415,19 @@ export function is_store(val) {
* @template V * @template V
* @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj * @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj
* @param {string} key * @param {string} key
* @param {boolean} immutable * @param {number} flags
* @param {V | (() => V)} [default_value] * @param {V | (() => V)} [default_value]
* @param {boolean} [call_default_value]
* @returns {import('./types.js').Signal<V> | (() => V)} * @returns {import('./types.js').Signal<V> | (() => V)}
*/ */
export function prop_source(props_obj, key, immutable, default_value, call_default_value) { export function prop_source(props_obj, key, flags, default_value) {
const call_default_value = (flags & PROPS_CALL_DEFAULT_VALUE) !== 0;
const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
const props = is_signal(props_obj) ? get(props_obj) : props_obj; const props = is_signal(props_obj) ? get(props_obj) : props_obj;
const possible_signal = /** @type {import('./types.js').MaybeSignal<V>} */ ( const update_bound_prop = get_descriptor(props, key)?.set;
expose(() => props[key])
);
const update_bound_prop = Object.getOwnPropertyDescriptor(props, key)?.set;
let value = props[key]; let value = props[key];
const should_set_default_value = value === undefined && default_value !== undefined; const should_set_default_value = value === undefined && default_value !== undefined;
if (
is_signal(possible_signal) &&
possible_signal.v === value &&
update_bound_prop === undefined
) {
if (should_set_default_value) {
set(
possible_signal,
// @ts-expect-error would need a cumbersome method overload to type this
call_default_value ? default_value() : default_value
);
}
return possible_signal;
}
if (should_set_default_value) { if (should_set_default_value) {
value = value =
// @ts-expect-error would need a cumbersome method overload to type this // @ts-expect-error would need a cumbersome method overload to type this
@ -1534,7 +1470,7 @@ export function prop_source(props_obj, key, immutable, default_value, call_defau
} }
}); });
if (is_signal(possible_signal) && update_bound_prop !== undefined) { if (update_bound_prop !== undefined) {
let ignore_first = !should_set_default_value; let ignore_first = !should_set_default_value;
sync_effect(() => { sync_effect(() => {
// Before if to ensure signal dependency is registered // Before if to ensure signal dependency is registered
@ -1548,11 +1484,9 @@ export function prop_source(props_obj, key, immutable, default_value, call_defau
return; return;
} }
if (not_equal(immutable, propagating_value, possible_signal.v)) {
ignore_next1 = true; ignore_next1 = true;
did_update_to_defined = true; did_update_to_defined = true;
untrack(() => update_bound_prop(propagating_value)); untrack(() => update_bound_prop(propagating_value));
}
}); });
} }

@ -177,10 +177,13 @@ class TickAnimation {
} }
cancel() { cancel() {
const t = this.#reversed ? 1 : 0;
active_tick_animations.delete(this); active_tick_animations.delete(this);
const current = this.#current / this.#duration;
if (current > 0 && current < 1) {
const t = this.#reversed ? 1 : 0;
this.#tick_fn(t, 1 - t); this.#tick_fn(t, 1 - t);
} }
}
finish() { finish() {
active_tick_animations.delete(this); active_tick_animations.delete(this);
@ -322,7 +325,7 @@ function create_transition(dom, init, direction, effect) {
animation.onfinish = () => { animation.onfinish = () => {
const is_outro = curr_direction === 'out'; const is_outro = curr_direction === 'out';
/** @type {Animation | TickAnimation} */ (animation).pause(); /** @type {Animation | TickAnimation} */ (animation).cancel();
if (is_outro) { if (is_outro) {
run_all(subs); run_all(subs);
subs = []; subs = [];

@ -4,8 +4,6 @@ export {
set, set,
set_sync, set_sync,
invalidate_inner_signals, invalidate_inner_signals,
expose,
exposable,
source, source,
mutable_source, mutable_source,
derived, derived,

@ -102,16 +102,18 @@ class Animation {
} }
finish() { finish() {
this.onfinish();
this.currentTime = this.#reversed ? 0 : this.#duration; this.currentTime = this.#reversed ? 0 : this.#duration;
if (this.#reversed) { if (this.#reversed) {
raf.animations.delete(this); raf.animations.delete(this);
} }
this.onfinish();
} }
cancel() { cancel() {
this.#paused = true;
if (this.currentTime > 0 && this.currentTime < this.#duration) {
this._applyKeyFrame(this.#reversed ? this.#keyframes.length - 1 : 0); this._applyKeyFrame(this.#reversed ? this.#keyframes.length - 1 : 0);
raf.animations.delete(this); }
} }
pause() { pause() {

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'missing-attribute-value', code: 'missing-attribute-value',
message: 'Expected value for the attribute', message: 'Expected attribute value',
position: [12, 12] position: [12, 12]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-catch-placement', code: 'expected-token',
message: 'Expected to close {#each} block before seeing {:catch} block', message: 'Expected token {:else}',
position: [41, 41] position: [35, 35]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-catch-placement', code: 'invalid-continuing-block-placement',
message: 'Cannot have an {:catch} block outside an {#await ...} block', message:
position: [7, 7] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [1, 1]
} }
}); });

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'invalid-state-location', code: 'invalid-state-location',
message: '$state() can only be used as a variable declaration initializer or a class field', message: '$state() can only be used as a variable declaration initializer or a class field',
position: process.platform === 'win32' ? [35, 43] : [33, 41] position: [33, 41]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-comment', code: 'unexpected-eof',
message: 'comment was left open, expected -->', message: 'Unexpected end of input (expected -->)',
position: [24, 24] position: [24, 24]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'css-syntax-error', code: 'invalid-css-global-selector',
message: ':global() must contain a selector', message: ':global(...) must contain exactly one selector',
position: [9, 9] position: [16, 16]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'css-syntax-error', code: 'invalid-css-identifier',
message: '"{" is expected', message: 'Expected a valid CSS identifier',
position: [24, 24] position: [25, 25]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-else-placement', code: 'expected-token',
message: 'Expected to close {#await} block before seeing {:else} block', message: 'Expected token {:then ...} or {:catch ...}',
position: [29, 29] position: [24, 24]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-else-placement', code: 'invalid-continuing-block-placement',
message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block', message:
position: [11, 11] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [6, 6]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-else-placement', code: 'invalid-continuing-block-placement',
message: 'Expected to close <li> tag before seeing {:else} block', message:
position: [23, 23] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [18, 18]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-elseif-placement', code: 'invalid-continuing-block-placement',
message: 'Expected to close <p> tag before seeing {:else if ...} block', message:
position: [25, 25] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [17, 17]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-elseif-placement', code: 'expected-token',
message: 'Expected to close {#await} block before seeing {:else if ...} block', message: 'Expected token {:then ...} or {:catch ...}',
position: [34, 34] position: [26, 26]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-elseif-placement', code: 'expected-token',
message: 'Cannot have an {:else if ...} block outside an {#if ...} block', message: 'Expected token {:then ...} or {:catch ...}',
position: [35, 35] position: [27, 27]
} }
}); });

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'empty-directive-name', code: 'empty-directive-name',
message: 'Class name cannot be empty', message: 'ClassDirective name cannot be empty',
position: [10, 10] position: [10, 10]
} }
}); });

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'empty-directive-name', code: 'empty-directive-name',
message: 'Action name cannot be empty', message: 'UseDirective name cannot be empty',
position: [8, 8] position: [8, 8]
} }
}); });

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'invalid-derived-export', code: 'invalid-derived-export',
message: 'Cannot export derived state', message: 'Cannot export derived state',
position: process.platform === 'win32' ? [26, 68] : [24, 66] position: [24, 66]
} }
}); });

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'invalid-state-export', code: 'invalid-state-export',
message: 'Cannot export state if it is reassigned', message: 'Cannot export state if it is reassigned',
position: process.platform === 'win32' ? [50, 90] : [46, 86] position: [46, 86]
} }
}); });

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'parse-error', code: 'js-parse-error',
message: 'Assigning to rvalue', message: 'Assigning to rvalue',
position: [1, 1] position: [1, 1]
} }

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'duplicate-style', code: 'duplicate-style-element',
message: 'You can only have one top-level <style> tag per component', message: 'A component can have a single top-level <style> element',
position: [58, 58] position: [58, 58]
} }
}); });

@ -2,7 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: '', code: 'invalid-state-export',
message: 'Cannot export value created with $state' message: 'Cannot export state if it is reassigned',
position: [28, 53]
} }
}); });

@ -0,0 +1,3 @@
export const x = $state(0);
export let y = $state(0);
y = 1;

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-script', code: 'unexpected-eof',
message: '<script> must have a closing tag', message: 'Unexpected end of input',
position: [32, 32] position: [32, 32]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-script', code: 'unclosed-element',
message: '<script> must have a closing tag', message: '<script> was left open',
position: [32, 32] position: [32, 32]
} }
}); });

@ -4,7 +4,7 @@ export default test({
error: { error: {
code: 'invalid-self-placement', code: 'invalid-self-placement',
message: message:
'<svelte:self> components can only exist inside {#if} blocks, {#each} blocks,, {#snippet} blocks or slots passed to components', '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, {#snippet} blocks or slots passed to components',
position: [1, 1] position: [1, 1]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-style', code: 'expected-token',
message: '<style> must have a closing tag', message: 'Expected token </style',
position: [31, 31] position: [31, 31]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-style', code: 'invalid-css-identifier',
message: '<style> must have a closing tag', message: 'Expected a valid CSS identifier',
position: [31, 31] position: [9, 9]
} }
}); });

@ -2,9 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-tag-name', code: 'invalid-svelte-tag',
message: message:
'Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:self, svelte:component, svelte:fragment or svelte:element', 'Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:element, svelte:component, svelte:self or svelte:fragment',
position: [10, 10] position: [10, 10]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-then-placement', code: 'invalid-continuing-block-placement',
message: 'Expected to close <li> tag before seeing {:then} block', message:
position: [26, 26] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [21, 21]
} }
}); });

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-then-placement', code: 'invalid-continuing-block-placement',
message: 'Cannot have an {:then} block outside an {#await ...} block', message:
position: [6, 6] '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [1, 1]
} }
}); });

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'unclosed-attribute-value', code: 'unclosed-attribute-value',
message: 'Expected to close the attribute value with }', message: 'Expected closing } character',
position: [25, 25] position: [19, 19]
} }
}); });

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'unclosed-block', code: 'unclosed-block',
message: 'Block was left open', message: 'Block was left open',
position: [0, 0] position: [0, 1]
} }
}); });

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'unclosed-element', code: 'unclosed-element',
message: '<div> was left open', message: '<div> was left open',
position: [0, 0] position: [0, 1]
} }
}); });

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-closing-tag', code: 'invalid-closing-tag-after-autoclose',
message: '</p> attempted to close <p> that was already automatically closed by <pre>', message: '</p> attempted to close element that was already automatically closed by <pre>',
position: [24, 24] position: [24, 24]
} }
}); });

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save