svelte 5.45.1 → 5.45.3

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.
Files changed (39) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/errors.js +9 -0
  4. package/src/compiler/phases/1-parse/index.js +32 -16
  5. package/src/compiler/phases/1-parse/read/context.js +3 -12
  6. package/src/compiler/phases/1-parse/state/element.js +79 -55
  7. package/src/compiler/phases/1-parse/state/tag.js +8 -16
  8. package/src/compiler/phases/2-analyze/index.js +2 -2
  9. package/src/compiler/phases/2-analyze/visitors/Identifier.js +2 -1
  10. package/src/compiler/phases/2-analyze/visitors/RegularElement.js +3 -2
  11. package/src/compiler/phases/3-transform/client/transform-template/index.js +1 -2
  12. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +27 -11
  13. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +2 -1
  14. package/src/compiler/phases/3-transform/client/visitors/Component.js +1 -2
  15. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +1 -1
  16. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +1 -0
  17. package/src/compiler/phases/3-transform/client/visitors/SvelteComponent.js +2 -1
  18. package/src/compiler/phases/3-transform/client/visitors/SvelteHead.js +1 -1
  19. package/src/compiler/phases/3-transform/client/visitors/SvelteSelf.js +2 -1
  20. package/src/compiler/phases/3-transform/client/visitors/TitleElement.js +1 -1
  21. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +2 -1
  22. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +27 -25
  23. package/src/compiler/phases/3-transform/client/visitors/shared/events.js +8 -2
  24. package/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +10 -4
  25. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +2 -2
  26. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +1 -1
  27. package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +1 -1
  28. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +2 -2
  29. package/src/compiler/phases/3-transform/shared/transform-async.js +4 -1
  30. package/src/compiler/phases/nodes.js +4 -2
  31. package/src/compiler/state.js +12 -4
  32. package/src/compiler/utils/builders.js +6 -2
  33. package/src/internal/client/dev/debug.js +361 -3
  34. package/src/internal/client/reactivity/batch.js +3 -0
  35. package/src/internal/client/reactivity/sources.js +2 -0
  36. package/src/internal/server/hydratable.js +11 -1
  37. package/src/version.js +1 -1
  38. package/types/index.d.ts +16 -11
  39. package/types/index.d.ts.map +1 -1
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.45.1",
5
+ "version": "5.45.3",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -1129,6 +1129,15 @@ export function expected_pattern(node) {
1129
1129
  e(node, 'expected_pattern', `Expected identifier or destructure pattern\nhttps://svelte.dev/e/expected_pattern`);
1130
1130
  }
1131
1131
 
1132
+ /**
1133
+ * Expected 'html', 'render', 'attach', 'const', or 'debug'
1134
+ * @param {null | number | NodeLike} node
1135
+ * @returns {never}
1136
+ */
1137
+ export function expected_tag(node) {
1138
+ e(node, 'expected_tag', `Expected 'html', 'render', 'attach', 'const', or 'debug'\nhttps://svelte.dev/e/expected_tag`);
1139
+ }
1140
+
1132
1141
  /**
1133
1142
  * Expected token %token%
1134
1143
  * @param {null | number | NodeLike} node
@@ -1,4 +1,6 @@
1
1
  /** @import { AST } from '#compiler' */
2
+ /** @import { Location } from 'locate-character' */
3
+ /** @import * as ESTree from 'estree' */
2
4
  // @ts-expect-error acorn type definitions are borked in the release we use
3
5
  import { isIdentifierStart, isIdentifierChar } from 'acorn';
4
6
  import fragment from './state/fragment.js';
@@ -218,31 +220,45 @@ export class Parser {
218
220
  return result;
219
221
  }
220
222
 
