Skip to content

Commit cc2b888

Browse files
committed
merge main
2 parents ab0ec6f + e40e9eb commit cc2b888

File tree

24 files changed

+384
-71
lines changed

24 files changed

+384
-71
lines changed

documentation/docs/98-reference/.generated/compile-errors.md

+6
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ A `:global` selector cannot modify an existing selector
274274
A `:global` selector can only be modified if it is a descendant of other selectors
275275
```
276276

277+
### css_global_block_invalid_placement
278+
279+
```
280+
A `:global` selector cannot be inside a pseudoclass
281+
```
282+
277283
### css_global_invalid_placement
278284

279285
```

packages/svelte/CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# svelte
22

3+
## 5.28.1
4+
5+
### Patch Changes
6+
7+
- fix: ensure `<svelte:boundary>` properly removes error content in production mode ([#15793](https://github.com/sveltejs/svelte/pull/15793))
8+
9+
- fix: `update_version` after `delete` if `source` is `undefined` and `prop` in `target` ([#15796](https://github.com/sveltejs/svelte/pull/15796))
10+
11+
- fix: emit error on wrong placement of the `:global` block selector ([#15794](https://github.com/sveltejs/svelte/pull/15794))
12+
13+
## 5.28.0
14+
15+
### Minor Changes
16+
17+
- feat: partially evaluate more expressions ([#15781](https://github.com/sveltejs/svelte/pull/15781))
18+
319
## 5.27.3
420

521
### Patch Changes

packages/svelte/messages/compile-errors/style.md

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ x y {
5050

5151
> A `:global` selector can only be modified if it is a descendant of other selectors
5252
53+
## css_global_block_invalid_placement
54+
55+
> A `:global` selector cannot be inside a pseudoclass
56+
5357
## css_global_invalid_placement
5458

5559
> `:global(...)` can be at the start or end of a selector sequence, but not in the middle

packages/svelte/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.27.3",
5+
"version": "5.28.1",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/compiler/errors.js

+9
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,15 @@ export function css_global_block_invalid_modifier_start(node) {
599599
e(node, 'css_global_block_invalid_modifier_start', `A \`:global\` selector can only be modified if it is a descendant of other selectors\nhttps://svelte.dev/e/css_global_block_invalid_modifier_start`);
600600
}
601601

602+
/**
603+
* A `:global` selector cannot be inside a pseudoclass
604+
* @param {null | number | NodeLike} node
605+
* @returns {never}
606+
*/
607+
export function css_global_block_invalid_placement(node) {
608+
e(node, 'css_global_block_invalid_placement', `A \`:global\` selector cannot be inside a pseudoclass\nhttps://svelte.dev/e/css_global_block_invalid_placement`);
609+
}
610+
602611
/**
603612
* `:global(...)` can be at the start or end of a selector sequence, but not in the middle
604613
* @param {null | number | NodeLike} node

packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ const css_visitors = {
6868
const global = node.children.find(is_global);
6969

7070
if (global) {
71-
const idx = node.children.indexOf(global);
71+
const is_nested = context.path.at(-2)?.type === 'PseudoClassSelector';
72+
if (is_nested && !global.selectors[0].args) {
73+
e.css_global_block_invalid_placement(global.selectors[0]);
74+
}
7275

76+
const idx = node.children.indexOf(global);
7377
if (global.selectors[0].args !== null && idx !== 0 && idx !== node.children.length - 1) {
7478
// ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok)
7579
for (let i = idx + 1; i < node.children.length; i++) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,18 @@ export function build_template_chunk(
7777
node.metadata.expression
7878
);
7979

80+
const evaluated = state.scope.evaluate(value);
81+
8082
has_await ||= node.metadata.expression.has_await;
81-
has_state ||= has_await || node.metadata.expression.has_state;
83+
has_state ||= has_await || (node.metadata.expression.has_state && !evaluated.is_known);
8284

8385
if (values.length === 1) {
8486
// If we have a single expression, then pass that in directly to possibly avoid doing
8587
// extra work in the template_effect (instead we do the work in set_text).
88+
if (evaluated.is_known) {
89+
value = b.literal(evaluated.value);
90+
}
91+
8692
return { value, has_state };
8793
}
8894

@@ -98,8 +104,6 @@ export function build_template_chunk(
98104
}
99105
}
100106

101-
const evaluated = state.scope.evaluate(value);
102-
103107
if (evaluated.is_known) {
104108
quasi.value.cooked += evaluated.value + '';
105109
} else {

packages/svelte/src/compiler/phases/scope.js

+191-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { ArrowFunctionExpression, BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator } from 'estree' */
1+
/** @import { ArrowFunctionExpression, BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator, Super } from 'estree' */
22
/** @import { Context, Visitor } from 'zimmerframe' */
33
/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
44
import is_reference from 'is-reference';
@@ -18,8 +18,71 @@ import { validate_identifier_name } from './2-analyze/visitors/shared/utils.js';
1818

1919
const UNKNOWN = Symbol('unknown');
2020
/** Includes `BigInt` */
21-
const NUMBER = Symbol('number');
22-
const STRING = Symbol('string');
21+
export const NUMBER = Symbol('number');
22+
export const STRING = Symbol('string');
23+
24+
/** @type {Record<string, [type: NUMBER | STRING | UNKNOWN, fn?: Function]>} */
25+
const globals = {
26+
BigInt: [NUMBER, BigInt],
27+
'Math.min': [NUMBER, Math.min],
28+
'Math.max': [NUMBER, Math.max],
29+
'Math.random': [NUMBER],
30+
'Math.floor': [NUMBER, Math.floor],
31+
// @ts-expect-error
32+
'Math.f16round': [NUMBER, Math.f16round],
33+
'Math.round': [NUMBER, Math.round],
34+
'Math.abs': [NUMBER, Math.abs],
35+
'Math.acos': [NUMBER, Math.acos],
36+
'Math.asin': [NUMBER, Math.asin],
37+
'Math.atan': [NUMBER, Math.atan],
38+
'Math.atan2': [NUMBER, Math.atan2],
39+
'Math.ceil': [NUMBER, Math.ceil],
40+
'Math.cos': [NUMBER, Math.cos],
41+
'Math.sin': [NUMBER, Math.sin],
42+
'Math.tan': [NUMBER, Math.tan],
43+
'Math.exp': [NUMBER, Math.exp],
44+
'Math.log': [NUMBER, Math.log],
45+
'Math.pow': [NUMBER, Math.pow],
46+
'Math.sqrt': [NUMBER, Math.sqrt],
47+
'Math.clz32': [NUMBER, Math.clz32],
48+
'Math.imul': [NUMBER, Math.imul],
49+
'Math.sign': [NUMBER, Math.sign],
50+
'Math.log10': [NUMBER, Math.log10],
51+
'Math.log2': [NUMBER, Math.log2],
52+
'Math.log1p': [NUMBER, Math.log1p],
53+
'Math.expm1': [NUMBER, Math.expm1],
54+
'Math.cosh': [NUMBER, Math.cosh],
55+
'Math.sinh': [NUMBER, Math.sinh],
56+
'Math.tanh': [NUMBER, Math.tanh],
57+
'Math.acosh': [NUMBER, Math.acosh],
58+
'Math.asinh': [NUMBER, Math.asinh],
59+
'Math.atanh': [NUMBER, Math.atanh],
60+
'Math.trunc': [NUMBER, Math.trunc],
61+
'Math.fround': [NUMBER, Math.fround],
62+
'Math.cbrt': [NUMBER, Math.cbrt],
63+
Number: [NUMBER, Number],
64+
'Number.isInteger': [NUMBER, Number.isInteger],
65+
'Number.isFinite': [NUMBER, Number.isFinite],
66+
'Number.isNaN': [NUMBER, Number.isNaN],
67+
'Number.isSafeInteger': [NUMBER, Number.isSafeInteger],
68+
'Number.parseFloat': [NUMBER, Number.parseFloat],
69+
'Number.parseInt': [NUMBER, Number.parseInt],
70+
String: [STRING, String],
71+
'String.fromCharCode': [STRING, String.fromCharCode],
72+
'String.fromCodePoint': [STRING, String.fromCodePoint]
73+
};
74+
75+
/** @type {Record<string, any>} */
76+
const global_constants = {
77+
'Math.PI': Math.PI,
78+
'Math.E': Math.E,
79+
'Math.LN10': Math.LN10,
80+
'Math.LN2': Math.LN2,
81+
'Math.LOG10E': Math.LOG10E,
82+
'Math.LOG2E': Math.LOG2E,
83+
'Math.SQRT2': Math.SQRT2,
84+
'Math.SQRT1_2': Math.SQRT1_2
85+
};
2386

2487
export class Binding {
2588
/** @type {Scope} */
@@ -107,7 +170,7 @@ export class Binding {
107170

108171
class Evaluation {
109172
/** @type {Set<any>} */
110-
values = new Set();
173+
values;
111174

112175
/**
113176
* True if there is exactly one possible value
@@ -147,8 +210,11 @@ class Evaluation {
147210
*
148211
* @param {Scope} scope
149212
* @param {Expression} expression
213+
* @param {Set<any>} values
150214
*/
151-
constructor(scope, expression) {
215+
constructor(scope, expression, values) {
216+
this.values = values;
217+
152218
switch (expression.type) {
153219
case 'Literal': {
154220
this.values.add(expression.value);
@@ -172,15 +238,18 @@ class Evaluation {
172238
binding.kind === 'rest_prop' ||
173239
binding.kind === 'bindable_prop';
174240

175-
if (!binding.updated && binding.initial !== null && !is_prop) {
176-
const evaluation = binding.scope.evaluate(/** @type {Expression} */ (binding.initial));
177-
for (const value of evaluation.values) {
178-
this.values.add(value);
179-
}
241+
if (binding.initial?.type === 'EachBlock' && binding.initial.index === expression.name) {
242+
this.values.add(NUMBER);
180243
break;
181244
}
182245

183-
// TODO each index is always defined
246+
if (!binding.updated && binding.initial !== null && !is_prop) {
247+
binding.scope.evaluate(/** @type {Expression} */ (binding.initial), this.values);
248+
break;
249+
}
250+
} else if (expression.name === 'undefined') {
251+
this.values.add(undefined);
252+
break;
184253
}
185254

186255
// TODO glean what we can from reassignments
@@ -336,6 +405,101 @@ class Evaluation {
336405
break;
337406
}
338407

408+
case 'CallExpression': {
409+
const keypath = get_global_keypath(expression.callee, scope);
410+
411+
if (keypath) {
412+
if (is_rune(keypath)) {
413+
const arg = /** @type {Expression | undefined} */ (expression.arguments[0]);
414+
415+
switch (keypath) {
416+
case '$state':
417+
case '$state.raw':
418+
case '$derived':
419+
if (arg) {
420+
scope.evaluate(arg, this.values);
421+
} else {
422+
this.values.add(undefined);
423+
}
424+
break;
425+
426+
case '$props.id':
427+
this.values.add(STRING);
428+
break;
429+
430+
case '$effect.tracking':
431+
this.values.add(false);
432+
this.values.add(true);
433+
break;
434+
435+
case '$derived.by':
436+
if (arg?.type === 'ArrowFunctionExpression' && arg.body.type !== 'BlockStatement') {
437+
scope.evaluate(arg.body, this.values);
438+
break;
439+
}
440+
441+
this.values.add(UNKNOWN);
442+
break;
443+
444+
default: {
445+
this.values.add(UNKNOWN);
446+
}
447+
}
448+
449+
break;
450+
}
451+
452+
if (
453+
Object.hasOwn(globals, keypath) &&
454+
expression.arguments.every((arg) => arg.type !== 'SpreadElement')
455+
) {
456+
const [type, fn] = globals[keypath];
457+
const values = expression.arguments.map((arg) => scope.evaluate(arg));
458+
459+
if (fn && values.every((e) => e.is_known)) {
460+
this.values.add(fn(...values.map((e) => e.value)));
461+
} else {
462+
this.values.add(type);
463+
}
464+
465+
break;
466+
}
467+
}
468+
469+
this.values.add(UNKNOWN);
470+
break;
471+
}
472+
473+
case 'TemplateLiteral': {
474+
let result = expression.quasis[0].value.cooked;
475+
476+
for (let i = 0; i < expression.expressions.length; i += 1) {
477+
const e = scope.evaluate(expression.expressions[i]);
478+
479+
if (e.is_known) {
480+
result += e.value + expression.quasis[i + 1].value.cooked;
481+
} else {
482+
this.values.add(STRING);
483+
break;
484+
}
485+
}
486+
487+
this.values.add(result);
488+
break;
489+
}
490+
491+
case 'MemberExpression': {
492+
const keypath = get_global_keypath(expression, scope);
493+
494+
if (keypath && Object.hasOwn(global_constants, keypath)) {
495+
this.values.add(global_constants[keypath]);
496+
break;
497+
}
498+
499+
this.values.add(UNKNOWN);
500+
break;
501+
}
502+
339503
default: {
340504
this.values.add(UNKNOWN);
341505
}
@@ -548,10 +712,10 @@ export class Scope {
548712
* Only call this once scope has been fully generated in a first pass,
549713
* else this evaluates on incomplete data and may yield wrong results.
550714
* @param {Expression} expression
551-
* @param {Set<any>} values
715+
* @param {Set<any>} [values]
552716
*/
553717
evaluate(expression, values = new Set()) {
554-
return new Evaluation(this, expression);
718+
return new Evaluation(this, expression, values);
555719
}
556720
}
557721

@@ -1133,7 +1297,19 @@ export function get_rune(node, scope) {
11331297
if (!node) return null;
11341298
if (node.type !== 'CallExpression') return null;
11351299

1136-
let n = node.callee;
1300+
const keypath = get_global_keypath(node.callee, scope);
1301+
1302+
if (!keypath || !is_rune(keypath)) return null;
1303+
return keypath;
1304+
}
1305+
1306+
/**
1307+
* Returns the name of the rune if the given expression is a `CallExpression` using a rune.
1308+
* @param {Expression | Super} node
1309+
* @param {Scope} scope
1310+
*/
1311+
function get_global_keypath(node, scope) {
1312+
let n = node;
11371313

11381314
let joined = '';
11391315

@@ -1151,12 +1327,8 @@ export function get_rune(node, scope) {
11511327

11521328
if (n.type !== 'Identifier') return null;
11531329

1154-
joined = n.name + joined;
1155-
1156-
if (!is_rune(joined)) return null;
1157-
11581330
const binding = scope.get(n.name);
11591331
if (binding !== null) return null; // rune name, but references a variable or store
11601332

1161-
return joined;
1333+
return n.name + joined;
11621334
}

packages/svelte/src/internal/client/proxy.js

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function proxy(value) {
100100
prop,
101101
with_parent(() => source(UNINITIALIZED, stack))
102102
);
103+
update_version(version);
103104
}
104105
} else {
105106
// When working with arrays, we need to also ensure we update the length when removing

0 commit comments

Comments
 (0)