svelte 5.56.9 → 5.56.10

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 (29) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/migrate/index.js +6 -1
  4. package/src/compiler/phases/1-parse/read/style.js +16 -6
  5. package/src/compiler/phases/2-analyze/index.js +19 -15
  6. package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +16 -3
  7. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +13 -2
  8. package/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js +0 -1
  9. package/src/compiler/phases/3-transform/css/index.js +5 -1
  10. package/src/compiler/phases/3-transform/server/visitors/LabeledStatement.js +1 -0
  11. package/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +4 -2
  12. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +48 -43
  13. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +1 -1
  14. package/src/compiler/print/index.js +74 -8
  15. package/src/internal/client/context.js +20 -53
  16. package/src/internal/client/dom/blocks/css-props.js +3 -3
  17. package/src/internal/client/dom/css.js +6 -1
  18. package/src/internal/client/dom/elements/attributes.js +2 -1
  19. package/src/internal/client/dom/elements/events.js +19 -1
  20. package/src/internal/client/reactivity/batch.js +1 -0
  21. package/src/internal/client/reactivity/sources.js +7 -1
  22. package/src/internal/server/context.js +8 -47
  23. package/src/internal/server/renderer.js +1 -0
  24. package/src/internal/shared/attributes.js +2 -1
  25. package/src/internal/shared/context.js +52 -0
  26. package/src/internal/shared/errors.js +2 -2
  27. package/src/version.js +1 -1
  28. package/types/index.d.ts +13 -9
  29. 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.56.9",
