svelte 5.56.0 → 5.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svelte",
3
3
  "description": "Cybernetically enhanced web apps",
4
4
  "license": "MIT",
5
- "version": "5.56.0",
5
+ "version": "5.56.1",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -5,7 +5,6 @@ import * as acorn from 'acorn';
5
5
  import { walk } from 'zimmerframe';
6
6
  import { tsPlugin } from '@sveltejs/acorn-typescript';
7
7
  import * as e from '../../errors.js';
8
- import { find_matching_bracket } from './utils/bracket.js';
9
8
 
10
9
  const JSParser = acorn.Parser;
11
10
  const TSParser = JSParser.extend(tsPlugin());
@@ -106,38 +105,26 @@ export function parse_expression_at(parser, source, index) {
106
105
  * @returns {Statement}
107
106
  */
108
107
  export function parse_statement_at(parser, source, index) {
109
- const acorn = parser.ts ? TSParser : JSParser;
110
- let end = find_matching_bracket(source, index, '{');
111
- if (end === undefined) e.unexpected_eof(source.length);
112
-
113
- while (source[end - 1] === ';') {
114
- end -= 1;
115
- }
116
-
117
- const padded_source = `${' '.repeat(index)}${source.slice(index, end)}`;
118
- const { onComment, add_comments } = get_comment_handlers(
119
- padded_source,
120
- parser.root.comments,
121
- index
122
- );
108
+ // cast to `any`: acorn's Parser constructor and parseStatement/nextToken aren't in its public types
109
+ const acorn = /** @type {any} */ (parser.ts ? TSParser : JSParser);
110
+ const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index);
123
111
 
124
112
  try {
125
- const ast = acorn.parse(padded_source, {
126
- onComment,
127
- sourceType: 'module',
128
- ecmaVersion: 16,
129
- locations: true
130
- });
131
-
132
- add_comments(ast);
133
-
134
- const statement = /** @type {Statement} */ (
135
- /** @type {unknown} */ (/** @type {Program} */ (ast).body[0])
113
+ // This is like parseExpressionAt but for statements
114
+ const p = new acorn(
115
+ { onComment, sourceType: 'module', ecmaVersion: 16, locations: true },
116
+ source,
117
+ index
136
118
  );
137
- statement.end = Math.min(/** @type {number} */ (statement.end), end);
119
+ p.nextToken();
120
+ const statement = /** @type {Statement} */ (p.parseStatement(null, true, Object.create(null)));
121
+ add_comments(/** @type {acorn.Node} */ (statement));
138
122
  return statement;
139
- } catch (e) {
140
- handle_parse_error(e);
123
+ } catch (err) {
124
+ // A statement that runs to the end of the source (e.g. an unterminated declaration tag)
125
+ // is an EOF, not a stray token; preserve the friendlier `unexpected_eof` diagnostic.
126
+ if (/** @type {any} */ (err).pos === source.length) e.unexpected_eof(source.length);
127
+ handle_parse_error(err);
141
128
  }
142
129
  }
143
130
 
@@ -12,9 +12,9 @@ import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
12
12
 
13
13
  const regex_whitespace_with_closing_curly_brace = /\s*}/y;
14
14
  const regex_supported_declaration = /(?:let|const)\b/y;
