svelte-shaker 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyze.js +12 -5
- package/dist/eval.d.ts +15 -0
- package/dist/eval.js +25 -1
- package/dist/ir.d.ts +14 -0
- package/dist/ir.js +16 -0
- package/dist/mono.d.ts +1 -1
- package/dist/mono.js +3 -0
- package/dist/parse.d.ts +7 -0
- package/dist/transform.js +21 -0
- package/package.json +1 -1
package/dist/analyze.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseCached, parseModuleProgram, walk, } from './parse.js';
|
|
2
|
-
import { emptyPlan, } from './ir.js';
|
|
2
|
+
import { emptyPlan, isFoldableValue, } from './ir.js';
|
|
3
3
|
import { computeDeadSpans, inSpans } from './dead.js';
|
|
4
|
-
import { evaluate, setVar, unwrapTsAssertions } from './eval.js';
|
|
4
|
+
import { evaluate, literalValue, setVar, unwrapTsAssertions } from './eval.js';
|
|
5
5
|
const isSvelte = (source) => source.endsWith('.svelte');
|
|
6
6
|
/** Floor for the fixpoint iteration bound (see {@link fixpointIterationBound}). */
|
|
7
7
|
const MIN_FIXPOINT_ITERATIONS = 10;
|
|
@@ -1488,7 +1488,7 @@ function literalAttrValue(value) {
|
|
|
1488
1488
|
// `dynamic` flag itself must match — mono's `specializableShape` reads it.
|
|
1489
1489
|
const expr = unwrapTsAssertions(part.expression);
|
|
1490
1490
|
if (expr?.type === 'Literal')
|
|
1491
|
-
return
|
|
1491
|
+
return literalValue(expr);
|
|
1492
1492
|
}
|
|
1493
1493
|
return { known: false };
|
|
1494
1494
|
}
|
|
@@ -1549,7 +1549,9 @@ function buildPlan(model, u, ownerEnv) {
|
|
|
1549
1549
|
continue;
|
|
1550
1550
|
// constant fold: a clean singleton value set is the foldable case.
|
|
1551
1551
|
if (set.values.length === 1) {
|
|
1552
|
-
|
|
1552
|
+
const only = set.values[0];
|
|
1553
|
+
if (isFoldableValue(only))
|
|
1554
|
+
plan.constFold.set(decl.name, only);
|
|
1553
1555
|
continue;
|
|
1554
1556
|
}
|
|
1555
1557
|
// value-set narrowing: >= 2 distinct literals with no dynamic/⊤ contribution is a fully
|
|
@@ -1633,8 +1635,13 @@ function literalDefault(expr) {
|
|
|
1633
1635
|
expr = unwrapTsAssertions(expr) ?? undefined;
|
|
1634
1636
|
if (!expr)
|
|
1635
1637
|
return { known: true, value: undefined }; // omitted default -> undefined
|
|
1638
|
+
// `literalValue` is the same gate `literalAttrValue` and `evaluate` use: a
|
|
1639
|
+
// BigInt/RegExp default (no faithful source form) or a `null` that is really
|
|
1640
|
+
// `1e999` surviving JSON transport must stay UNKNOWN here too, or a call site
|
|
1641
|
+
// that merely omits the prop reintroduces the crash/misfold this default path
|
|
1642
|
+
// exists to prevent.
|
|
1636
1643
|
if (expr.type === 'Literal')
|
|
1637
|
-
return
|
|
1644
|
+
return literalValue(expr);
|
|
1638
1645
|
if (expr.type === 'Identifier' && expr.name === 'undefined')
|
|
1639
1646
|
return { known: true, value: undefined };
|
|
1640
1647
|
return { known: false };
|
package/dist/eval.d.ts
CHANGED
|
@@ -31,6 +31,21 @@ export declare function unwrapTsAssertions(node: AnyNode | null | undefined): An
|
|
|
31
31
|
* unknown on non-distributive ops), just without the interprocedural lattice.
|
|
32
32
|
*/
|
|
33
33
|
export declare function evaluate(node: AnyNode | null | undefined, env: ReadonlyMap<string, Literal>): EvalResult;
|
|
34
|
+
/**
|
|
35
|
+
* The `Literal` value of an ESTree `Literal` node, or unknown when the node
|
|
36
|
+
* carries a value outside that union. This is the ONLY door through which a
|
|
37
|
+
* parsed value enters the engine, so it is where the union is enforced:
|
|
38
|
+
*
|
|
39
|
+
* - **RegExp / BigInt literals** (`/x/g`, `1n`) hold a `RegExp` / `bigint` in
|
|
40
|
+
* `value` — no faithful literal source form, and `JSON.stringify` throws on
|
|
41
|
+
* the latter and flattens the former to `{}`. Both carry a discriminator.
|
|
42
|
+
* - **`value: null`** is ambiguous: an AST that reached us as JSON (the rsvelte
|
|
43
|
+
* parser crosses a WASM boundary that way) cannot represent `Infinity`, so
|
|
44
|
+
* `1e999` arrives indistinguishable from a genuine `null` except by `raw`.
|
|
45
|
+
*
|
|
46
|
+
* Anything unproven stays unknown, which costs one fold — never a wrong one.
|
|
47
|
+
*/
|
|
48
|
+
export declare function literalValue(node: AnyNode): EvalResult;
|
|
34
49
|
/**
|
|
35
50
|
* Sound set-aware predicate. `constEnv` holds props collapsed to a single
|
|
36
51
|
* literal (`constFold`); `setEnv` holds props whose reachable value set is known
|
package/dist/eval.js
CHANGED
|
@@ -37,7 +37,7 @@ export function evaluate(node, env) {
|
|
|
37
37
|
return UNKNOWN;
|
|
38
38
|
switch (node.type) {
|
|
39
39
|
case 'Literal':
|
|
40
|
-
return
|
|
40
|
+
return literalValue(node);
|
|
41
41
|
case 'Identifier': {
|
|
42
42
|
const name = node.name ?? '';
|
|
43
43
|
if (name === 'undefined')
|
|
@@ -132,6 +132,30 @@ export function evaluate(node, env) {
|
|
|
132
132
|
return UNKNOWN;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The `Literal` value of an ESTree `Literal` node, or unknown when the node
|
|
137
|
+
* carries a value outside that union. This is the ONLY door through which a
|
|
138
|
+
* parsed value enters the engine, so it is where the union is enforced:
|
|
139
|
+
*
|
|
140
|
+
* - **RegExp / BigInt literals** (`/x/g`, `1n`) hold a `RegExp` / `bigint` in
|
|
141
|
+
* `value` — no faithful literal source form, and `JSON.stringify` throws on
|
|
142
|
+
* the latter and flattens the former to `{}`. Both carry a discriminator.
|
|
143
|
+
* - **`value: null`** is ambiguous: an AST that reached us as JSON (the rsvelte
|
|
144
|
+
* parser crosses a WASM boundary that way) cannot represent `Infinity`, so
|
|
145
|
+
* `1e999` arrives indistinguishable from a genuine `null` except by `raw`.
|
|
146
|
+
*
|
|
147
|
+
* Anything unproven stays unknown, which costs one fold — never a wrong one.
|
|
148
|
+
*/
|
|
149
|
+
export function literalValue(node) {
|
|
150
|
+
if (node.regex !== undefined || node.bigint !== undefined)
|
|
151
|
+
return UNKNOWN;
|
|
152
|
+
const v = node.value;
|
|
153
|
+
if (v === null)
|
|
154
|
+
return node.raw === 'null' ? { known: true, value: null } : UNKNOWN;
|
|
155
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
|
|
156
|
+
return { known: true, value: v };
|
|
157
|
+
return UNKNOWN;
|
|
158
|
+
}
|
|
135
159
|
/**
|
|
136
160
|
* Sound set-aware predicate. `constEnv` holds props collapsed to a single
|
|
137
161
|
* literal (`constFold`); `setEnv` holds props whose reachable value set is known
|
package/dist/ir.d.ts
CHANGED
|
@@ -131,4 +131,18 @@ export interface ComponentPlan {
|
|
|
131
131
|
*/
|
|
132
132
|
valueSets: Map<string, PropValueSet>;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Whether a proven value may be SUBSTITUTED into the residual as a constant.
|
|
136
|
+
*
|
|
137
|
+
* Every member of {@link Literal} has a faithful source form except `-0`: the
|
|
138
|
+
* Svelte compiler constant-folds the expression it is spliced into and loses the
|
|
139
|
+
* sign of zero there (`{1 / n}` renders `-Infinity` with `n = -0` at runtime,
|
|
140
|
+
* but the folded `{1 / (-0)}` compiles to a literal `Infinity`). Emitting it
|
|
141
|
+
* would therefore change what renders even though the value we proved is right,
|
|
142
|
+
* so a `-0`-valued prop is simply left alone — one missed fold, no risk.
|
|
143
|
+
*
|
|
144
|
+
* Narrowing is unaffected: a narrowed prop stays dynamic and is never
|
|
145
|
+
* substituted, and the comparisons it feeds use real JS semantics.
|
|
146
|
+
*/
|
|
147
|
+
export declare function isFoldableValue(value: Literal): boolean;
|
|
134
148
|
export declare function emptyPlan(id: ComponentId): ComponentPlan;
|
package/dist/ir.js
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
// See docs/ARCHITECTURE.md §5.1. This is the M0 (walking-skeleton) subset:
|
|
4
4
|
// only the pieces basic1 exercises, but shaped so later levels slot in.
|
|
5
5
|
// ----------------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* Whether a proven value may be SUBSTITUTED into the residual as a constant.
|
|
8
|
+
*
|
|
9
|
+
* Every member of {@link Literal} has a faithful source form except `-0`: the
|
|
10
|
+
* Svelte compiler constant-folds the expression it is spliced into and loses the
|
|
11
|
+
* sign of zero there (`{1 / n}` renders `-Infinity` with `n = -0` at runtime,
|
|
12
|
+
* but the folded `{1 / (-0)}` compiles to a literal `Infinity`). Emitting it
|
|
13
|
+
* would therefore change what renders even though the value we proved is right,
|
|
14
|
+
* so a `-0`-valued prop is simply left alone — one missed fold, no risk.
|
|
15
|
+
*
|
|
16
|
+
* Narrowing is unaffected: a narrowed prop stays dynamic and is never
|
|
17
|
+
* substituted, and the comparisons it feeds use real JS semantics.
|
|
18
|
+
*/
|
|
19
|
+
export function isFoldableValue(value) {
|
|
20
|
+
return !Object.is(value, -0);
|
|
21
|
+
}
|
|
6
22
|
export function emptyPlan(id) {
|
|
7
23
|
return {
|
|
8
24
|
id,
|
package/dist/mono.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FileModel } from './analyze.js';
|
|
2
2
|
import { type AnyNode } from './parse.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type ComponentId, type ComponentPlan, type Literal } from './ir.js';
|
|
4
4
|
/** Tuning knobs for monomorphization (docs §8.1, §13.2). All have sound defaults. */
|
|
5
5
|
export interface MonomorphizeOptions {
|
|
6
6
|
/** Master switch. Default OFF — every existing behavior is unchanged. */
|
package/dist/mono.js
CHANGED
|
@@ -60,6 +60,7 @@ import { inSpans } from './dead.js';
|
|
|
60
60
|
import { shakeBody } from './transform.js';
|
|
61
61
|
import { readCallSite, deadSpansForPlans, isFoldBlockedName, } from './analyze.js';
|
|
62
62
|
import { parseSvelte, walk } from './parse.js';
|
|
63
|
+
import { isFoldableValue } from './ir.js';
|
|
63
64
|
export const DEFAULT_MONO_OPTIONS = {
|
|
64
65
|
enabled: false,
|
|
65
66
|
maxVariants: 8,
|
|
@@ -418,6 +419,8 @@ function specializableShape(node, child, plan) {
|
|
|
418
419
|
// override — exactly the analysis's "safely explicit" condition.
|
|
419
420
|
if (explicit.dynamic || !explicit.afterLastSpread)
|
|
420
421
|
continue;
|
|
422
|
+
if (!isFoldableValue(explicit.value))
|
|
423
|
+
continue; // no safe source form — exactly as constant fold
|
|
421
424
|
shape.set(name, explicit.value);
|
|
422
425
|
}
|
|
423
426
|
return shape;
|
package/dist/parse.d.ts
CHANGED
|
@@ -75,6 +75,13 @@ export interface AnyNode {
|
|
|
75
75
|
kind?: string | undefined;
|
|
76
76
|
/** ObjectExpression `Property` shorthand-method flag (`{ m() {} }`). */
|
|
77
77
|
method?: boolean | undefined;
|
|
78
|
+
/** ESTree `Literal` discriminators, present only on a RegExp (`/x/g`) or
|
|
79
|
+
* BigInt (`1n`) literal — neither of which carries a foldable `value`. */
|
|
80
|
+
regex?: {
|
|
81
|
+
pattern: string;
|
|
82
|
+
flags: string;
|
|
83
|
+
} | undefined;
|
|
84
|
+
bigint?: string | undefined;
|
|
78
85
|
value?: unknown;
|
|
79
86
|
}
|
|
80
87
|
export interface Root extends AnyNode {
|
package/dist/transform.js
CHANGED
|
@@ -1014,8 +1014,29 @@ function setDefault(map, key) {
|
|
|
1014
1014
|
function isSpace(ch) {
|
|
1015
1015
|
return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r';
|
|
1016
1016
|
}
|
|
1017
|
+
/** Source text for a folded value, faithful for every member of {@link Literal}
|
|
1018
|
+
* (the union {@link evaluate} admits). Always an expression, so it drops into
|
|
1019
|
+
* both substitution positions unchanged. */
|
|
1017
1020
|
function literalSource(value) {
|
|
1018
1021
|
if (value === undefined)
|
|
1019
1022
|
return 'undefined';
|
|
1023
|
+
if (typeof value === 'number')
|
|
1024
|
+
return numberSource(value);
|
|
1025
|
+
return JSON.stringify(value);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* `JSON.stringify` flattens `Infinity`/`-Infinity`/`NaN` to `null`, so those get
|
|
1029
|
+
* an explicit form. Written as arithmetic rather than the `Infinity`/`NaN`
|
|
1030
|
+
* globals because the substituted text lands in the CALLEE's scope, where a
|
|
1031
|
+
* local of either name would silently capture it; `(0/0)` cannot be shadowed.
|
|
1032
|
+
* (`-0`, the fourth lossy case, never reaches here — see `isFoldableValue`.)
|
|
1033
|
+
*/
|
|
1034
|
+
function numberSource(value) {
|
|
1035
|
+
if (Number.isNaN(value))
|
|
1036
|
+
return '(0/0)';
|
|
1037
|
+
if (value === Infinity)
|
|
1038
|
+
return '(1/0)';
|
|
1039
|
+
if (value === -Infinity)
|
|
1040
|
+
return '(-1/0)';
|
|
1020
1041
|
return JSON.stringify(value);
|
|
1021
1042
|
}
|