svelte 5.57.0 → 5.57.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.
Files changed (59) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +8 -8
  3. package/src/compiler/phases/1-parse/acorn.js +36 -2
  4. package/src/compiler/phases/1-parse/index.js +23 -22
  5. package/src/compiler/phases/1-parse/read/context.js +3 -18
  6. package/src/compiler/phases/1-parse/read/expression.js +110 -2
  7. package/src/compiler/phases/1-parse/read/script.js +1 -1
  8. package/src/compiler/phases/1-parse/read/style.js +3 -5
  9. package/src/compiler/phases/1-parse/state/element.js +68 -40
  10. package/src/compiler/phases/1-parse/state/tag.js +6 -6
  11. package/src/compiler/phases/1-parse/utils/html.js +2 -0
  12. package/src/compiler/phases/1-parse/utils/whitespace.js +18 -0
  13. package/src/compiler/phases/2-analyze/css/css-prune.js +7 -6
  14. package/src/compiler/phases/2-analyze/index.js +1 -1
  15. package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -1
  16. package/src/compiler/phases/2-analyze/visitors/ClassDeclaration.js +1 -1
  17. package/src/compiler/phases/2-analyze/visitors/FunctionDeclaration.js +1 -1
  18. package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -2
  19. package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +1 -1
  20. package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +1 -1
  21. package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +1 -1
  22. package/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +2 -2
  23. package/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js +1 -1
  24. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +1 -1
  25. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +0 -38
  26. package/src/compiler/phases/3-transform/client/utils.js +0 -38
  27. package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +2 -2
  28. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +1 -2
  29. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +3 -8
  30. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +8 -0
  31. package/src/compiler/phases/3-transform/utils.js +1 -1
  32. package/src/compiler/phases/nodes.js +30 -0
  33. package/src/compiler/phases/scope.js +77 -2
  34. package/src/compiler/state.js +9 -5
  35. package/src/compiler/utils/ast.js +2 -5
  36. package/src/compiler/utils/compile_diagnostic.js +1 -1
  37. package/src/compiler/utils/extract_svelte_ignore.js +1 -1
  38. package/src/compiler/utils/mapped_code.js +13 -1
  39. package/src/internal/client/constants.js +0 -9
  40. package/src/internal/client/dev/debug.js +0 -2
  41. package/src/internal/client/dom/elements/events.js +6 -1
  42. package/src/internal/client/errors.js +0 -16
  43. package/src/internal/client/proxy.js +12 -7
  44. package/src/internal/client/reactivity/batch.js +91 -71
  45. package/src/internal/client/reactivity/deriveds.js +0 -3
  46. package/src/internal/client/reactivity/sources.js +28 -16
  47. package/src/internal/client/reactivity/utils.js +2 -23
  48. package/src/internal/client/runtime.js +5 -9
  49. package/src/internal/server/context.js +9 -2
  50. package/src/internal/server/index.js +9 -2
  51. package/src/internal/server/renderer.js +101 -69
  52. package/src/internal/shared/errors.js +16 -0
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts.map +1 -1
  55. package/src/compiler/phases/1-parse/utils/create.js +0 -16
  56. package/src/compiler/phases/2-analyze/visitors/shared/special-element.js +0 -16
  57. package/src/compiler/utils/push_array.js +0 -13
  58. /package/src/compiler/{phases/2-analyze/utils → utils}/check_graph_for_cycles.js +0 -0
  59. /package/src/compiler/{phases/1-parse/utils → utils}/fuzzymatch.js +0 -0
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.57.0",
5
+ "version": "5.57.1",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -158,17 +158,17 @@
158
158
  "web-features": "^3.29.0"
159
159
  },