15
- // All except `type` are reserved keywords and cannot be used as variable names.
16
- // For type we check if it's not something like `type .x` / `type ()` / `type % 2` / ...
17
- const regex_unsupported_declaration = /(?:(?:var|interface|enum)\b)|(?:type\s+[^?.(`<[&|%^}])/y;
15
+ const regex_unsupported_declaration = /(?:var|interface|enum)\b/y;
16
+ // `type` is a contextual keyword; this is just a shape hint, confirmed by parsing.
17
+ const regex_maybe_type_declaration = /type\b/y;
18
18
 
19
19
  const pointy_bois = { '<': '>' };
20
20
 
@@ -77,10 +77,17 @@ function read_declaration(parser) {
77
77
  e.declaration_tag_invalid_type({ start, end: start + unsupported.length });
78
78
  }
79
79
 
80
- if (!parser.match_regex(regex_supported_declaration)) {
80
+ if (
81
+ !parser.match_regex(regex_supported_declaration) &&
82
+ // `type` is special, since it is not a reserved keyword and can be used
83
+ // as part of a valid expression. We gotta parse first and then see what it is.
84
+ !parser.match_regex(regex_maybe_type_declaration)
85
+ ) {
81
86
  return null;
82
87
  }
83
88
 
89
+ const initial_comment_count = parser.root.comments.length;
90
+
84
91
  /** @type {import('estree').Statement | import('estree').VariableDeclaration} */
85
92
  let declaration;
86
93
  try {
@@ -117,10 +124,16 @@ function read_declaration(parser) {
117
124
  }
118
125
 
119
126
  if (declaration.type !== 'VariableDeclaration') {
120
- e.declaration_tag_invalid_type({
121
- start: declaration.start ?? start,
122
- end: declaration.end ?? parser.index
123
- });
127
+ if (declaration.type === 'ExpressionStatement') {
128
+ parser.root.comments.length = initial_comment_count; // Else they show up duplicated
129
+ return null;
130
+ } else {
131
+ // This is a TSTypeAliasDeclaration
132
+ e.declaration_tag_invalid_type({
133
+ start: declaration.start ?? start,
134
+ end: declaration.end ?? parser.index
135
+ });
136
+ }
124
137
  }
125
138
 
126
139
  // TODO support using
@@ -39,7 +39,9 @@ function find_string_end(string, search_start_index, string_start_char) {
39
39
  * @returns {number} The index of the end of this regex expression, or `Infinity` if not found.
40
40
  */
41
41
  function find_regex_end(string, search_start_index) {
42
- return find_unescaped_char(string, search_start_index, '/');
42
+ const slash = find_unescaped_char(string, search_start_index, '/');
43
+ const eol = find_unescaped_char(string, search_start_index, '\n');
44
+ return slash < eol ? slash : Infinity;
43
45
  }
44
46
 
45
47
  /**
@@ -105,7 +107,11 @@ export function find_matching_bracket(template, index, open) {
105
107
  continue;
106
108
  case '/': {
107
109
  const next_char = template[i + 1];
108
- if (!next_char) continue;
110
+ if (!next_char) {
111
+ // `/` is the last character; advance past it so we don't loop forever
112
+ i++;
113
+ continue;
114
+ }
109
115
  if (next_char === '/') {
110
116
  i = infinity_if_negative(template.indexOf('\n', i + 1)) + '\n'.length;
111
117
  continue;
@@ -114,7 +120,12 @@ export function find_matching_bracket(template, index, open) {
114
120
  i = infinity_if_negative(template.indexOf('*/', i + 1)) + '*/'.length;
115
121
  continue;
116
122
  }
117
- i = find_regex_end(template, i + 1) + '/'.length;
123
+ const end = find_regex_end(template, i + 1) + '/'.length;
124
+ if (end === Infinity) {
125
+ i++;
126
+ } else {
127
+ i = end;
128
+ }
118
129
  continue;
119
130
  }
120
131
  default: {
@@ -2,21 +2,34 @@
2
2
  /** @import { Context } from '../types' */
3
3
  import * as b from '#compiler/builders';
4
4
  import * as e from '../../../errors.js';
5
- import { validate_opening_tag } from './shared/utils.js';
5
+ import { extract_identifiers } from '../../../utils/ast.js';
6
6
 
7
7
  /**
8
8
  * @param {AST.DeclarationTag} node
9
9
  * @param {Context} context
10
10
  */
11
11
  export function DeclarationTag(node, context) {
12
- validate_opening_tag(node, context.state, node.declaration.kind[0]);
13
12
  if (!context.state.analysis.runes && !context.state.analysis.maybe_runes) {
14
13
  e.declaration_tag_no_legacy_mode(node);
15
14
  }
16
15
 
16
+ const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
17
+ if (is_top_level) {
18
+ const duplicate = node.declaration.declarations
19
+ .flatMap((declaration) => extract_identifiers(declaration.id))
20
+ .find((id) => context.state.analysis.instance.scope.declarations.has(id.name));
21
+ if (duplicate) {
22
+ e.declaration_duplicate(duplicate, duplicate.name);
23
+ }
24
+ }
25
+
17
26
  context.visit(node.declaration, {
18
27
  ...context.state,
19
28
  in_declaration_tag: true,
29
+ // the declaration lives in the fragment scope, which is one level deeper than the
30
+ // `function_depth` we're tracking here (`set_scope` doesn't update `function_depth`).
31
+ // align them so that `state_referenced_locally` warnings are calculated correctly
32
+ function_depth: context.state.scope.function_depth,
20
33
  expression: node.metadata.expression
21
34
  });
22
35
 
@@ -25,19 +25,24 @@ export function SnippetBlock(node, context) {
25
25
 
26
26
  context.next({ ...context.state, parent_element: null });
27
27
 
28
- const can_hoist =
29
- context.path.length === 1 &&
30
- context.path[0].type === 'Fragment' &&
31
- can_hoist_snippet(context.state.scope, context.state.scopes);
28
+ const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
32
29
 
33
- const name = node.expression.name;
30
+ if (is_top_level) {
31
+ const name = node.expression.name;
34
32
 
35
- if (can_hoist) {
36
- const binding = /** @type {Binding} */ (context.state.scope.get(name));
37
- context.state.analysis.module.scope.declarations.set(name, binding);
38
- }
33
+ if (context.state.analysis.instance.scope.declarations.has(name)) {
34
+ e.declaration_duplicate(node.expression, name);
35
+ }
36
+
37
+ node.metadata.can_hoist =
38
+ is_top_level && can_hoist_snippet(context.state.scope, context.state.scopes);
39
39
 
40
- node.metadata.can_hoist = can_hoist;
40
+ if (node.metadata.can_hoist) {
41
+ const name = node.expression.name;
42
+ const binding = /** @type {Binding} */ (context.state.scope.get(name));
43
+ context.state.analysis.module.scope.declarations.set(name, binding);
44
+ }
45
+ }
41
46
 
42
47
  const { path } = context;
43
48
  const parent = path.at(-2);
@@ -10,8 +10,10 @@ import { add_state_transformers } from './shared/declarations.js';
10
10
  * @param {ComponentContext} context
11
11
  */
12
12
  export function DeclarationTag(node, context) {
13
- const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
13
+ // register the transformers _before_ visiting the declaration, so that
14
+ // later declarators can reference earlier ones (e.g. `{let a = $state(0), b = $derived(a * 2)}`)
14
15
  add_state_transformers(context);
16
+ const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
15
17
 
16
18
  if (
17
19
  node.metadata.promises_id &&
@@ -343,7 +343,7 @@ export class PromiseOptimiser {
343
343
  * @param {ExpressionMetadata} metadata
344
344
  */
345
345
  check_blockers(metadata) {
346
- for (const binding of metadata.dependencies) {
346
+ for (const binding of metadata.references) {
347
347
  if (binding.blocker) {
348
348
  this.#blockers.add(binding.blocker);
349
349
  }
@@ -203,7 +203,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
203
203
  var each_array = derived_safe_equal(() => {
204
204
  var collection = get_collection();
205
205
 
206
- return is_array(collection) ? collection : collection == null ? [] : array_from(collection);
206
+ return /** @type {V[]} */ (
207
+ is_array(collection) ? collection : collection == null ? [] : array_from(collection)
208
+ );
207
209
  });
208
210
 
209
211
  if (DEV) {
@@ -332,6 +332,15 @@ function set_attributes(
332
332
 
333
333
  var setters = get_setters(element);
334
334
 
335
+ if (element.nodeName === INPUT_TAG && 'type' in next && ('value' in next || '__value' in next)) {
336
+ var type = next.type;
337
+
338
+ if (type !== current.type || (type === undefined && element.hasAttribute('type'))) {
339
+ current.type = type;
340
+ set_attribute(element, 'type', type, skip_warning);
341
+ }
342
+ }
343
+
335
344
  // since key is captured we use const
336
345
  for (const key in next) {
337
346
  // let instead of var because referenced in a closure
@@ -7,7 +7,6 @@ import { is } from '../../../proxy.js';
7
7
  import { queue_micro_task } from '../../task.js';
8
8
  import { hydrating } from '../../hydration.js';
9
9
  import { tick, untrack } from '../../../runtime.js';
10
- import { is_runes } from '../../../context.js';
11
10
  import { current_batch, previous_batch } from '../../../reactivity/batch.js';
12
11
  import { async_mode_flag } from '../../../../flags/index.js';
13
12
 
@@ -1,4 +1,4 @@
1
- /** @import { Blocker, Effect, Value } from '#client' */
1
+ /** @import { Blocker, Effect, Source, Value } from '#client' */
2
2
  import { DESTROYED, STALE_REACTION } from '#client/constants';
3
3
  import { DEV } from 'esm-env';
4
4
  import {
@@ -38,8 +38,22 @@ export function flatten(blockers, sync, async, fn) {
38
38
  // Filter out already-settled blockers - no need to wait for them
39
39
  var pending = blockers.filter((b) => !b.settled);
40
40
 
41
+ var deriveds = sync.map(d);
42
+
43
+ if (DEV) {
44
+ deriveds.forEach((d, i) => {
45
+ // TODO this is kinda useful for debugging but a lousy implementation —
46
+ // maybe the compiler could pass through the template string
47
+ d.label = sync[i]
48
+ .toString()
49
+ .replace('() => ', '')
50
+ .replaceAll('$.eager(() => ', '$state.eager(')
51
+ .replace(/\$\.get\((.+?)\)/g, (_, id) => id);
52
+ });
53
+ }
54
+
41
55
  if (async.length === 0 && pending.length === 0) {
42
- fn(sync.map(d));
56
+ fn(deriveds);
43
57
  return;
44
58
  }
45
59
 
@@ -53,8 +67,10 @@ export function flatten(blockers, sync, async, fn) {
53
67
  ? Promise.all(pending.map((b) => b.promise))
54
68
  : null;
55
69
 
56
- /** @param {Value[]} values */
57
- function finish(values) {
70
+ /**
71
+ * @param {Source[]} async
72
+ */
73
+ function finish(async) {
58
74
  if ((parent.f & DESTROYED) !== 0) {
59
75
  return;
60
76
  }
@@ -62,7 +78,7 @@ export function flatten(blockers, sync, async, fn) {
62
78
  restore();
63
79
 
64
80
  try {
65
- fn(values);
81
+ fn([...deriveds, ...async]);
66
82
  } catch (error) {
67
83
  invoke_error_boundary(error, parent);
68
84
  }
@@ -74,17 +90,14 @@ export function flatten(blockers, sync, async, fn) {
74
90
 
75
91
  // Fast path: blockers but no async expressions
76
92
  if (async.length === 0) {
77
- /** @type {Promise<any>} */ (blocker_promise)
78
- .then(() => finish(sync.map(d)))
79
- .finally(decrement_pending);
80
-
93
+ /** @type {Promise<any>} */ (blocker_promise).then(() => finish([])).finally(decrement_pending);
81
94
  return;
82
95
  }
83
96
 
84
97
  // Full path: has async expressions
85
98
  function run() {
86
99
  Promise.all(async.map((expression) => async_derived(expression)))
87
- .then((result) => finish([...sync.map(d), ...result]))
100
+ .then(finish)
88
101
  .catch((error) => invoke_error_boundary(error, parent))
89
102
  .finally(decrement_pending);
90
103
  }
@@ -387,7 +387,9 @@ export function render_effect(fn, flags = 0) {
387
387
  */
388
388
  export function template_effect(fn, sync = [], async = [], blockers = []) {
389
389
  flatten(blockers, sync, async, (values) => {
390
- create_effect(RENDER_EFFECT, () => fn(...values.map(get)));
390
+ create_effect(RENDER_EFFECT, () => {
391
+ fn(...values.map(get));
392
+ });
391
393
  });
392
394
  }
393
395
 
@@ -518,7 +520,7 @@ export function destroy_effect(effect, remove_dom = true) {
518
520
  removed = true;
519
521
  }
520
522
 
521
- set_signal_status(effect, DESTROYING);
523
+ effect.f |= DESTROYING;
522
524
  destroy_effect_children(effect, remove_dom && !removed);
523
525
  remove_reactions(effect, 0);
524
526
 
@@ -132,11 +132,12 @@ export class SvelteURLSearchParams extends URLSearchParams {
132
132
  * @returns {void}
133
133
  */
134
134
  set(name, value) {
135
- var previous = super.getAll(name).join('');
135
+ var previous = super.getAll(name);
136
136
  super.set(name, value);
137
137
  // can't use has(name, value), because for something like https://svelte.dev?foo=1&bar=2&foo=3
138
138
  // if you set `foo` to 1, then foo=3 gets deleted whilst `has("foo", "1")` returns true
139
- if (previous !== super.getAll(name).join('')) {
139
+ var current = super.getAll(name);
140
+ if (previous.length !== current.length || previous.some((value, i) => value !== current[i])) {
140
141
  this.#update_url();
141
142
  increment(this.#version);
142
143
  }
package/src/version.js CHANGED
@@ -4,5 +4,5 @@
4
4
  * The current version, as set in package.json.
5
5
  * @type {string}
6
6
  */
7
- export const VERSION = '5.56.0';
7
+ export const VERSION = '5.56.1';
8
8
  export const PUBLIC_VERSION = '5';