5
+ "version": "5.56.10",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -1590,7 +1590,12 @@ function migrate_slot_usage(node, path, state) {
1590
1590
  */
1591
1591
  function extract_type_and_comment(declarator, state, path) {
1592
1592
  const str = state.str;
1593
- const parent = path.at(-1);
1593
+ let parent = path.at(-1);
1594
+
1595
+ if (parent?.type === 'Program') {
1596
+ // We don't want comments from the program node
1597
+ parent = undefined;
1598
+ }
1594
1599
 
1595
1600
  // Try to find jsdoc above the declaration
1596
1601
  let comment_node = /** @type {Node} */ (parent)?.leadingComments?.at(-1);
@@ -7,8 +7,11 @@ const REGEX_CLOSING_BRACKET = /[\s\]]/;
7
7
  const REGEX_ATTRIBUTE_FLAGS = /[a-zA-Z]+/y; // only `i` and `s` are valid today, but make it future-proof
8
8
  const REGEX_COMBINATOR = /(\+|~|>|\|\|)/y;
9
9
  const REGEX_PERCENTAGE = /\d+(\.\d+)?%/y;
10
+ // `of` must be preceded by whitespace, otherwise it would be part of the `<an+b>` token
11
+ // (`2nof` is a single dimension token). It does not need to be followed by whitespace,
12
+ // because a `.`, `#`, `[`, `*`, `:` or `&` already ends the `of` identifier — minifiers rely on that
10
13
  const REGEX_NTH_OF =
11
- /(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of\s+)/y;
14
+ /(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of(\s+|(?=[.#[*:&])))/y;
12
15
  const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
13
16
  const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/y;
14
17
  const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
@@ -204,15 +207,18 @@ function read_selector(parser, inside_pseudo_class = false) {
204
207
  });
205
208
  } else if (parser.eat('*')) {
206
209
  let name = '*';
210
+ /** @type {string | undefined} */
211
+ let namespace;
207
212
 
208
213
  if (parser.eat('|')) {
209
- // * is the namespace (which we ignore)
210
- name = read_identifier(parser);
214
+ namespace = name;
215
+ name = parser.eat('*') ? '*' : read_identifier(parser);
211
216
  }
212
217
 
213
218
  relative_selector.selectors.push({
214
219
  type: 'TypeSelector',
215
220
  name,
221
+ ...(namespace !== undefined && { namespace }),
216
222
  start,
217
223
  end: parser.index
218
224
  });
@@ -314,15 +320,18 @@ function read_selector(parser, inside_pseudo_class = false) {
314
320
  });
315
321
  } else if (!parser.match_regex(REGEX_COMBINATOR)) {
316
322
  let name = read_identifier(parser);
323
+ /** @type {string | undefined} */
324
+ let namespace;
317
325
 
318
326
  if (parser.eat('|')) {
319
- // we ignore the namespace when trying to find matching element classes
320
- name = read_identifier(parser);
327
+ namespace = name;
328
+ name = parser.eat('*') ? '*' : read_identifier(parser);
321
329
  }
322
330
 
323
331
  relative_selector.selectors.push({
324
332
  type: 'TypeSelector',
325
333
  name,
334
+ ...(namespace !== undefined && { namespace }),
326
335
  start,
327
336
  end: parser.index
328
337
  });
@@ -614,7 +623,8 @@ function read_identifier(parser) {
614
623
  if (char === '\\') {
615
624
  const sequence = parser.match_regex(REGEX_UNICODE_SEQUENCE);
616
625
  if (sequence) {
617
- identifier += String.fromCodePoint(parseInt(sequence.slice(1), 16));
626
+ const character = String.fromCodePoint(parseInt(sequence.slice(1), 16));
627
+ identifier += character === '\\' ? '\\\\' : character;
618
628
  parser.index += sequence.length;
619
629
  } else {
620
630
  identifier += '\\' + parser.template[parser.index + 1];
@@ -958,7 +958,7 @@ function calculate_blockers(instance, analysis) {
958
958
  * @param {Set<Binding>} touched
959
959
  * @param {Set<ESTree.Node>} seen
960
960
  */
961
- const touch = (expression, scope, touched, seen = new Set()) => {
961
+ const touch = (expression, scope, touched, seen) => {
962
962
  if (seen.has(expression)) return;
963
963
  seen.add(expression);
964
964
 
@@ -1015,6 +1015,13 @@ function calculate_blockers(instance, analysis) {
1015
1015
  }
1016
1016
  }
1017
1017
 
1018
+ // Share seen nodes across calls so transitive assignments are only visited once.
1019
+ // Keep separate read/write state because the target sets can differ.
1020
+ /** @type {Set<ESTree.Node>} */
1021
+ const writes_seen = new Set();
1022
+ /** @type {Set<ESTree.Node>} */
1023
+ const reads_seen = new Set();
1024
+
1018
1025
  walk(
1019
1026
  node,
1020
1027
  { scope },
@@ -1044,13 +1051,7 @@ function calculate_blockers(instance, analysis) {
1044
1051
  const rune = get_rune(node, context.state.scope);
1045
1052
  if (rune === '$effect') return;
1046
1053
 
1047
- /** @type {Set<Binding>} */
1048
- const touched = new Set();
1049
- touch(node, context.state.scope, touched);
1050
-
1051
- for (const b of touched) {
1052
- writes.add(b);
1053
- }
1054
+ touch(node, context.state.scope, writes, writes_seen);
1054
1055
  },
1055
1056
  Identifier(node, context) {
1056
1057
  const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
@@ -1066,7 +1067,7 @@ function calculate_blockers(instance, analysis) {
1066
1067
  // might be called immediately, so we have to touch all references within it. Example:
1067
1068
  // function foo() { return () => blocker; } foo(); // blocker is touched
1068
1069
  if (node.argument) {
1069
- touch(node.argument, context.state.scope, reads);
1070
+ touch(node.argument, context.state.scope, reads, reads_seen);
1070
1071
  }
1071
1072
  },
1072
1073
  // don't look inside functions until they are called
@@ -1235,12 +1236,15 @@ function calculate_blockers(instance, analysis) {
1235
1236
  ? /** @type {ESTree.FunctionExpression | ESTree.ArrowFunctionExpression} */ (fn.init)
1236
1237
  : fn;
1237
1238
 
1238
- trace_references(
1239
- init.body,
1240
- reads_writes,
1241
- reads_writes,
1242
- /** @type {Scope} */ (instance.scopes.get(init))
1243
- );
1239
+ const fn_scope = /** @type {Scope} */ (instance.scopes.get(init));
1240
+
1241
+ if (init.body.type === 'BlockStatement') {
1242
+ trace_references(init.body, reads_writes, reads_writes, fn_scope);
1243
+ } else {
1244
+ // A concise arrow body is an implicit return, so treat it like the
1245
+ // `ReturnStatement` visitor in `trace_references` would.
1246
+ touch(init.body, fn_scope, reads_writes, new Set());
1247
+ }
1244
1248
 
1245
1249
  const max = [...reads_writes].reduce((max, binding) => {
1246
1250
  if (binding.blocker) {
@@ -1,4 +1,4 @@
1
- /** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
1
+ /** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, LogicalOperator, Pattern } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { Context } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
@@ -79,8 +79,13 @@ function build_assignment(operator, left, right, context) {
79
79
 
80
80
  // special case — assignment to private state field
81
81
  if (left.property.type === 'PrivateIdentifier') {
82
+ const logical_operator = ['||=', '&&=', '??='].includes(operator)
83
+ ? /** @type {LogicalOperator} */ (operator.slice(0, -1))
84
+ : null;
82
85
  let value = /** @type {Expression} */ (
83
- context.visit(build_assignment_value(operator, left, right))
86
+ context.visit(
87
+ logical_operator === null ? build_assignment_value(operator, left, right) : right
88
+ )
84
89
  );
85
90
 
86
91
  const needs_proxy =
@@ -88,7 +93,15 @@ function build_assignment(operator, left, right, context) {
88
93
  is_non_coercive_operator(operator) &&
89
94
  should_proxy(value, context.state.scope);
90
95
 
91
- return b.call('$.set', left, value, needs_proxy && b.true);
96
+ const assignment = b.call('$.set', left, value, needs_proxy && b.true);
97
+
98
+ return logical_operator === null
99
+ ? assignment
100
+ : b.logical(
101
+ logical_operator,
102
+ /** @type {Expression} */ (context.visit(left)),
103
+ assignment
104
+ );
92
105
  }
93
106
  }
94
107
  }
@@ -664,14 +664,25 @@ function build_element_attribute_update(element, node_id, name, value, attribute
664
664
  * @param {ComponentContext} context
665
665
  */
666
666
  function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
667
- const { value, has_state } = build_attribute_value(attribute.value, context);
667
+ const memoizer = new Memoizer();
668
+ const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
669
+ memoizer.add(value, metadata)
670
+ );
668
671
 
669
672
  // don't lowercase name, as we set the element's property, which might be case sensitive
670
673
  const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value);
671
674
 
672
675
  // this is different from other updates — it doesn't get grouped,
673
676
  // because set_custom_element_data may not be idempotent
674
- const update = has_state ? b.call('$.template_effect', b.thunk(call)) : call;
677
+ const update = has_state
678
+ ? b.call(
679
+ '$.template_effect',
680
+ b.arrow(memoizer.apply(), call),
681
+ memoizer.sync_values(),
682
+ memoizer.async_values(),
683
+ memoizer.blockers()
684
+ )
685
+ : call;
675
686
 
676
687
  context.state.init.push(b.stmt(update));
677
688
  }
@@ -13,7 +13,6 @@ export function UpdateExpression(node, context) {
13
13
 
14
14
  if (
15
15
  argument.type === 'MemberExpression' &&
16
- argument.object.type === 'ThisExpression' &&
17
16
  argument.property.type === 'PrivateIdentifier' &&
18
17
  context.state.state_fields.has('#' + argument.property.name)
19
18
  ) {
@@ -355,7 +355,11 @@ const visitors = {
355
355
  continue;
356
356
  }
357
357
 
358
- if (selector.type === 'TypeSelector' && selector.name === '*') {
358
+ if (
359
+ selector.type === 'TypeSelector' &&
360
+ selector.name === '*' &&
361
+ selector.namespace === undefined
362
+ ) {
359
363
  context.state.code.update(selector.start, selector.end, modifier);
360
364
  } else {
361
365
  context.state.code.appendLeft(selector.end, modifier);
@@ -8,6 +8,7 @@ import * as b from '#compiler/builders';
8
8
  */
9
9
  export function LabeledStatement(node, context) {
10
10
  if (context.state.analysis.runes || context.path.length > 1 || node.label.name !== '$') {
11
+ context.next();
11
12
  return;
12
13
  }
13
14
 
@@ -102,9 +102,11 @@ export function VariableDeclaration(node, context) {
102
102
  } else {
103
103
  const call = /** @type {CallExpression} */ (declarator.init);
104
104
 
105
- let rhs = value;
105
+ // - cannot be a SpreadElement because refused during analysis
106
+ // - use args[0] rather than value to avoid visiting twice (above in const value = ... and below in for-ofs)
107
+ let rhs = /** @type {Expression} */ (call.arguments[0]);
106
108
 
107
- if (rune !== '$derived' || call.arguments[0].type !== 'Identifier') {
109
+ if (rune === '$derived.by' || call.arguments[0].type !== 'Identifier') {
108
110
  const id = b.id(context.state.scope.generate('$$d'));
109
111
 
110
112
  rhs = b.call(id);
@@ -3,7 +3,7 @@
3
3
  /** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
4
4
  import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
5
5
  import { binding_properties } from '../../../../bindings.js';
6
- import { create_attribute, ExpressionMetadata, is_custom_element_node } from '../../../../nodes.js';
6
+ import { ExpressionMetadata, is_custom_element_node } from '../../../../nodes.js';
7
7
  import { regex_starts_with_newline } from '../../../../patterns.js';
8
8
  import * as b from '#compiler/builders';
9
9
  import {
@@ -21,6 +21,11 @@ import { escape_html } from '../../../../../../escaping.js';
21
21
 
22
22
  const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
23
23
 
24
+ /**
25
+ * @typedef {{ type: 'transformed', name: string, expression: Expression }} TransformedAttribute
26
+ * An attribute whose expression has already been transformed and must not be visited again.
27
+ */
28
+
24
29
  /**
25
30
  * Writes the output to the template output. Some elements may have attributes on them that require the
26
31
  * their output to be the child content instead. In this case, an object is returned.
@@ -29,7 +34,7 @@ const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
29
34
  * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
30
35
  */
31
36
  export function build_element_attributes(node, context, transform) {
32
- /** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
37
+ /** @type {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} */
33
38
  const attributes = [];
34
39
 
35
40
  /** @type {AST.ClassDirective[]} */
@@ -145,42 +150,26 @@ export function build_element_attributes(node, context, transform) {
145
150
  attr.value[0].data === 'checkbox'
146
151
  );
147
152
 
148
- attributes.push(
149
- create_attribute('checked', null, -1, -1, [
150
- {
151
- type: 'ExpressionTag',
152
- start: -1,
153
- end: -1,
154
- expression: is_checkbox
155
- ? b.call(
156
- b.member(attribute.expression, 'includes'),
157
- build_attribute_value(value_attribute.value, context, transform)
158
- )
159
- : b.binary(
160
- '===',
161
- attribute.expression,
162
- build_attribute_value(value_attribute.value, context, transform)
163
- ),
164
- metadata: {
165
- expression: new ExpressionMetadata()
166
- }
167
- }
168
- ])
169
- );
153
+ attributes.push({
154
+ type: 'transformed',
155
+ name: 'checked',
156
+ expression: is_checkbox
157
+ ? b.call(
158
+ b.member(expression, 'includes'),
159
+ build_attribute_value(value_attribute.value, context, transform)
160
+ )
161
+ : b.binary(
162
+ '===',
163
+ expression,
164
+ build_attribute_value(value_attribute.value, context, transform)
165
+ )
166
+ });
170
167
  } else {
171
- attributes.push(
172
- create_attribute(attribute.name, null, -1, -1, [
173
- {
174
- type: 'ExpressionTag',
175
- start: -1,
176
- end: -1,
177
- expression,
178
- metadata: {
179
- expression: new ExpressionMetadata()
180
- }
181
- }
182
- ])
183
- );
168
+ attributes.push({
169
+ type: 'transformed',
170
+ name: get_attribute_name(node, attribute),
171
+ expression
172
+ });
184
173
  }
185
174
  } else if (attribute.type === 'SpreadAttribute') {
186
175
  attributes.push(attribute);
@@ -217,7 +206,21 @@ export function build_element_attributes(node, context, transform) {
217
206
  } else {
218
207
  const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;
219
208
 
220
- for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
209
+ for (const attribute of /** @type {Array<AST.Attribute | TransformedAttribute>} */ (
210
+ attributes
211
+ )) {
212
+ if (attribute.type === 'transformed') {
213
+ context.state.template.push(
214
+ b.call(
215
+ '$.attr',
216
+ b.literal(attribute.name),
217
+ attribute.expression,
218
+ is_boolean_attribute(attribute.name) && b.true
219
+ )
220
+ );
221
+ continue;
222
+ }
223
+
221
224
  const name = get_attribute_name(node, attribute);
222
225
  const can_use_literal =
223
226
  (name !== 'class' || class_directives.length === 0) &&
@@ -298,14 +301,16 @@ function get_attribute_name(element, attribute) {
298
301
 
299
302
  /**
300
303
  * @param {AST.RegularElement | AST.SvelteElement} element
301
- * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
304
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
302
305
  * @param {ComponentContext} context
303
306
  * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
304
307
  */
305
308
  export function build_spread_object(element, attributes, context, transform) {
306
309
  const object = b.object(
307
310
  attributes.map((attribute) => {
308
- if (attribute.type === 'Attribute') {
311
+ if (attribute.type === 'transformed') {
312
+ return b.prop('init', b.key(attribute.name), attribute.expression);
313
+ } else if (attribute.type === 'Attribute') {
309
314
  const name = get_attribute_name(element, attribute);
310
315
  const value = build_attribute_value(
311
316
  attribute.value,
@@ -340,7 +345,7 @@ export function build_spread_object(element, attributes, context, transform) {
340
345
  /**
341
346
  *
342
347
  * @param {AST.RegularElement | AST.SvelteElement} element
343
- * @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
348
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} attributes
344
349
  * @param {AST.StyleDirective[]} style_directives
345
350
  * @param {AST.ClassDirective[]} class_directives
346
351
  * @param {ComponentContext} context
@@ -356,7 +361,7 @@ function build_element_spread_attributes(
356
361
  ) {
357
362
  const args = prepare_element_spread(
358
363
  element,
359
- /** @type {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} */ (attributes),
364
+ attributes,
360
365
  style_directives,
361
366
  class_directives,
362
367
  context,
@@ -410,7 +415,7 @@ export function prepare_element_spread_object(element, context, transform) {
410
415
  /**
411
416
  * Prepare args for $.attributes(...): compute object, css_hash, classes, styles and flags.
412
417
  * @param {AST.RegularElement | AST.SvelteElement} element
413
- * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
418
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
414
419
  * @param {AST.StyleDirective[]} style_directives
415
420
  * @param {AST.ClassDirective[]} class_directives
416
421
  * @param {ComponentContext} context
@@ -286,7 +286,7 @@ export function build_getter(node, state) {
286
286
  }
287
287
 
288
288
  if (binding.kind === 'derived') {
289
- return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(binding.node);
289
+ return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(node);
290
290
  }
291
291
 
292
292
  return node;
@@ -7,6 +7,68 @@ import { is_void } from '../../utils.js';
7
7
  /** Threshold for when content should be formatted on separate lines */
8
8
  const LINE_BREAK_THRESHOLD = 50;
9
9
 
10
+ /** Characters that are valid in a CSS identifier without escaping */
11
+ const REGEX_IDENTIFIER_CHAR = /^[a-zA-Z0-9_-]$/;
12
+
13
+ /** Hex digits — a backslash followed by one of these is read as a hex escape */
14
+ const REGEX_HEX_DIGIT = /[0-9a-fA-F]/;
15
+
16
+ /**
17
+ * Re-escape a CSS identifier name so that it prints as valid CSS.
18
+ *
19
+ * `parse` decodes CSS escape sequences when building the AST — `\31` becomes `1`,
20
+ * `\a` becomes a newline — but keeps single-character escapes such as `\.` and
21
+ * escaped backslashes intact. When printing we therefore only need to escape the
22
+ * characters that would be illegal in a bare identifier: a leading digit, `-`
23
+ * followed by a digit, whitespace and control characters, and anything else that
24
+ * is not already escaped.
25
+ * @param {string} name
26
+ */
27
+ function escape_identifier(name) {
28
+ let escaped = '';
29
+ let i = 0;
30
+
31
+ while (i < name.length) {
32
+ const char = name[i];
33
+
34
+ if (char === '\\') {
35
+ const next = name.charAt(i + 1);
36
+ if (next === '' || REGEX_HEX_DIGIT.test(next)) {
37
+ // A literal backslash in a name must itself be escaped: `\5c `
38
+ // re-parses to a backslash, whereas a backslash followed by a hex
39
+ // digit (or by nothing) would be read back as a hex escape.
40
+ escaped += '\\5c ';
41
+ i += 1;
42
+ continue;
43
+ }
44
+
45
+ // Already escaped — copy the backslash and the escaped character as-is.
46
+ escaped += '\\' + next;
47
+ i += 2;
48
+ continue;
49
+ }
50
+
51
+ const code = /** @type {number} */ (char.codePointAt(0));
52
+ const is_leading_digit = i === 0 && char >= '0' && char <= '9';
53
+ const is_leading_hyphen_digit =
54
+ i === 0 && char === '-' && name.charAt(i + 1) >= '0' && name.charAt(i + 1) <= '9';
55
+
56
+ if (
57
+ is_leading_digit ||
58
+ is_leading_hyphen_digit ||
59
+ !(REGEX_IDENTIFIER_CHAR.test(char) || code >= 160)
60
+ ) {
61
+ escaped += `\\${code.toString(16)} `;
62
+ } else {
63
+ escaped += char;
64
+ }
65
+
66
+ i += 1;
67
+ }
68
+
69
+ return escaped;
70
+ }
71
+
10
72
  /**
11
73
  * `print` converts a Svelte AST node back into Svelte source code.
12
74
  * It is primarily intended for tools that parse and transform components using the compiler’s modern AST representation.
@@ -324,7 +386,7 @@ function css_visitors(comments, js_comments) {
324
386
 
325
387
  return {
326
388
  Atrule(node, context) {
327
- context.write(`@${node.name}`);
389
+ context.write(`@${escape_identifier(node.name)}`);
328
390
 
329
391
  const prelude_end = node.block?.start ?? node.end;
330
392
  if (node.prelude || has_comment_before(prelude_end)) {
@@ -341,7 +403,7 @@ function css_visitors(comments, js_comments) {
341
403
  },
342
404
 
343
405
  AttributeSelector(node, context) {
344
- context.write(`[${node.name}`);
406
+ context.write(`[${escape_identifier(node.name)}`);
345
407
  if (node.matcher) {
346
408
  context.write(node.matcher);
347
409
  context.write(`"${node.value}"`);
@@ -365,7 +427,7 @@ function css_visitors(comments, js_comments) {
365
427
  },
366
428
 
367
429
  ClassSelector(node, context) {
368
- context.write(`.${node.name}`);
430
+ context.write(`.${escape_identifier(node.name)}`);
369
431
  },
370
432
 
371
433
  ComplexSelector(node, context) {
@@ -379,7 +441,7 @@ function css_visitors(comments, js_comments) {
379
441
  },
380
442
 
381
443
  IdSelector(node, context) {
382
- context.write(`#${node.name}`);
444
+ context.write(`#${escape_identifier(node.name)}`);
383
445
  },
384
446
 
385
447
  NestingSelector(node, context) {
@@ -395,7 +457,7 @@ function css_visitors(comments, js_comments) {
395
457
  },
396
458
 
397
459
  PseudoClassSelector(node, context) {
398
- context.write(`:${node.name}`);
460
+ context.write(`:${escape_identifier(node.name)}`);
399
461
 
400
462
  if (node.args) {
401
463
  context.write('(');
@@ -409,7 +471,7 @@ function css_visitors(comments, js_comments) {
409
471
  },
410
472
 
411
473
  PseudoElementSelector(node, context) {
412
- context.write(`::${node.name}`);
474
+ context.write(`::${escape_identifier(node.name)}`);
413
475
  if (node.args) {
414
476
  context.write('(');
415
477
  context.visit(node.args);
@@ -458,7 +520,11 @@ function css_visitors(comments, js_comments) {
458
520
  },
459
521
 
460
522
  TypeSelector(node, context) {
461
- context.write(node.name);
523
+ if (node.namespace !== undefined) {
524
+ context.write(node.namespace === '*' ? '*' : escape_identifier(node.namespace));
525
+ context.write('|');
526
+ }
527
+ context.write(node.name === '*' ? node.name : escape_identifier(node.name));
462
528
  }
463
529
  };
464
530
  }
@@ -701,7 +767,7 @@ const svelte_visitors = (comments, state) => ({
701
767
  }
702
768
 
703
769
  if (node.catch) {
704
- context.write(node.value ? 'catch ' : 'catch');
770
+ context.write(node.error ? 'catch ' : 'catch');
705
771
  if (node.error) context.visit(node.error);
706
772
  context.write('}');
707
773