160
160
  "dependencies": {
161
- "@jridgewell/remapping": "^2.3.4",
162
- "@jridgewell/sourcemap-codec": "^1.5.0",
163
- "@sveltejs/acorn-typescript": "^1.0.10",
164
- "@types/estree": "^1.0.5",
165
- "acorn": "^8.12.1",
161
+ "@jridgewell/remapping": "^2.3.5",
162
+ "@jridgewell/sourcemap-codec": "^1.6.0",
163
+ "@sveltejs/acorn-typescript": "^1.0.13",
164
+ "@types/estree": "^1.0.9",
165
+ "acorn": "^8.18.0",
166
166
  "aria-query": "5.3.1",
167
167
  "axobject-query": "^4.1.0",
168
168
  "clsx": "^2.1.1",
169
- "devalue": "^5.8.1",
169
+ "devalue": "^5.9.2",
170
170
  "esm-env": "^1.2.1",
171
- "esrap": "^2.2.12",
171
+ "esrap": "^2.3.6",
172
172
  "is-reference": "^3.0.3",
173
173
  "locate-character": "^3.0.0",
174
174
  "magic-string": "^0.30.11",
@@ -5,6 +5,7 @@ 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 { locator } from '../../state.js';
8
9
 
9
10
  const JSParser = acorn.Parser;
10
11
  const TSParser = JSParser.extend(tsPlugin());
@@ -87,7 +88,8 @@ export function parse_expression_at(parser, source, index) {
87
88
  sourceType: 'module',
88
89
  ecmaVersion: 16,
89
90
  locations: true,
90
- preserveParens: true
91
+ preserveParens: true,
92
+ startLocation: start_location(parser, index)
91
93
  });
92
94
 
93
95
  add_comments(ast);