221
- /** @param {any} allow_reserved */
222
- read_identifier(allow_reserved = false) {
223
+ /**
224
+ * @returns {ESTree.Identifier & { start: number, end: number, loc: { start: Location, end: Location } }}
225
+ */
226
+ read_identifier() {
223
227
  const start = this.index;
228
+ let end = start;
229
+ let name = '';
224
230
 
225
- let i = this.index;
231
+ const code = /** @type {number} */ (this.template.codePointAt(this.index));
226
232
 
227
- const code = /** @type {number} */ (this.template.codePointAt(i));
228
- if (!isIdentifierStart(code, true)) return null;
233
+ if (isIdentifierStart(code, true)) {
234
+ let i = this.index;
235
+ end += code <= 0xffff ? 1 : 2;
229
236
 
230
- i += code <= 0xffff ? 1 : 2;
237
+ while (end < this.template.length) {
238
+ const code = /** @type {number} */ (this.template.codePointAt(end));
231
239
 
232
- while (i < this.template.length) {
233
- const code = /** @type {number} */ (this.template.codePointAt(i));
234
-
235
- if (!isIdentifierChar(code, true)) break;
236
- i += code <= 0xffff ? 1 : 2;
237
- }
240
+ if (!isIdentifierChar(code, true)) break;
241
+ end += code <= 0xffff ? 1 : 2;
242
+ }
238
243
 
239
- const identifier = this.template.slice(this.index, (this.index = i));
244
+ name = this.template.slice(start, end);
245
+ this.index = end;
240
246
 
241
- if (!allow_reserved && is_reserved(identifier)) {
242
- e.unexpected_reserved_word(start, identifier);
247
+ if (is_reserved(name)) {
248
+ e.unexpected_reserved_word(start, name);
249
+ }
243
250
  }
244
251
 
245
- return identifier;
252
+ return {
253
+ type: 'Identifier',
254
+ name,
255
+ start,
256
+ end,
257
+ loc: {
258
+ start: state.locator(start),
259
+ end: state.locator(end)
260
+ }
261
+ };
246
262
  }
247
263
 
248
264
  /** @param {RegExp} pattern */
@@ -1,11 +1,9 @@
1
- /** @import { Location } from 'locate-character' */
2
1
  /** @import { Pattern } from 'estree' */
3
2
  /** @import { Parser } from '../index.js' */
4
3
  import { match_bracket } from '../utils/bracket.js';
5
4
  import { parse_expression_at } from '../acorn.js';
6
5
  import { regex_not_newline_characters } from '../../patterns.js';
7
6
  import * as e from '../../../errors.js';
8
- import { locator } from '../../../state.js';
9
7
 
10
8
  /**
11
9
  * @param {Parser} parser
@@ -15,20 +13,13 @@ export default function read_pattern(parser) {
15
13
  const start = parser.index;
16
14
  let i = parser.index;
17
15
 
18
- const name = parser.read_identifier();
16
+ const id = parser.read_identifier();
19
17
 
20
- if (name !== null) {
18
+ if (id.name !== '') {
21
19
  const annotation = read_type_annotation(parser);
22
20
 
23
21
  return {
24
- type: 'Identifier',
25
- name,
26
- start,
27
- loc: {
28
- start: /** @type {Location} */ (locator(start)),
29
- end: /** @type {Location} */ (locator(parser.index))
30
- },
31
- end: parser.index,
22
+ ...id,
32
23
  typeAnnotation: annotation
33
24
  };
34
25
  }
@@ -1,4 +1,5 @@
1
- /** @import { Expression } from 'estree' */
1
+ /** @import { Expression, Identifier, SourceLocation } from 'estree' */
2
+ /** @import { Location } from 'locate-character' */
2
3
  /** @import { AST } from '#compiler' */
3
4
  /** @import { Parser } from '../index.js' */
4
5
  import { is_void } from '../../../../utils.js';
@@ -13,6 +14,8 @@ import { create_attribute, ExpressionMetadata, is_element_node } from '../../nod
13
14
  import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
14
15
  import { closing_tag_omitted } from '../../../../html-tree-validation.js';
15
16
  import { list } from '../../../utils/string.js';
17
+ import { locator } from '../../../state.js';
18
+ import * as b from '#compiler/builders';
16
19
 
