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.
- package/compiler/index.js +1 -1
- package/package.json +1 -1
- package/src/compiler/migrate/index.js +6 -1
- package/src/compiler/phases/1-parse/read/style.js +16 -6
- package/src/compiler/phases/2-analyze/index.js +19 -15
- package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +16 -3
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +13 -2
- package/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js +0 -1
- package/src/compiler/phases/3-transform/css/index.js +5 -1
- package/src/compiler/phases/3-transform/server/visitors/LabeledStatement.js +1 -0
- package/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +4 -2
- package/src/compiler/phases/3-transform/server/visitors/shared/element.js +48 -43
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +1 -1
- package/src/compiler/print/index.js +74 -8
- package/src/internal/client/context.js +20 -53
- package/src/internal/client/dom/blocks/css-props.js +3 -3
- package/src/internal/client/dom/css.js +6 -1
- package/src/internal/client/dom/elements/attributes.js +2 -1
- package/src/internal/client/dom/elements/events.js +19 -1
- package/src/internal/client/reactivity/batch.js +1 -0
- package/src/internal/client/reactivity/sources.js +7 -1
- package/src/internal/server/context.js +8 -47
- package/src/internal/server/renderer.js +1 -0
- package/src/internal/shared/attributes.js +2 -1
- package/src/internal/shared/context.js +52 -0
- package/src/internal/shared/errors.js +2 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +13 -9
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
reads_writes,
|
|
1242
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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);
|
|
@@ -102,9 +102,11 @@ export function VariableDeclaration(node, context) {
|
|
|
102
102
|
} else {
|
|
103
103
|
const call = /** @type {CallExpression} */ (declarator.init);
|
|
104
104
|
|
|
105
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
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 === '
|
|
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
|
-
|
|
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)(
|
|
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
|
-
|
|
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.
|
|
770
|
+
context.write(node.error ? 'catch ' : 'catch');
|
|
705
771
|
if (node.error) context.visit(node.error);
|
|
706
772
|
context.write('}');
|
|
707
773
|
|