@@ -112,7 +114,13 @@ export function parse_statement_at(parser, source, index) {
112
114
  try {
113
115
  // This is like parseExpressionAt but for statements
114
116
  const p = new acorn(
115
- { onComment, sourceType: 'module', ecmaVersion: 16, locations: true },
117
+ {
118
+ onComment,
119
+ sourceType: 'module',
120
+ ecmaVersion: 16,
121
+ locations: true,
122
+ startLocation: start_location(parser, index)
123
+ },
116
124
  source,
117
125
  index
118
126
  );
@@ -128,6 +136,32 @@ export function parse_statement_at(parser, source, index) {
128
136
  }
129
137
  }
130
138
 
139
+ const regex_non_lf_line_break = /\r(?!\n)|[\u2028\u2029]/;
140
+
141
+ let last_template = '';
142
+ let lf_only = true;
143
+
144
+ /**
145
+ * Without `startLocation`, acorn counts the lines before `index` on every call
146
+ * @param {Parser} parser
147
+ * @param {number} index
148
+ */
149
+ function start_location(parser, index) {
150
+ return has_lf_line_breaks_only(parser) ? locator(index) : undefined;
151
+ }
152
+
153
+ /**
154
+ * acorn breaks lines on bare `\r`, `\u2028` and `\u2029`, which the locator doesn't
155
+ * @param {Parser} parser
156
+ */
157
+ export function has_lf_line_breaks_only(parser) {
158
+ if (parser.template !== last_template) {
159
+ last_template = parser.template;
160
+ lf_only = !regex_non_lf_line_break.test(last_template);
161
+ }
162
+ return lf_only;
163
+ }
164
+
131
165
  const regex_position_indicator = / \(\d+:\d+\)$/;
132
166
 
133
167
  /**
@@ -5,30 +5,11 @@
5
5
  import { isIdentifierStart, isIdentifierChar } from 'acorn';
6
6
  import fragment from './state/fragment.js';
7
7
  import * as e from '../../errors.js';
8
- import { create_fragment } from './utils/create.js';
8
+ import { create_fragment, disallow_children } from '../nodes.js';
9
9
  import read_options from './read/options.js';
10
10
  import { is_reserved } from '../../../utils.js';
11
- import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
12
11
  import * as state from '../../state.js';
13
-
14
- /** @param {number} cc */
15
- function is_whitespace(cc) {
16
- // fast path for common whitespace
17
- if (cc === 32 || (cc <= 13 && cc >= 9)) return true;
18
- // rare whitespace — \u00a0, \u1680, \u2000-\u200a, \u2028, \u2029, \u202f, \u205f, \u3000, \ufeff
19
- if (cc < 160) return false;
20
- return (
21
- cc === 160 ||
22
- cc === 5760 ||
23
- (cc >= 8192 && cc <= 8202) ||
24
- cc === 8232 ||
25
- cc === 8233 ||
26
- cc === 8239 ||
27
- cc === 8287 ||
28
- cc === 12288 ||
29
- cc === 65279
30
- );
31
- }
12
+ import { is_whitespace } from './utils/whitespace.js';
32
13
 
33
14
  const regex_lang_attribute =
34
15
  /<!--[^]*?-->|<script\s+(?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g;
@@ -278,8 +259,27 @@ export class Parser {
278
259
  };
279
260
  }
280
261
 
262
+ /** @param {string} delimiter */
263
+ read_until(delimiter) {
264
+ if (this.index >= this.template.length) {
265
+ if (this.loose) return '';
266
+ e.unexpected_eof(this.template.length);
267
+ }
268
+
269
+ const start = this.index;
270
+ const index = this.template.indexOf(delimiter, start);
271
+
272
+ if (index !== -1) {
273
+ this.index = index;
274
+ return this.template.slice(start, this.index);
275
+ }
276
+
277
+ this.index = this.template.length;
278
+ return this.template.slice(start);
279
+ }
280
+
281
281
  /** @param {RegExp} pattern */
282
- read_until(pattern) {
282
+ read_until_regex(pattern) {
283
283
  if (this.index >= this.template.length) {
284
284
  if (this.loose) return '';
285
285
  e.unexpected_eof(this.template.length);
@@ -302,6 +302,7 @@ export class Parser {
302
302
  e.expected_whitespace(this.index);
303
303
  }
304
304
 
305
+ this.index++;
305
306
  this.allow_whitespace();
306
307
  }
307
308
 
@@ -2,7 +2,6 @@
2
2
  /** @import { Parser } from '../index.js' */
3
3
  import { match_bracket } from '../utils/bracket.js';
4
4
  import { parse_expression_at, remove_parens } from '../acorn.js';
5
- import { regex_not_newline_characters } from '../../patterns.js';
6
5
  import * as e from '../../../errors.js';
7
6
 
8
7
  /**
@@ -33,24 +32,10 @@ export default function read_pattern(parser) {
33
32
  i = match_bracket(parser, start);
34
33
  parser.index = i;
35
34
 
36
- const pattern_string = parser.template.slice(start, i);
37
-
38
- // the length of the `space_with_newline` has to be start - 1
39
- // because we added a `(` in front of the pattern_string,
40
- // which shifted the entire string to right by 1
41
- // so we offset it by removing 1 character in the `space_with_newline`
42
- // to achieve that, we remove the 1st space encountered,
43
- // so it will not affect the `column` of the node
44
- let space_with_newline = parser.template
45
- .slice(0, start)
46
- .replace(regex_not_newline_characters, ' ');
47
- const first_space = space_with_newline.indexOf(' ');
48
- space_with_newline =
49
- space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
50
-
35
+ // acorn never reads before `start`, so the template itself can serve as the prefix
51
36
  /** @type {any} */
52
37
  let expression = remove_parens(
53
- parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1)
38
+ parse_expression_at(parser, parser.template.slice(0, i) + ' = 1', start)
54
39
  );
55
40
 
56
41
  expression = expression.left;
@@ -80,7 +65,7 @@ function read_type_annotation(parser) {
80
65
  const insert = '_ as ';
81
66
  let a = parser.index - insert.length;
82
67
  const template =
83
- parser.template.slice(0, a).replace(/[^\n]/g, ' ') +
68
+ parser.template.slice(0, a) +
84
69
  insert +
85
70
  // If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
86
71
  // parameters as part of a sequence expression instead, and will then error on optional
@@ -1,9 +1,13 @@
1
- /** @import { Expression } from 'estree' */
1
+ /** @import { Expression, Identifier } from 'estree' */
2
2
  /** @import { Parser } from '../index.js' */
3
- import { parse_expression_at, remove_parens } from '../acorn.js';
3
+ // @ts-expect-error acorn type definitions are borked in the release we use
4
+ import { isIdentifierStart, isIdentifierChar } from 'acorn';
5
+ import { has_lf_line_breaks_only, parse_expression_at, remove_parens } from '../acorn.js';
4
6
  import { regex_whitespace } from '../../patterns.js';
5
7
  import * as e from '../../../errors.js';
6
8
  import { find_matching_bracket } from '../utils/bracket.js';
9
+ import { is_reserved } from '../../../../utils.js';
10
+ import { locator } from '../../../state.js';
7
11
 
8
12
  /**
9
13
  * @param {Parser} parser
@@ -33,6 +37,9 @@ export function get_loose_identifier(parser, opening_token) {
33
37
  * @returns {Expression}
34
38
  */
35
39
  export default function read_expression(parser, opening_token, disallow_loose) {
40
+ const simple = read_simple_expression(parser);
41
+ if (simple) return simple;
42
+
36
43
  try {
37
44
  const node = parse_expression_at(parser, parser.template, parser.index);
38
45
 
@@ -57,3 +64,104 @@ export default function read_expression(parser, opening_token, disallow_loose) {
57
64
  throw err;
58
65
  }
59
66
  }
67
+
68
+ /**
69
+ * Most template expressions are an identifier or a `a.b.c` member chain followed by `}`.
70
+ * Those are built directly for better parse performance, with the same shape acorn would produce; anything else goes to acorn
71
+ * @param {Parser} parser
72
+ * @returns {Expression | null}
73
+ */
74
+ function read_simple_expression(parser) {
75
+ if (!has_lf_line_breaks_only(parser)) return null;
76
+
77
+ const template = parser.template;
78
+ const index = parser.index;
79
+
80
+ parser.allow_whitespace();
81
+ const start = parser.index;
82
+
83
+ let end = read_word(template, start);
84
+ if (end === -1 || is_reserved(template.slice(start, end))) {
85
+ parser.index = index;
86
+ return null;
87
+ }
88
+
89
+ /** @type {Expression} */
90
+ let node = identifier(template, start, end);
91
+
92
+ while (template[end] === '.') {
93
+ const property_end = read_word(template, end + 1);
94
+ if (property_end === -1) {
95
+ parser.index = index;
96
+ return null;
97
+ }
98
+
99
+ node = {
100
+ type: 'MemberExpression',
101
+ start,
102
+ end: property_end,
103
+ loc: { start: position(start), end: position(property_end) },
104
+ object: node,
105
+ property: identifier(template, end + 1, property_end),
106
+ computed: false,
107
+ optional: false
108
+ };
109
+
110
+ end = property_end;
111
+ }
112
+
113
+ parser.index = end;
114
+ parser.allow_whitespace();
115
+
116
+ if (!parser.match('}')) {
117
+ parser.index = index;
118
+ return null;
119
+ }
120
+
121
+ parser.index = end;
122
+ return node;
123
+ }
124
+
125
+ /**
126
+ * @param {string} template
127
+ * @param {number} start
128
+ * @returns {number} the end of the identifier starting at `start`, or -1
129
+ */
130
+ function read_word(template, start) {
131
+ if (start >= template.length) return -1;
132
+
133
+ const code = /** @type {number} */ (template.codePointAt(start));
134
+ if (!isIdentifierStart(code, true)) return -1;
135
+
136
+ let end = start + (code <= 0xffff ? 1 : 2);
137
+
138
+ while (end < template.length) {
139
+ const code = /** @type {number} */ (template.codePointAt(end));
140
+ if (!isIdentifierChar(code, true)) break;
141
+ end += code <= 0xffff ? 1 : 2;
142
+ }
143
+
144
+ return end;
145
+ }
146
+
147
+ /**
148
+ * @param {string} template
149
+ * @param {number} start
150
+ * @param {number} end
151
+ * @returns {Identifier}
152
+ */
153
+ function identifier(template, start, end) {
154
+ return {
155
+ type: 'Identifier',
156
+ start,
157
+ end,
158
+ loc: { start: position(start), end: position(end) },
159
+ name: template.slice(start, end)
160
+ };
161
+ }
162
+
163
+ /** @param {number} index */
164
+ function position(index) {
165
+ const { line, column } = locator(index);
166
+ return { line, column };
167
+ }
@@ -22,7 +22,7 @@ const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
22
22
  */
23
23
  export function read_script(parser, start, attributes) {
24
24
  const script_start = parser.index;
25
- const data = parser.read_until(regex_closing_script_tag);
25
+ const data = parser.read_until_regex(regex_closing_script_tag);
26
26
  if (parser.index >= parser.template.length) {
27
27
  e.element_unclosed(parser.template.length, 'script');
28
28
  }
@@ -16,8 +16,6 @@ const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
16
16
  const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/y;
17
17
  const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
18
18
  const REGEX_UNICODE_SEQUENCE = /\\[0-9a-fA-F]{1,6}(\r\n|\s)?/y;
19
- const REGEX_COMMENT_CLOSE = /\*\//;
20
- const REGEX_HTML_COMMENT_CLOSE = /-->/;
21
19
 
22
20
  /**
23
21
  * @param {Parser} parser
@@ -478,7 +476,7 @@ function read_block_item(parser) {
478
476
  function read_declaration(parser) {
479
477
  const start = parser.index;
480
478
 
481
- const property = parser.read_until(REGEX_WHITESPACE_OR_COLON);
479
+ const property = parser.read_until_regex(REGEX_WHITESPACE_OR_COLON);
482
480
  parser.allow_whitespace();
483
481
  parser.eat(':');
484
482
  let index = parser.index;
@@ -661,7 +659,7 @@ function allow_comment_or_whitespace(parser, capture_comments = true) {
661
659
  }
662
660
 
663
661
  if (parser.eat('<!--')) {
664
- parser.read_until(REGEX_HTML_COMMENT_CLOSE);
662
+ parser.read_until('-->');
665
663
  parser.eat('-->', true);
666
664
  }
667
665
 
@@ -676,7 +674,7 @@ function allow_comment_or_whitespace(parser, capture_comments = true) {
676
674
  function read_comment(parser) {
677
675
  const start = parser.index;
678
676
  parser.eat('/*', true);
679
- const value = parser.read_until(REGEX_COMMENT_CLOSE);
677
+ const value = parser.read_until('*/');
680
678
  parser.eat('*/', true);
681
679
  const end = parser.index;
682
680
 
@@ -9,19 +9,20 @@ import read_style from '../read/style.js';
9
9
  import { decode_character_references } from '../utils/html.js';
10
10
  import * as e from '../../../errors.js';
11
11
  import * as w from '../../../warnings.js';
12
- import { create_fragment } from '../utils/create.js';
13
- import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js';
12
+ import {
13
+ create_attribute,
14
+ create_fragment,
15
+ ExpressionMetadata,
16
+ is_element_node
17
+ } from '../../nodes.js';
14
18
  import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
15
19
  import { closing_tag_omitted } from '../../../../html-tree-validation.js';
16
20
  import { list } from '../../../utils/string.js';
17
21
  import { locator } from '../../../state.js';
18
- import * as b from '#compiler/builders';
22
+ import { is_whitespace } from '../utils/whitespace.js';
19
23
 
20
24
  const regex_invalid_unquoted_attribute_value = /(\/>|[\s"'=<>`])/y;
21
25
  const regex_closing_textarea_tag = /<\/textarea(\s[^>]*)?>/iy;
22
- const regex_closing_comment = /-->/;
23
- const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
24
- const regex_token_ending_character = /[\s=/>"']/;
25
26
  const regex_starts_with_quote_characters = /["']/y;
26
27
  const regex_attribute_value = /(?:"([^"]*)"|'([^'])*'|([^>\s]+))/y;
27
28
  const regex_doctype_name = /^![a-zA-Z]+$/;
@@ -67,7 +68,7 @@ export default function element(parser) {
67
68
  let parent = parser.current();
68
69
 
69
70
  if (parser.eat('!--')) {
70
- const data = parser.read_until(regex_closing_comment);
71
+ const data = parser.read_until('-->');
71
72
  parser.eat('-->', true);
72
73
 
73
74
  parser.append({
@@ -81,7 +82,7 @@ export default function element(parser) {
81
82
  }
82
83
 
83
84
  if (parser.eat('/')) {
84
- const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
85
+ const name = read_tag_name(parser);
85
86
 
86
87
  parser.allow_whitespace();
87
88
  parser.eat('>', true);
@@ -137,7 +138,7 @@ export default function element(parser) {
137
138
  return;
138
139
  }
139
140
 
140
- const tag = read_tag(parser, regex_whitespace_or_slash_or_closing_tag);
141
+ const tag = read_tag(parser);
141
142
 
142
143
  if (tag.name.startsWith('svelte:') && !meta_tags.has(tag.name)) {
143
144
  const bounds = { start: start + 1, end: start + 1 + tag.name.length };
@@ -475,7 +476,7 @@ function parent_is_shadowroot_template(stack) {
475
476
  function read_static_attribute(parser) {
476
477
  const start = parser.index;
477
478
 
478
- const tag = read_tag(parser, regex_token_ending_character);
479
+ const tag = read_tag(parser, true);
479
480
  if (!tag.name) return null;
480
481
 
481
482
  /** @type {true | Array<AST.Text | AST.ExpressionTag>} */
@@ -607,7 +608,7 @@ function read_attribute(parser) {
607
608
  }
608
609
  }
609
610
 
610
- const tag = read_tag(parser, regex_token_ending_character);
611
+ const tag = read_tag(parser, true);
611
612
 
612
613
  if (!tag.name) return null;
613
614
 
@@ -731,7 +732,7 @@ function read_comment(parser) {
731
732
  const start = parser.index;
732
733
 
733
734
  if (parser.eat('//')) {
734
- const value = parser.read_until(/\n/);
735
+ const value = parser.read_until('\n');
735
736
  const end = parser.index;
736
737
 
737
738
  return {
@@ -747,7 +748,7 @@ function read_comment(parser) {
747
748
  }
748
749
 
749
750
  if (parser.eat('/*')) {
750
- const value = parser.read_until(/\*\//);
751
+ const value = parser.read_until('*/');
751
752
 
752
753
  parser.eat('*/');
753
754
  const end = parser.index;
@@ -847,25 +848,21 @@ function read_attribute_value(parser) {
847
848
  * @returns {any[]}
848
849
  */
849
850
  function read_sequence(parser, done, location) {
850
- /** @type {AST.Text} */
851
- let current_chunk = {
852
- start: parser.index,
853
- end: -1,
854
- type: 'Text',
855
- raw: '',
856
- data: ''
857
- };
858
-
859
851
  /** @type {Array<AST.Text | AST.ExpressionTag>} */
860
852
  const chunks = [];
853
+ let chunk_start = parser.index;
861
854
 
862
855
  /** @param {number} end */
863
856
  function flush(end) {
864
- if (end > current_chunk.start) {
865
- current_chunk.raw = parser.template.slice(current_chunk.start, end);
866
- current_chunk.data = decode_character_references(current_chunk.raw, true);
867
- current_chunk.end = end;
868
- chunks.push(current_chunk);
857
+ if (end > chunk_start) {
858
+ const raw = parser.template.slice(chunk_start, end);
859
+ chunks.push({
860
+ start: chunk_start,
861
+ end,
862
+ type: 'Text',
863
+ raw,
864
+ data: decode_character_references(raw, true)
865
+ });
869
866
  }
870
867
  }
871
868
 
@@ -879,12 +876,14 @@ function read_sequence(parser, done, location) {
879
876
  if (parser.match('#')) {
880
877
  const index = parser.index - 1;
881
878
  parser.eat('#');
882
- const name = parser.read_until(/[^a-z]/);
879
+ // const name = parser.read_until_regex(/[^a-z]/);
880
+ const name = read_lowercase_name(parser);
883
881
  e.block_invalid_placement(index, name, location);
884
882
  } else if (parser.match('@')) {
885
883
  const index = parser.index - 1;
886
884
  parser.eat('@');
887
- const name = parser.read_until(/[^a-z]/);
885
+ // const name = parser.read_until_regex(/[^a-z]/);
886
+ const name = read_lowercase_name(parser);
888
887
  e.tag_invalid_placement(index, name, location);
889
888
  }
890
889
 
@@ -907,14 +906,7 @@ function read_sequence(parser, done, location) {
907
906
  };
908
907
 
909
908
  chunks.push(chunk);
910
-
911
- current_chunk = {
912
- start: parser.index,
913
- end: -1,
914
- type: 'Text',
915
- raw: '',
916
- data: ''
917
- };
909
+ chunk_start = parser.index;
918
910
  } else {
919
911
  parser.index++;
920
912
  }
@@ -929,12 +921,36 @@ function read_sequence(parser, done, location) {
929
921
 
930
922
  /**
931
923
  * @param {Parser} parser
932
- * @param {RegExp} regex
924
+ * @param {boolean} [attribute]
925
+ */
926
+ function read_tag_name(parser, attribute = false) {
927
+ const start = parser.index;
928
+ if (start >= parser.template.length && !parser.loose) e.unexpected_eof(parser.template.length);
929
+
930
+ while (parser.index < parser.template.length) {
931
+ const cc = parser.template.charCodeAt(parser.index);
932
+ if (
933
+ is_whitespace(cc) ||
934
+ cc === 47 || // /
935
+ cc === 62 || // >
936
+ (attribute && (cc === 34 || cc === 39 || cc === 61)) // " ' =
937
+ ) {
938
+ break;
939
+ }
940
+ parser.index += 1;
941
+ }
942
+
943
+ return parser.template.slice(start, parser.index);
944
+ }
945
+
946
+ /**
947
+ * @param {Parser} parser
948
+ * @param {boolean} [attribute]
933
949
  * @returns {Identifier & { start: number, end: number, loc: SourceLocation }}
934
950
  */
935
- function read_tag(parser, regex) {
951
+ function read_tag(parser, attribute = false) {
936
952
  const start = parser.index;
937
- const name = parser.read_until(regex);
953
+ const name = read_tag_name(parser, attribute);
938
954
  const end = parser.index;
939
955
 
940
956
  return {
@@ -948,3 +964,15 @@ function read_tag(parser, regex) {
948
964
  }
949
965
  };
950
966
  }
967
+
968
+ /** @param {Parser} parser */
969
+ function read_lowercase_name(parser) {
970
+ const start = parser.index;
971
+ while (parser.index < parser.template.length) {
972
+ const cc = parser.template.charCodeAt(parser.index);
973
+ // a-z
974
+ if (cc < 97 || cc > 122) break;
975
+ parser.index += 1;
976
+ }
977
+ return parser.template.slice(start, parser.index);
978
+ }
@@ -3,11 +3,10 @@
3
3
  /** @import { Parser } from '../index.js' */
4
4
  import { walk } from 'zimmerframe';
5
5
  import * as e from '../../../errors.js';
6
- import { ExpressionMetadata } from '../../nodes.js';
6
+ import { create_fragment, ExpressionMetadata } from '../../nodes.js';
7
7
  import { parse_expression_at, parse_statement_at } from '../acorn.js';
8
8
  import read_pattern from '../read/context.js';
9
9
  import read_expression, { get_loose_identifier } from '../read/expression.js';
10
- import { create_fragment } from '../utils/create.js';
11
10
  import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
12
11
 
13
12
  const regex_whitespace_with_closing_curly_brace = /\s*}/y;
@@ -488,12 +487,13 @@ function open(parser) {
488
487
  parser.eat(')', true);
489
488
  }
490
489
 
491
- const prelude = parser.template.slice(0, params_start).replace(/\S/g, ' ');
492
- const params = parser.template.slice(params_start, parser.index);
493
-
494
490
  let function_expression = matched
495
491
  ? /** @type {ArrowFunctionExpression} */ (
496
- parse_expression_at(parser, prelude + `${params} => {}`, params_start)
492
+ parse_expression_at(
493
+ parser,
494
+ parser.template.slice(0, parser.index) + ' => {}',
495
+ params_start
496
+ )
497
497
  )
498
498
  : { params: [] };
499
499
 
@@ -38,6 +38,8 @@ const entity_pattern_attr_value = get_entity_pattern(true);
38
38
  * @param {boolean} is_attribute_value
39
39
  */
40
40
  export function decode_character_references(html, is_attribute_value) {
41
+ if (html.indexOf('&') === -1) return html; // fast path
42
+
41
43
  const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content;
42
44
  return html.replace(
43
45
  entity_pattern,
@@ -0,0 +1,18 @@
1
+ /** @param {number} cc */
2
+ export function is_whitespace(cc) {
3
+ // fast path for common whitespace
4
+ if (cc === 32 || (cc <= 13 && cc >= 9)) return true;
5
+ // rare whitespace — \u00a0, \u1680, \u2000-\u200a, \u2028, \u2029, \u202f, \u205f, \u3000, \ufeff
6
+ if (cc < 160) return false;
7
+ return (
8
+ cc === 160 ||
9
+ cc === 5760 ||
10
+ (cc >= 8192 && cc <= 8202) ||
11
+ cc === 8232 ||
12
+ cc === 8233 ||
13
+ cc === 8239 ||
14
+ cc === 8287 ||
15
+ cc === 12288 ||
16
+ cc === 65279
17
+ );
18
+ }
@@ -145,6 +145,12 @@ export function prune(stylesheet, elements) {
145
145
  },
146
146
  ComplexSelector(node) {
147
147
  const selectors = get_relative_selectors(node);
148
+ const rule = /** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule);
149
+
150
+ // Global and ICSS export rules do not depend on an element in this component
151
+ if (every_is_global(selectors, 0, selectors.length, rule)) {
152
+ node.metadata.used = true;
153
+ }
148
154
 
149
155
  for (const element of elements) {
150
156
  seen.clear();
@@ -153,12 +159,7 @@ export function prune(stylesheet, elements) {
153
159
  // Elements rendered through <svelte:head> are not style-scopable.
154
160
  // Prevent css hash injection (class="s-...") on tags like <meta>, <link>, <script>.
155
161
  !is_inside_svelte_head(element) &&
156
- apply_selector(
157
- selectors,
158
- /** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule),
159
- element,
160
- BACKWARD
161
- )
162
+ apply_selector(selectors, rule, element, BACKWARD)
162
163
  ) {
163
164
  node.metadata.used = true;
164
165
  }