17
20
  const regex_invalid_unquoted_attribute_value = /^(\/>|[\s"'=<>`])/;
18
21
  const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
@@ -67,10 +70,9 @@ export default function element(parser) {
67
70
  return;
68
71
  }
69
72
 
70
- const is_closing_tag = parser.eat('/');
71
- const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
73
+ if (parser.eat('/')) {
74
+ const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
72
75
 
73
- if (is_closing_tag) {
74
76
  parser.allow_whitespace();
75
77
  parser.eat('>', true);
76
78
 
@@ -125,39 +127,41 @@ export default function element(parser) {
125
127
  return;
126
128
  }
127
129
 
128
- if (name.startsWith('svelte:') && !meta_tags.has(name)) {
129
- const bounds = { start: start + 1, end: start + 1 + name.length };
130
+ const tag = read_tag(parser, regex_whitespace_or_slash_or_closing_tag);
131
+
132
+ if (tag.name.startsWith('svelte:') && !meta_tags.has(tag.name)) {
133
+ const bounds = { start: start + 1, end: start + 1 + tag.name.length };
130
134
  e.svelte_meta_invalid_tag(bounds, list(Array.from(meta_tags.keys())));
131
135
  }
132
136
 
133
- if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
137
+ if (!regex_valid_element_name.test(tag.name) && !regex_valid_component_name.test(tag.name)) {
134
138
  // <div. -> in the middle of typing -> allow in loose mode
135
- if (!parser.loose || !name.endsWith('.')) {
136
- const bounds = { start: start + 1, end: start + 1 + name.length };
139
+ if (!parser.loose || !tag.name.endsWith('.')) {
140
+ const bounds = { start: start + 1, end: start + 1 + tag.name.length };
137
141
  e.tag_invalid_name(bounds);
138
142
  }
139
143
  }
140
144
 
141
- if (root_only_meta_tags.has(name)) {
142
- if (name in parser.meta_tags) {
143
- e.svelte_meta_duplicate(start, name);
145
+ if (root_only_meta_tags.has(tag.name)) {
146
+ if (tag.name in parser.meta_tags) {
147
+ e.svelte_meta_duplicate(start, tag.name);
144
148
  }
145
149
 
146
150
  if (parent.type !== 'Root') {
147
- e.svelte_meta_invalid_placement(start, name);
151
+ e.svelte_meta_invalid_placement(start, tag.name);
148
152
  }
149
153
 
150
- parser.meta_tags[name] = true;
154
+ parser.meta_tags[tag.name] = true;
151
155
  }
152
156
 
153
- const type = meta_tags.has(name)
154
- ? meta_tags.get(name)
155
- : regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.'))
157
+ const type = meta_tags.has(tag.name)
158
+ ? meta_tags.get(tag.name)
159
+ : regex_valid_component_name.test(tag.name) || (parser.loose && tag.name.endsWith('.'))
156
160
  ? 'Component'
157
- : name === 'title' && parent_is_head(parser.stack)
161
+ : tag.name === 'title' && parent_is_head(parser.stack)
158
162
  ? 'TitleElement'
159
163
  : // TODO Svelte 6/7: once slots are removed in favor of snippets, always keep slot as a regular element
160
- name === 'slot' && !parent_is_shadowroot_template(parser.stack)
164
+ tag.name === 'slot' && !parent_is_shadowroot_template(parser.stack)
161
165
  ? 'SlotElement'
162
166
  : 'RegularElement';
163
167
 
@@ -168,7 +172,8 @@ export default function element(parser) {
168
172
  type,
169
173
  start,
170
174
  end: -1,
171
- name,
175
+ name: tag.name,
176
+ name_loc: tag.loc,
172
177
  attributes: [],
173
178
  fragment: create_fragment(true),
174
179
  metadata: {
@@ -184,7 +189,8 @@ export default function element(parser) {
184
189
  type,
185
190
  start,
186
191
  end: -1,
187
- name,
192
+ name: tag.name,
193
+ name_loc: tag.loc,
188
194
  attributes: [],
189
195
  fragment: create_fragment(true),
190
196
  metadata: {
@@ -194,14 +200,14 @@ export default function element(parser) {
194
200
 
195
201
  parser.allow_whitespace();
196
202
 
197
- if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) {
203
+ if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, tag.name)) {
198
204
  const end = parent.fragment.nodes[0]?.start ?? start;
199
- w.element_implicitly_closed({ start: parent.start, end }, `<${name}>`, `</${parent.name}>`);
205
+ w.element_implicitly_closed({ start: parent.start, end }, `<${tag.name}>`, `</${parent.name}>`);
200
206
  parent.end = start;
201
207
  parser.pop();
202
208
  parser.last_auto_closed_tag = {
203
209
  tag: parent.name,
204
- reason: name,
210
+ reason: tag.name,
205
211
  depth: parser.stack.length
206
212
  };
207
213
  }
@@ -211,7 +217,7 @@ export default function element(parser) {
211
217
 
212
218
  const current = parser.current();
213
219
  const is_top_level_script_or_style =
214
- (name === 'script' || name === 'style') && current.type === 'Root';
220
+ (tag.name === 'script' || tag.name === 'style') && current.type === 'Root';
215
221
 
216
222
  const read = is_top_level_script_or_style ? read_static_attribute : read_attribute;
217
223
 
@@ -324,7 +330,7 @@ export default function element(parser) {
324
330
  }
325
331
  }
326
332
 
327
- if (name === 'script') {
333
+ if (tag.name === 'script') {
328
334
  const content = read_script(parser, start, element.attributes);
329
335
  if (prev_comment) {
330
336
  // We take advantage of the fact that the root will never have leadingComments set,
@@ -352,7 +358,7 @@ export default function element(parser) {
352
358
 
353
359
  parser.append(element);
354
360
 
355
- const self_closing = parser.eat('/') || is_void(name);
361
+ const self_closing = parser.eat('/') || is_void(tag.name);
356
362
  const closed = parser.eat('>', true, false);
357
363
 
358
364
  // Loose parsing mode
@@ -382,7 +388,7 @@ export default function element(parser) {
382
388
  if (self_closing || !closed) {
383
389
  // don't push self-closing elements onto the stack
384
390
  element.end = parser.index;
385
- } else if (name === 'textarea') {
391
+ } else if (tag.name === 'textarea') {
386
392
  // special case
387
393
  element.fragment.nodes = read_sequence(
388
394
  parser,
@@ -391,10 +397,10 @@ export default function element(parser) {
391
397
  );
392
398
  parser.read(regex_closing_textarea_tag);
393
399
  element.end = parser.index;
394
- } else if (name === 'script' || name === 'style') {
400
+ } else if (tag.name === 'script' || tag.name === 'style') {
395
401
  // special case
396
402
  const start = parser.index;
397
- const data = parser.read_until(new RegExp(`</${name}>`));
403
+ const data = parser.read_until(new RegExp(`</${tag.name}>`));
398
404
  const end = parser.index;
399
405
 
400
406
  /** @type {AST.Text} */
@@ -407,7 +413,7 @@ export default function element(parser) {
407
413
  };
408
414
 
409
415
  element.fragment.nodes.push(node);
410
- parser.eat(`</${name}>`, true);
416
+ parser.eat(`</${tag.name}>`, true);
411
417
  element.end = parser.index;
412
418
  } else {
413
419
  parser.stack.push(element);
@@ -450,8 +456,8 @@ function parent_is_shadowroot_template(stack) {
450
456
  function read_static_attribute(parser) {
451
457
  const start = parser.index;
452
458
 
453
- const name = parser.read_until(regex_token_ending_character);
454
- if (!name) return null;
459
+ const tag = read_tag(parser, regex_token_ending_character);
460
+ if (!tag.name) return null;
455
461
 
456
462
  /** @type {true | Array<AST.Text | AST.ExpressionTag>} */
457
463
  let value = true;
@@ -485,7 +491,7 @@ function read_static_attribute(parser) {
485
491
  e.expected_token(parser.index, '=');
486
492
  }
487
493
 
488
- return create_attribute(name, start, parser.index, value);
494
+ return create_attribute(tag.name, tag.loc, start, parser.index, value);
489
495
  }
490
496
 
491
497
  /**
@@ -538,10 +544,9 @@ function read_attribute(parser) {
538
544
 
539
545
  return spread;
540
546
  } else {
541
- const value_start = parser.index;
542
- let name = parser.read_identifier();
547
+ const id = parser.read_identifier();
543
548
 
544
- if (name === null) {
549
+ if (id.name === '') {
545
550
  if (
546
551
  parser.loose &&
547
552
  (parser.match('#') || parser.match('/') || parser.match('@') || parser.match(':'))
@@ -551,7 +556,6 @@ function read_attribute(parser) {
551
556
  return null;
552
557
  } else if (parser.loose && parser.match('}')) {
553
558
  // Likely in the middle of typing, just created the shorthand
554
- name = '';
555
559
  } else {
556
560
  e.attribute_empty_shorthand(start);
557
561
  }
@@ -563,32 +567,28 @@ function read_attribute(parser) {
563
567
  /** @type {AST.ExpressionTag} */
564
568
  const expression = {
565
569
  type: 'ExpressionTag',
566
- start: value_start,
567
- end: value_start + name.length,
568
- expression: {
569
- start: value_start,
570
- end: value_start + name.length,
571
- type: 'Identifier',
572
- name
573
- },
570
+ start: id.start,
571
+ end: id.end,
572
+ expression: id,
574
573
  metadata: {
575
574
  expression: new ExpressionMetadata()
576
575
  }
577
576
  };
578
577
 
579
- return create_attribute(name, start, parser.index, expression);
578
+ return create_attribute(id.name, id.loc, start, parser.index, expression);
580
579
  }
581
580
  }
582
581
 
583
- const name = parser.read_until(regex_token_ending_character);
584
- if (!name) return null;
582
+ const tag = read_tag(parser, regex_token_ending_character);
583
+
584
+ if (!tag.name) return null;
585
585
 
586
586
  let end = parser.index;
587
587
 
588
588
  parser.allow_whitespace();
589
589
 
590
- const colon_index = name.indexOf(':');
591
- const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index));
590
+ const colon_index = tag.name.indexOf(':');
591
+ const type = colon_index !== -1 && get_directive_type(tag.name.slice(0, colon_index));
592
592
 
593
593
  /** @type {true | AST.ExpressionTag | Array<AST.Text | AST.ExpressionTag>} */
594
594
  let value = true;
@@ -617,10 +617,10 @@ function read_attribute(parser) {
617
617
  }
618
618
 
619
619
  if (type) {
620
- const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
620
+ const [directive_name, ...modifiers] = tag.name.slice(colon_index + 1).split('|');
621
621
 
622
622
  if (directive_name === '') {
623
- e.directive_missing_name({ start, end: start + colon_index + 1 }, name);
623
+ e.directive_missing_name({ start, end: start + colon_index + 1 }, tag.name);
624
624
  }
625
625
 
626
626
  if (type === 'StyleDirective') {
@@ -629,6 +629,7 @@ function read_attribute(parser) {
629
629
  end,
630
630
  type,
631
631
  name: directive_name,
632
+ name_loc: tag.loc,
632
633
  modifiers: /** @type {Array<'important'>} */ (modifiers),
633
634
  value,
634
635
  metadata: {
@@ -659,6 +660,7 @@ function read_attribute(parser) {
659
660
  end,
660
661
  type,
661
662
  name: directive_name,
663
+ name_loc: tag.loc,
662
664
  expression,
663
665
  metadata: {
664
666
  expression: new ExpressionMetadata()
@@ -669,7 +671,7 @@ function read_attribute(parser) {
669
671
  directive.modifiers = modifiers;
670
672
 
671
673
  if (directive.type === 'TransitionDirective') {
672
- const direction = name.slice(0, colon_index);
674
+ const direction = tag.name.slice(0, colon_index);
673
675
  directive.intro = direction === 'in' || direction === 'transition';
674
676
  directive.outro = direction === 'out' || direction === 'transition';
675
677
  }
@@ -690,7 +692,7 @@ function read_attribute(parser) {
690
692
  return directive;
691
693
  }
692
694
 
693
- return create_attribute(name, start, end, value);
695
+ return create_attribute(tag.name, tag.loc, start, end, value);
694
696
  }
695
697
 
696
698
  /**
@@ -851,3 +853,25 @@ function read_sequence(parser, done, location) {
851
853
  e.unexpected_eof(parser.template.length);
852
854
  }
853
855
  }
856
+
857
+ /**
858
+ * @param {Parser} parser
859
+ * @param {RegExp} regex
860
+ * @returns {Identifier & { start: number, end: number, loc: SourceLocation }}
861
+ */
862
+ function read_tag(parser, regex) {
863
+ const start = parser.index;
864
+ const name = parser.read_until(regex);
865
+ const end = parser.index;
866
+
867
+ return {
868
+ type: 'Identifier',
869
+ name,
870
+ start,
871
+ end,
872
+ loc: {
873
+ start: locator(start),
874
+ end: locator(end)
875
+ }
876
+ };
877
+ }
@@ -177,7 +177,7 @@ function open(parser) {
177
177
 
178
178
  if (parser.eat(',')) {
179
179
  parser.allow_whitespace();
180
- index = parser.read_identifier();
180
+ index = parser.read_identifier().name;
181
181
  if (!index) {
182
182
  e.expected_identifier(parser.index);
183
183
  }
@@ -347,16 +347,10 @@ function open(parser) {
347
347
  if (parser.eat('snippet')) {
348
348
  parser.require_whitespace();
349
349
 
350
- const name_start = parser.index;
351
- let name = parser.read_identifier();
352
- const name_end = parser.index;
350
+ const id = parser.read_identifier();
353
351
 
354
- if (name === null) {
355
- if (parser.loose) {
356
- name = '';
357
- } else {
358
- e.expected_identifier(parser.index);
359
- }
352
+ if (id.name === '' && !parser.loose) {
353
+ e.expected_identifier(parser.index);
360
354
  }
361
355
 
362
356
  parser.allow_whitespace();
@@ -415,12 +409,7 @@ function open(parser) {
415
409
  type: 'SnippetBlock',
416
410
  start,
417
411
  end: -1,
418
- expression: {
419
- type: 'Identifier',
420
- start: name_start,
421
- end: name_end,
422
- name
423
- },
412
+ expression: id,
424
413
  typeParams: type_params,
425
414
  parameters: function_expression.params,
426
415
  body: create_fragment(),
@@ -724,6 +713,7 @@ function special(parser) {
724
713
  expression: new ExpressionMetadata()
725
714
  }
726
715
  });
716
+ return;
727
717
  }
728
718
 
729
719
  if (parser.eat('render')) {
@@ -755,5 +745,7 @@ function special(parser) {
755
745
  snippets: new Set()
756
746
  }
757
747
  });
748
+ return;
758
749
  }
750
+ e.expected_tag(parser.index);
759
751
  }
@@ -900,7 +900,7 @@ export function analyze_component(root, source, options) {
900
900
  // We need an empty class to generate the set_class() or class="" correctly
901
901
  if (!has_spread && !has_class && (node.metadata.scoped || has_class_directive)) {
902
902
  node.attributes.push(
903
- create_attribute('class', -1, -1, [
903
+ create_attribute('class', null, -1, -1, [
904
904
  {
905
905
  type: 'Text',
906
906
  data: '',
@@ -915,7 +915,7 @@ export function analyze_component(root, source, options) {
915
915
  // We need an empty style to generate the set_style() correctly
916
916
  if (!has_spread && !has_style && has_style_directive) {
917
917
  node.attributes.push(
918
- create_attribute('style', -1, -1, [
918
+ create_attribute('style', null, -1, -1, [
919
919
  {
920
920
  type: 'Text',
921
921
  data: '',
@@ -114,7 +114,8 @@ export function Identifier(node, context) {
114
114
  binding.initial.arguments[0].type !== 'SpreadElement' &&
115
115
  !should_proxy(binding.initial.arguments[0], context.state.scope)))) ||
116
116
  binding.kind === 'raw_state' ||
117
- binding.kind === 'derived') &&
117
+ binding.kind === 'derived' ||
118
+ binding.kind === 'prop') &&
118
119
  // We're only concerned with reads here
119
120
  (parent.type !== 'AssignmentExpression' || parent.left !== node) &&
120
121
  parent.type !== 'UpdateExpression'
@@ -48,8 +48,9 @@ export function RegularElement(node, context) {
48
48
  node.attributes.push(
49
49
  create_attribute(
50
50
  'value',
51
- /** @type {AST.Text} */ (node.fragment.nodes.at(0)).start,
52
- /** @type {AST.Text} */ (node.fragment.nodes.at(-1)).end,
51
+ null,
52
+ -1,
53
+ -1,
53
54
  // @ts-ignore
54
55
  node.fragment.nodes
55
56
  )
@@ -1,4 +1,3 @@
1
- /** @import { Location } from 'locate-character' */
2
1
  /** @import { Namespace } from '#compiler' */
3
2
  /** @import { ComponentClientTransformState } from '../types.js' */
4
3
  /** @import { Node } from './types.js' */
@@ -15,7 +14,7 @@ function build_locations(nodes) {
15
14
  for (const node of nodes) {
16
15
  if (node.type !== 'element') continue;
17
16
 
18
- const { line, column } = /** @type {Location} */ (locator(node.start));
17
+ const { line, column } = locator(node.start);
19
18
 
20
19
  const expression = b.array([b.literal(line), b.literal(column)]);
21
20
  const children = build_locations(node.children);
@@ -40,22 +40,38 @@ export function BindDirective(node, context) {
40
40
  validate_binding(context.state, node, expression);
41
41
  }
42
42
 
43
- get = b.thunk(expression);
43
+ const assignment = /** @type {Expression} */ (
44
+ context.visit(b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value')))
45
+ );
44
46
 
45
- /** @type {Expression | undefined} */
46
- set = b.unthunk(
47
- b.arrow(
47
+ if (dev) {
48
+ // in dev, create named functions, so that `$inspect(...)` delivers
49
+ // useful stack traces
50
+ get = b.function(b.id('get', node.name_loc), [], b.block([b.return(expression)]));
51
+ set = b.function(
52
+ b.id('set', node.name_loc),
48
53
  [b.id('$$value')],
49
- /** @type {Expression} */ (
50
- context.visit(
51
- b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value'))
54
+ b.block([b.stmt(assignment)])
55
+ );
56
+ } else {
57
+ // in prod, optimise for brevity
58
+ get = b.thunk(expression);
59
+
60
+ /** @type {Expression | undefined} */
61
+ set = b.unthunk(
62
+ b.arrow(
63
+ [b.id('$$value')],
64
+ /** @type {Expression} */ (
65
+ context.visit(
66
+ b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value'))
67
+ )
52
68
  )
53
69
  )
54
- )
55
- );
70
+ );
56
71
 
57
- if (get === set) {
58
- set = undefined;
72
+ if (get === set) {
73
+ set = undefined;
74
+ }
59
75
  }
60
76
  }
61
77
 
@@ -39,7 +39,8 @@ export function CallExpression(node, context) {
39
39
  }
40
40
  }
41
41
 
42
- return b.call('$.state', value);
42
+ const callee = b.id('$.state', node.callee.loc);
43
+ return b.call(callee, value);
43
44
  }
44
45
 
45
46
  case '$derived':
@@ -1,6 +1,5 @@
1
1
  /** @import { AST } from '#compiler' */
2
2
  /** @import { ComponentContext } from '../types' */
3
- import { regex_is_valid_identifier } from '../../../patterns.js';
4
3
  import { build_component } from './shared/component.js';
5
4
 
6
5
  /**
@@ -8,6 +7,6 @@ import { build_component } from './shared/component.js';
8
7
  * @param {ComponentContext} context
9
8
  */
10
9
  export function Component(node, context) {
11
- const component = build_component(node, node.name, context);
10
+ const component = build_component(node, node.name, node.name_loc, context);
12
11
  context.state.init.push(component);
13
12
  }
@@ -81,7 +81,7 @@ export function Fragment(node, context) {
81
81
  if (is_single_element) {
82
82
  const element = /** @type {AST.RegularElement} */ (trimmed[0]);
83
83
 
84
- const id = b.id(context.state.scope.generate(element.name));
84
+ const id = b.id(context.state.scope.generate(element.name), element.name_loc);
85
85
 
86
86
  context.visit(element, {
87
87
  ...state,
@@ -407,6 +407,7 @@ export function RegularElement(node, context) {
407
407
  const synthetic_node = node.metadata.synthetic_value_node;
408
408
  const synthetic_attribute = create_attribute(
409
409
  'value',
410
+ null,
410
411
  synthetic_node.start,
411
412
  synthetic_node.end,
412
413
  [synthetic_node]