octane 0.4.2 → 0.4.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/cjs/context-identity.cjs +29 -0
- package/dist/cjs/dom-stage.cjs +24 -18
- package/dist/cjs/error-codes.server.generated.cjs +5 -0
- package/dist/cjs/hydration-markers.cjs +12 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +253 -393
- package/dist/cjs/runtime.server.cjs +137 -35
- package/dist/cjs/server/index.cjs +2 -0
- package/dist/cjs/server/streamed-signals.cjs +41 -9
- package/dist/cjs/signals/client.cjs +1 -6
- package/dist/cjs/signals/engine.cjs +1 -12
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/client-only-server.js +1 -28
- package/dist/compiler/compile-renderer-boundaries.js +1 -22
- package/dist/compiler/compile-universal.js +1 -22
- package/dist/compiler/compile.js +782 -359
- package/dist/compiler/dom-binding-fixed-props.js +11 -1
- package/dist/compiler/generated-origin.js +46 -0
- package/dist/compiler/hydrate-boundaries.js +88 -52
- package/dist/compiler/plain-hook-memo.js +5 -1
- package/dist/compiler/pure-factories.js +66 -0
- package/dist/compiler/register.js +4 -1
- package/dist/compiler/slot-hooks.js +113 -186
- package/dist/compiler/typescript-lowering.js +763 -0
- package/dist/compiler/void-roots.js +239 -0
- package/dist/compiler/volar.js +91 -38
- package/dist/context-identity.d.ts +14 -0
- package/dist/context-identity.js +28 -0
- package/dist/dom-stage.js +24 -18
- package/dist/error-codes.server.generated.d.ts +1 -0
- package/dist/error-codes.server.generated.js +5 -0
- package/dist/hydration/idle.js +1 -0
- package/dist/hydration/independent-island.js +53 -2
- package/dist/hydration/media.js +1 -0
- package/dist/hydration/types.d.ts +5 -0
- package/dist/hydration/visible.js +1 -0
- package/dist/hydration-markers.d.ts +8 -0
- package/dist/hydration-markers.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +25 -40
- package/dist/runtime.js +254 -398
- package/dist/runtime.server.d.ts +23 -2
- package/dist/runtime.server.js +142 -36
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/streamed-signals.js +41 -9
- package/dist/signals/client.js +3 -8
- package/dist/signals/engine.d.ts +0 -2
- package/dist/signals/engine.js +1 -13
- package/dist/universal-core.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/cjs/signals/diagnostic-local-hook.cjs +0 -37
- package/dist/compiler/local-void-roots.js +0 -154
- package/dist/signals/diagnostic-local-hook.d.ts +0 -8
- package/dist/signals/diagnostic-local-hook.js +0 -12
package/dist/compiler/compile.js
CHANGED
|
@@ -127,7 +127,12 @@ import { collectProvenContextBindings, isProvenContextUse } from './context-use.
|
|
|
127
127
|
import { assertNoLegacyContextProviders } from './context-provider.js';
|
|
128
128
|
import { applyCssModuleConstants } from './css-module-constants.js';
|
|
129
129
|
import { assertUniversalRuntimeTarget, normalizeUniversalRuntime } from './universal-runtime.js';
|
|
130
|
-
import {
|
|
130
|
+
import { findRootFactoryImports, proveVoidRoots } from './void-roots.js';
|
|
131
|
+
import {
|
|
132
|
+
lowerParameterProperties,
|
|
133
|
+
lowerTypeScriptStatements,
|
|
134
|
+
needsTypeScriptStatementLowering,
|
|
135
|
+
} from './typescript-lowering.js';
|
|
131
136
|
import { findPrivateCompiledContexts } from './private-context.js';
|
|
132
137
|
|
|
133
138
|
// DOM truth tables shared with the client/server runtimes (via constants.ts) —
|
|
@@ -148,6 +153,11 @@ import {
|
|
|
148
153
|
hyphenateStyleName,
|
|
149
154
|
} from '../dom-tables.js';
|
|
150
155
|
import { isDelegatedEventProp } from '../event-names.js';
|
|
156
|
+
import {
|
|
157
|
+
collectPureFactoryLocals,
|
|
158
|
+
isPureFactoryCall,
|
|
159
|
+
octanePureFactoryNames,
|
|
160
|
+
} from './pure-factories.js';
|
|
151
161
|
import { sanitizeURLAttribute, shouldSanitizeURLAttribute } from '../sanitize-url.js';
|
|
152
162
|
import {
|
|
153
163
|
invalidHtmlNestingWithAncestor,
|
|
@@ -1610,7 +1620,7 @@ function collectOctaneImportBindings(astBody) {
|
|
|
1610
1620
|
return { locals, namespaces, foreignLocals };
|
|
1611
1621
|
}
|
|
1612
1622
|
|
|
1613
|
-
function collectNestedBindingNames(root) {
|
|
1623
|
+
export function collectNestedBindingNames(root) {
|
|
1614
1624
|
const names = new Set();
|
|
1615
1625
|
const seen = new WeakSet();
|
|
1616
1626
|
const walk = (value) => {
|
|
@@ -1721,40 +1731,25 @@ function errorBoundaryFallback(node, fallbackAttribute) {
|
|
|
1721
1731
|
}
|
|
1722
1732
|
|
|
1723
1733
|
/**
|
|
1724
|
-
* Annotate direct calls to octane's
|
|
1725
|
-
* tree-shake unused
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1734
|
+
* Annotate direct calls to octane's side-effect-free factories with
|
|
1735
|
+
* `@__PURE__` so bundlers can tree-shake unused declarations (see
|
|
1736
|
+
* pure-factories.js for why the declaration-side `@__NO_SIDE_EFFECTS__` is not
|
|
1737
|
+
* enough under esbuild). Creating a lazy descriptor has no side effects, and
|
|
1738
|
+
* React builds get the same effect (an unused `React.lazy(() =>
|
|
1739
|
+
* import('./x.client'))` const disappears from the server bundle, which Start's
|
|
1740
|
+
* import-protection tree-shake verification relies on for `*.client.*`
|
|
1741
|
+
* modules). Locals shadowed anywhere below module scope are skipped wholesale —
|
|
1742
|
+
* same conservatism as lowerImportedErrorBoundaries.
|
|
1731
1743
|
*/
|
|
1732
|
-
function
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
if (
|
|
1738
|
-
specifier.type === 'ImportSpecifier' &&
|
|
1739
|
-
(specifier.imported?.name ?? specifier.imported?.value) === 'lazy' &&
|
|
1740
|
-
specifier.local?.name
|
|
1741
|
-
) {
|
|
1742
|
-
lazyLocals.add(specifier.local.name);
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
if (lazyLocals.size === 0) return ast;
|
|
1747
|
-
for (const name of collectNestedBindingNames(ast.body)) lazyLocals.delete(name);
|
|
1748
|
-
if (lazyLocals.size === 0) return ast;
|
|
1744
|
+
function annotatePureFactoryCalls(ast, names) {
|
|
1745
|
+
const pureLocals = collectPureFactoryLocals(ast.body, names);
|
|
1746
|
+
if (pureLocals.size === 0) return ast;
|
|
1747
|
+
for (const name of collectNestedBindingNames(ast.body)) pureLocals.delete(name);
|
|
1748
|
+
if (pureLocals.size === 0) return ast;
|
|
1749
1749
|
// COW: the @__PURE__ mark rides on a shallow copy of the call node (marker
|
|
1750
1750
|
// props are compiler-owned annotations and may only live on rebuilt nodes).
|
|
1751
1751
|
const body = mapAst(ast.body, (node) => {
|
|
1752
|
-
if (
|
|
1753
|
-
node.type === 'CallExpression' &&
|
|
1754
|
-
node.callee?.type === 'Identifier' &&
|
|
1755
|
-
lazyLocals.has(node.callee.name) &&
|
|
1756
|
-
node.__octanePure !== true
|
|
1757
|
-
) {
|
|
1752
|
+
if (isPureFactoryCall(node, pureLocals) && node.__octanePure !== true) {
|
|
1758
1753
|
return { ...node, __octanePure: true };
|
|
1759
1754
|
}
|
|
1760
1755
|
return null;
|
|
@@ -2427,6 +2422,71 @@ function styleObjectNeedsNativeBinding(node) {
|
|
|
2427
2422
|
});
|
|
2428
2423
|
}
|
|
2429
2424
|
|
|
2425
|
+
const STATIC_HOST_COMPONENT_NODES = new Set([
|
|
2426
|
+
'JSXElement',
|
|
2427
|
+
'JSXOpeningElement',
|
|
2428
|
+
'JSXClosingElement',
|
|
2429
|
+
'JSXFragment',
|
|
2430
|
+
'JSXOpeningFragment',
|
|
2431
|
+
'JSXClosingFragment',
|
|
2432
|
+
'JSXAttribute',
|
|
2433
|
+
'JSXIdentifier',
|
|
2434
|
+
'JSXNamespacedName',
|
|
2435
|
+
'JSXText',
|
|
2436
|
+
'JSXExpressionContainer',
|
|
2437
|
+
'JSXEmptyExpression',
|
|
2438
|
+
'Literal',
|
|
2439
|
+
'TemplateLiteral',
|
|
2440
|
+
'TemplateElement',
|
|
2441
|
+
]);
|
|
2442
|
+
|
|
2443
|
+
/**
|
|
2444
|
+
* A component that evaluates no authored expression cannot perform a native
|
|
2445
|
+
* read, so a native-read module need not bracket its render. The proof is an
|
|
2446
|
+
* allowlist: no setup statements, no destructured or defaulted parameters, only
|
|
2447
|
+
* lowercase host elements (component tags invoke authored code), and only
|
|
2448
|
+
* literal attribute and child values. Any other node keeps the bracket.
|
|
2449
|
+
*/
|
|
2450
|
+
function isStaticHostComponent(node) {
|
|
2451
|
+
if (node?.body?.type !== 'JSXCodeBlock' || (node.body.body?.length ?? 0) !== 0) return false;
|
|
2452
|
+
if (!(node.params ?? []).every((param) => param.type === 'Identifier')) return false;
|
|
2453
|
+
const visit = (value) => {
|
|
2454
|
+
if (value === null || typeof value !== 'object') return true;
|
|
2455
|
+
if (Array.isArray(value)) return value.every(visit);
|
|
2456
|
+
// Plain records such as a template quasi's { raw, cooked } hold no code.
|
|
2457
|
+
if (value.type === undefined)
|
|
2458
|
+
return Object.values(value).every((item) => item === null || typeof item !== 'object');
|
|
2459
|
+
if (!STATIC_HOST_COMPONENT_NODES.has(value.type)) return false;
|
|
2460
|
+
if (value.type === 'JSXOpeningElement' && !/^[a-z]/.test(value.name?.name ?? '')) return false;
|
|
2461
|
+
if (value.type === 'Literal' && value.regex !== undefined) return false;
|
|
2462
|
+
if (value.type === 'TemplateLiteral' && value.expressions.length !== 0) return false;
|
|
2463
|
+
for (const key in value) {
|
|
2464
|
+
if (key === 'loc' || key === 'metadata' || key === 'parent' || key === 'range') continue;
|
|
2465
|
+
const child = value[key];
|
|
2466
|
+
if (child !== null && typeof child === 'object' && !visit(child)) return false;
|
|
2467
|
+
}
|
|
2468
|
+
return true;
|
|
2469
|
+
};
|
|
2470
|
+
return node.body.render != null && visit(node.body.render);
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// A fresh object literal whose properties are all plain data: enumerating it
|
|
2474
|
+
// runs no accessor, and nothing else can mutate it. nativeStyleBinding can then
|
|
2475
|
+
// check its values at runtime and skip the owning Block while all are scalar.
|
|
2476
|
+
function isPlainStyleObjectLiteral(node) {
|
|
2477
|
+
return (
|
|
2478
|
+
node?.type === 'ObjectExpression' &&
|
|
2479
|
+
(node.properties ?? []).every(
|
|
2480
|
+
(property) =>
|
|
2481
|
+
(property.type === 'Property' || property.type === 'ObjectProperty') &&
|
|
2482
|
+
!property.computed &&
|
|
2483
|
+
!property.method &&
|
|
2484
|
+
(property.kind === undefined || property.kind === 'init') &&
|
|
2485
|
+
(property.key?.name ?? property.key?.value) !== '__proto__',
|
|
2486
|
+
)
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2430
2490
|
// DEV must send invalid literal declarations through the same style helpers as
|
|
2431
2491
|
// dynamic objects. Keep valid literals and all production output fully baked.
|
|
2432
2492
|
function staticStyleObjectNeedsDevValidation(obj) {
|
|
@@ -5570,6 +5630,99 @@ function collectImmutableModuleFunctions(body) {
|
|
|
5570
5630
|
return declared;
|
|
5571
5631
|
}
|
|
5572
5632
|
|
|
5633
|
+
/**
|
|
5634
|
+
* Top-level module bindings, split by whether their identity can change after
|
|
5635
|
+
* evaluation. `mutable` holds `let`/`var` declarations, class declarations, and
|
|
5636
|
+
* function declarations that collectImmutableModuleFunctions demoted: an
|
|
5637
|
+
* identity-only survivor skip cannot witness them, so a region reading one
|
|
5638
|
+
* must re-render. `all` adds consts, unreassigned functions, enums, and
|
|
5639
|
+
* imports; a free name outside it (and outside every enclosing local) is a
|
|
5640
|
+
* global. Only top-level declarations count — a same-named parameter or local
|
|
5641
|
+
* elsewhere in the module never shadows the global a region actually reads.
|
|
5642
|
+
*/
|
|
5643
|
+
function collectModuleTopLevelBindings(body, immutableFunctions) {
|
|
5644
|
+
const all = new Set();
|
|
5645
|
+
const mutable = new Set();
|
|
5646
|
+
for (const statement of body) {
|
|
5647
|
+
if (statement.type === 'ImportDeclaration') {
|
|
5648
|
+
for (const specifier of statement.specifiers || []) {
|
|
5649
|
+
if (specifier.local) all.add(specifier.local.name);
|
|
5650
|
+
}
|
|
5651
|
+
continue;
|
|
5652
|
+
}
|
|
5653
|
+
const declaration =
|
|
5654
|
+
statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration'
|
|
5655
|
+
? statement.declaration
|
|
5656
|
+
: statement;
|
|
5657
|
+
if (!declaration) continue;
|
|
5658
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
5659
|
+
const names = new Set();
|
|
5660
|
+
for (const d of declaration.declarations || []) collectBindings(d.id, names);
|
|
5661
|
+
for (const name of names) {
|
|
5662
|
+
all.add(name);
|
|
5663
|
+
if (declaration.kind !== 'const') mutable.add(name);
|
|
5664
|
+
}
|
|
5665
|
+
} else if (declaration.type === 'ClassDeclaration' && declaration.id) {
|
|
5666
|
+
all.add(declaration.id.name);
|
|
5667
|
+
mutable.add(declaration.id.name);
|
|
5668
|
+
} else if (
|
|
5669
|
+
declaration.type === 'FunctionDeclaration' &&
|
|
5670
|
+
declaration.id?.type === 'Identifier'
|
|
5671
|
+
) {
|
|
5672
|
+
all.add(declaration.id.name);
|
|
5673
|
+
if (!immutableFunctions.has(declaration.id.name)) mutable.add(declaration.id.name);
|
|
5674
|
+
} else if (
|
|
5675
|
+
(declaration.type === 'TSEnumDeclaration' ||
|
|
5676
|
+
declaration.type === 'TSModuleDeclaration' ||
|
|
5677
|
+
declaration.type === 'TSImportEqualsDeclaration') &&
|
|
5678
|
+
declaration.id?.type === 'Identifier'
|
|
5679
|
+
) {
|
|
5680
|
+
all.add(declaration.id.name);
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
return { all, mutable };
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
// Globals a render-time read may treat as fixed: the value constants and the
|
|
5687
|
+
// standard ECMAScript namespaces and constructors. Any other unbound name
|
|
5688
|
+
// (`location`, `window`, `document`, `globalThis`, an app-installed global) is
|
|
5689
|
+
// host or application state that no compiler witness observes.
|
|
5690
|
+
const IMMUTABLE_AMBIENT_GLOBALS = new Set([
|
|
5691
|
+
'undefined',
|
|
5692
|
+
'NaN',
|
|
5693
|
+
'Infinity',
|
|
5694
|
+
...SETUP_PURE_GLOBAL_CALLEES,
|
|
5695
|
+
...SETUP_PURE_NAMESPACE_MEMBERS.keys(),
|
|
5696
|
+
'Error',
|
|
5697
|
+
'TypeError',
|
|
5698
|
+
'RangeError',
|
|
5699
|
+
'RegExp',
|
|
5700
|
+
'Map',
|
|
5701
|
+
'Set',
|
|
5702
|
+
'WeakMap',
|
|
5703
|
+
'WeakSet',
|
|
5704
|
+
'Promise',
|
|
5705
|
+
'Reflect',
|
|
5706
|
+
'Intl',
|
|
5707
|
+
]);
|
|
5708
|
+
|
|
5709
|
+
/**
|
|
5710
|
+
* Does `name`, a free read of a memo region that is neither an enclosing local
|
|
5711
|
+
* (ctx.currentComponentLocals, extended through every hoisted helper) nor an
|
|
5712
|
+
* import, resolve to state that can change without any witness the compiler
|
|
5713
|
+
* compares? True for mutable top-level module bindings and for non-intrinsic
|
|
5714
|
+
* globals. An enclosing binding the local set misses reads as a global and
|
|
5715
|
+
* fails closed — a lost fast path, never a stale row.
|
|
5716
|
+
*/
|
|
5717
|
+
function isUnwitnessedAmbientRead(name, ctx) {
|
|
5718
|
+
// `_$…` names are runtime helpers the compiler itself imports.
|
|
5719
|
+
if (name.startsWith('_$')) return false;
|
|
5720
|
+
const module = ctx.moduleTopLevelBindings;
|
|
5721
|
+
if (module === undefined) return false;
|
|
5722
|
+
if (module.mutable.has(name)) return true;
|
|
5723
|
+
return !module.all.has(name) && !IMMUTABLE_AMBIENT_GLOBALS.has(name);
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5573
5726
|
// A component whose synchronous proof reads its props is safe only at JSX
|
|
5574
5727
|
// edges that construct every read property as an own data field. Missing keys
|
|
5575
5728
|
// can reach an inherited getter, and `__proto__` changes an object literal's
|
|
@@ -8153,7 +8306,8 @@ function normalizeArrowComponents(ast) {
|
|
|
8153
8306
|
// `.tsrx` module. This is RUNTIME-ONLY: the Volar/TS-server path (volar.js) is a
|
|
8154
8307
|
// separate pipeline that intentionally PRESERVES all types for the language
|
|
8155
8308
|
// service, so it never calls this. Enums and value namespaces have runtime
|
|
8156
|
-
// semantics and are deliberately NOT treated as type-only
|
|
8309
|
+
// semantics and are deliberately NOT treated as type-only: the print lowers
|
|
8310
|
+
// them to plain JavaScript instead (typescript-lowering.js).
|
|
8157
8311
|
function isTypeOnlyStatement(node) {
|
|
8158
8312
|
if (node == null) return false;
|
|
8159
8313
|
if (
|
|
@@ -8177,8 +8331,13 @@ function isTypeOnlyStatement(node) {
|
|
|
8177
8331
|
);
|
|
8178
8332
|
}
|
|
8179
8333
|
}
|
|
8180
|
-
// `import type { … } from …`
|
|
8181
|
-
if (
|
|
8334
|
+
// `import type { … } from …` / `import type X = require('…')`
|
|
8335
|
+
if (
|
|
8336
|
+
(node.type === 'ImportDeclaration' || node.type === 'TSImportEqualsDeclaration') &&
|
|
8337
|
+
node.importKind === 'type'
|
|
8338
|
+
) {
|
|
8339
|
+
return true;
|
|
8340
|
+
}
|
|
8182
8341
|
// `export type { … }`, `export type X = …`, `export interface I {}`
|
|
8183
8342
|
if (node.type === 'ExportNamedDeclaration') {
|
|
8184
8343
|
if (node.exportKind === 'type') return true;
|
|
@@ -9976,6 +10135,15 @@ function compileInternal(
|
|
|
9976
10135
|
// rebuilt with builders/spreads (locations carried via setLocation) and
|
|
9977
10136
|
// untouched subtrees stay shared with the parse by reference.
|
|
9978
10137
|
let ast = parsedAst;
|
|
10138
|
+
// Void roots, private compiled contexts and compiled split Hydrate are
|
|
10139
|
+
// same-module proofs over the authored DOM tree. Renderer participation is
|
|
10140
|
+
// decided per module: `rendererBoundaryPreparation` is non-null exactly when
|
|
10141
|
+
// this module renders a configured boundary tag, and nested renderer compiles
|
|
10142
|
+
// carry `__rendererBoundariesLowered`/`__universal`. The project-wide
|
|
10143
|
+
// `rendererBoundaries`/`rendererRegistry` options alone do not disqualify a
|
|
10144
|
+
// module — bundlers pass them to every module once any boundary exists, and
|
|
10145
|
+
// the specialized runtime entries share the public Context/root shapes that
|
|
10146
|
+
// renderer bridges read.
|
|
9979
10147
|
const localVoidRootsEnabled =
|
|
9980
10148
|
!options?.dev &&
|
|
9981
10149
|
!options?.hmr &&
|
|
@@ -9984,9 +10152,8 @@ function compileInternal(
|
|
|
9984
10152
|
options?.universalRuntime == null &&
|
|
9985
10153
|
options?.__universal == null &&
|
|
9986
10154
|
!options?.__rendererBoundariesLowered &&
|
|
9987
|
-
rendererBoundaryPreparation === null
|
|
9988
|
-
|
|
9989
|
-
options?.rendererRegistry == null;
|
|
10155
|
+
rendererBoundaryPreparation === null;
|
|
10156
|
+
const rootFactories = localVoidRootsEnabled ? findRootFactoryImports(ast) : new Map();
|
|
9990
10157
|
const authoredVoidRootIds = new Set();
|
|
9991
10158
|
const privateCompiledContexts = localVoidRootsEnabled
|
|
9992
10159
|
? findPrivateCompiledContexts(ast)
|
|
@@ -9994,21 +10161,7 @@ function compileInternal(
|
|
|
9994
10161
|
const splitPrivateContexts = localVoidRootsEnabled
|
|
9995
10162
|
? privateCompiledContextsForHydrateAst(parsedAst)
|
|
9996
10163
|
: null;
|
|
9997
|
-
if (
|
|
9998
|
-
localVoidRootsEnabled &&
|
|
9999
|
-
ast.body.some(
|
|
10000
|
-
(node) =>
|
|
10001
|
-
node.type === 'ImportDeclaration' &&
|
|
10002
|
-
node.source?.value === 'octane' &&
|
|
10003
|
-
node.specifiers.some(
|
|
10004
|
-
(specifier) =>
|
|
10005
|
-
specifier.type === 'ImportSpecifier' &&
|
|
10006
|
-
['createRoot', 'hydrateRoot'].includes(
|
|
10007
|
-
specifier.imported.name ?? specifier.imported.value,
|
|
10008
|
-
),
|
|
10009
|
-
),
|
|
10010
|
-
)
|
|
10011
|
-
) {
|
|
10164
|
+
if (rootFactories.size > 0) {
|
|
10012
10165
|
for (const statement of ast.body) {
|
|
10013
10166
|
const node =
|
|
10014
10167
|
statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration'
|
|
@@ -10027,7 +10180,15 @@ function compileInternal(
|
|
|
10027
10180
|
// Normalize arrow-function components (`const X = () => @{…}`) to
|
|
10028
10181
|
// FunctionDeclaration form so the component pipeline recognizes them.
|
|
10029
10182
|
ast = normalizeArrowComponents(ast);
|
|
10030
|
-
ast =
|
|
10183
|
+
ast = annotatePureFactoryCalls(
|
|
10184
|
+
ast,
|
|
10185
|
+
octanePureFactoryNames({
|
|
10186
|
+
clientDom:
|
|
10187
|
+
(options?.renderer?.target ?? 'dom') === 'dom' &&
|
|
10188
|
+
universalRuntime === undefined &&
|
|
10189
|
+
options?.__universal == null,
|
|
10190
|
+
}),
|
|
10191
|
+
);
|
|
10031
10192
|
const errorBoundaryLowering = lowerImportedErrorBoundaries(ast);
|
|
10032
10193
|
ast = errorBoundaryLowering.ast;
|
|
10033
10194
|
const consumedRuntimeLocals = errorBoundaryLowering.consumed;
|
|
@@ -10402,6 +10563,13 @@ function compileInternal(
|
|
|
10402
10563
|
// that are ever assigned (`foo = bar`) are excluded: their identity is then
|
|
10403
10564
|
// as mutable as a module `let`, which fails closed.
|
|
10404
10565
|
ctx.moduleFunctionDeclarations = collectImmutableModuleFunctions(ast.body);
|
|
10566
|
+
// Top-level module bindings (and the mutable subset): identity-only survivor
|
|
10567
|
+
// skips (forBlock PURE / DEP-PURE) fail closed when a row reads a mutable
|
|
10568
|
+
// one or a global (see isUnwitnessedAmbientRead).
|
|
10569
|
+
ctx.moduleTopLevelBindings = collectModuleTopLevelBindings(
|
|
10570
|
+
ast.body,
|
|
10571
|
+
ctx.moduleFunctionDeclarations,
|
|
10572
|
+
);
|
|
10405
10573
|
// Global namespaces are only trusted by the setup checkpoint analysis when
|
|
10406
10574
|
// no module binding shadows them (see setupCanScheduleSelfUpdate).
|
|
10407
10575
|
ctx._moduleBoundNames = collectModuleBoundNames(ast);
|
|
@@ -10505,25 +10673,71 @@ function compileInternal(
|
|
|
10505
10673
|
});
|
|
10506
10674
|
}
|
|
10507
10675
|
}
|
|
10508
|
-
if (
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10676
|
+
if (
|
|
10677
|
+
rootFactories.size > 0 &&
|
|
10678
|
+
(authoredVoidRootIds.size > 0 ||
|
|
10679
|
+
(ctx.isVoidComponentImport !== null && ctx.importedComponentBindings.size > 0))
|
|
10680
|
+
) {
|
|
10681
|
+
// Definition IDs come from the authored, exact shorthand declarations:
|
|
10682
|
+
// arrow normalization and return-JSX lowering cannot manufacture this
|
|
10683
|
+
// evidence. Imports rely on the adapter's proof of the loaded export ABI.
|
|
10684
|
+
const writes = authoredVoidRootIds.size > 0 ? collectReassignedBindings(ast) : null;
|
|
10685
|
+
const stableComponents = new Set();
|
|
10686
|
+
for (const info of ctx.componentInfo.values())
|
|
10687
|
+
if (info.voidOutput && authoredVoidRootIds.has(info.node.id) && !writes.has(info.node.id))
|
|
10688
|
+
stableComponents.add(info.node.id.name);
|
|
10514
10689
|
const components = new Set([...ctx.componentInfo.values()].map((info) => info.node));
|
|
10515
|
-
const callees =
|
|
10690
|
+
const callees = new Map();
|
|
10691
|
+
let loweredElements = null;
|
|
10692
|
+
for (const root of proveVoidRoots(ast, {
|
|
10693
|
+
factories: rootFactories,
|
|
10694
|
+
component(name) {
|
|
10695
|
+
if (stableComponents.has(name)) return true;
|
|
10696
|
+
const imported = ctx.importedComponentBindings.get(name);
|
|
10697
|
+
return imported !== undefined &&
|
|
10698
|
+
ctx.isVoidComponentImport?.(imported.request, imported.imported) === true
|
|
10699
|
+
? true
|
|
10700
|
+
: undefined;
|
|
10701
|
+
},
|
|
10702
|
+
// Roots created while a component renders stay on the generic path.
|
|
10703
|
+
skip: (node) => node.type === 'JSXCodeBlock' || components.has(node),
|
|
10704
|
+
})) {
|
|
10705
|
+
callees.set(root.callee, root.helper);
|
|
10706
|
+
// Native reads keep their scoped element resolver around `$` reads.
|
|
10707
|
+
for (const { call, index } of ctx.nativeReads ? [] : root.elements) {
|
|
10708
|
+
const props = voidRootElementProps(call.arguments[index]);
|
|
10709
|
+
if (props !== null) (loweredElements ??= new Map()).set(call, { index, props });
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10516
10712
|
if (callees.size > 0) {
|
|
10517
10713
|
const aliases = new Map();
|
|
10518
|
-
|
|
10714
|
+
const helpers = new Set(callees.values());
|
|
10715
|
+
if (loweredElements !== null) helpers.add('__voidRootProps');
|
|
10716
|
+
for (const helper of helpers) {
|
|
10519
10717
|
const alias = allocCompilerName(ctx, rtAlias(helper));
|
|
10520
10718
|
(ctx.privateRuntimeAliases ??= new Map()).set(helper, alias);
|
|
10521
10719
|
ctx.runtimeNeeded.add(helper);
|
|
10522
10720
|
aliases.set(helper, alias);
|
|
10523
10721
|
}
|
|
10524
|
-
|
|
10525
|
-
callees.has(node)
|
|
10526
|
-
|
|
10722
|
+
const rewrite = (node) => {
|
|
10723
|
+
if (callees.has(node)) return { ...node, name: aliases.get(callees.get(node)) };
|
|
10724
|
+
const lowered = loweredElements?.get(node);
|
|
10725
|
+
if (lowered === undefined) return null;
|
|
10726
|
+
// `root.render(<C .../>)` → `root.render(C, props)`: the root unwraps
|
|
10727
|
+
// the element to exactly this body/props pair, with a null key.
|
|
10728
|
+
const element = node.arguments[lowered.index];
|
|
10729
|
+
const name = element.openingElement.name;
|
|
10730
|
+
const tag = () => inheritOriginLoc(b.id(name.name), name);
|
|
10731
|
+
const args = [...node.arguments];
|
|
10732
|
+
args.splice(
|
|
10733
|
+
lowered.index,
|
|
10734
|
+
1,
|
|
10735
|
+
tag(),
|
|
10736
|
+
inheritOriginLoc(b.call(aliases.get('__voidRootProps'), tag(), lowered.props), element),
|
|
10737
|
+
);
|
|
10738
|
+
return mapAst({ ...node, arguments: args }, rewrite);
|
|
10739
|
+
};
|
|
10740
|
+
ast = mapAst(ast, rewrite);
|
|
10527
10741
|
}
|
|
10528
10742
|
}
|
|
10529
10743
|
if (privateCompiledContexts.size > 0 || splitPrivateContexts?.callees.size > 0) {
|
|
@@ -11494,7 +11708,7 @@ function compileServer(
|
|
|
11494
11708
|
// Normalize arrow-function components (`const X = () => @{…}`) to
|
|
11495
11709
|
// FunctionDeclaration form so the component pipeline recognizes them.
|
|
11496
11710
|
ast = normalizeArrowComponents(ast);
|
|
11497
|
-
ast =
|
|
11711
|
+
ast = annotatePureFactoryCalls(ast, octanePureFactoryNames({ clientDom: false }));
|
|
11498
11712
|
const errorBoundaryLowering = lowerImportedErrorBoundaries(ast);
|
|
11499
11713
|
ast = errorBoundaryLowering.ast;
|
|
11500
11714
|
const consumedRuntimeLocals = errorBoundaryLowering.consumed;
|
|
@@ -12504,12 +12718,22 @@ function ssrEmitNodes(
|
|
|
12504
12718
|
// adjacency. The client counterpart is the hole-aware `sibling()` walk in
|
|
12505
12719
|
// runtime.ts, which treats separators as protocol nodes. Keep the comment
|
|
12506
12720
|
// payload in sync with HYDRATION_TEXT_SEP (constants.ts).
|
|
12721
|
+
//
|
|
12722
|
+
// A dynamic hole with siblings is a `<!>` position the client walks from
|
|
12723
|
+
// (htextSwap), so an empty value must still serialize ONE node: wrap it in
|
|
12724
|
+
// ssrTextSlot, which emits an empty anchor comment for ''. A sole child
|
|
12725
|
+
// needs no stand-in: the client mounts it with htext (element) or it ends
|
|
12726
|
+
// its block range (root/arm), and no later walk starts from it.
|
|
12507
12727
|
let prevText = null; // 'static' | 'dyn' | null — last emitted part's text kind
|
|
12508
12728
|
for (const n of nodes) {
|
|
12509
12729
|
const kind = textAdjacencyKind(n, ctx);
|
|
12510
12730
|
if (kind === 'empty') continue; // serializes nothing — skip, adjacency-transparent
|
|
12511
12731
|
const nlGuard = nlGuardFirst && parts.length === 0;
|
|
12512
|
-
|
|
12732
|
+
let p = ssrEmitNode(n, ctx, name, inlinedSubs, parentNs, cssHash, componentNs, nlGuard);
|
|
12733
|
+
if (p && kind === 'dyn' && nodes.length > 1) {
|
|
12734
|
+
ctx.runtimeNeeded.add('ssrTextSlot');
|
|
12735
|
+
p = ssrCall('ssrTextSlot', [p], n);
|
|
12736
|
+
}
|
|
12513
12737
|
if (p) {
|
|
12514
12738
|
if (kind !== 'other' && prevText !== null && (kind === 'dyn' || prevText === 'dyn')) {
|
|
12515
12739
|
parts.push('<!-- -->');
|
|
@@ -14465,6 +14689,10 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
14465
14689
|
containsComponentCallOrControlFlow(itemBody) ||
|
|
14466
14690
|
containsRenderCall(itemBody) ||
|
|
14467
14691
|
ssrAstCallsAny(itemSub.fn, ['_$ssrChild', '_$ssrComponent']);
|
|
14692
|
+
const signalSite =
|
|
14693
|
+
mapCall === null && (ctx.signalBindingsUsed || ctx.nativeReads) && containsRenderCall(itemBody)
|
|
14694
|
+
? ssrControlKey('for', node)
|
|
14695
|
+
: null;
|
|
14468
14696
|
const itemCall = ssrSubCall(
|
|
14469
14697
|
itemSub.fnName,
|
|
14470
14698
|
node.index ? [b.id('__it'), b.id('__i')] : [b.id('__it')],
|
|
@@ -14484,7 +14712,11 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
14484
14712
|
node.index ? b.id('__i') : ssrVoid(node),
|
|
14485
14713
|
b.id('__s'),
|
|
14486
14714
|
b.literal(!sharedItemRange && !bindingSite),
|
|
14487
|
-
...(
|
|
14715
|
+
...(signalSite !== null
|
|
14716
|
+
? [b.literal(false), b.literal(signalSite)]
|
|
14717
|
+
: mapCall === null
|
|
14718
|
+
? []
|
|
14719
|
+
: [b.literal(true)]),
|
|
14488
14720
|
],
|
|
14489
14721
|
node,
|
|
14490
14722
|
)
|
|
@@ -16255,7 +16487,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
16255
16487
|
b.id(name, node.id ?? node),
|
|
16256
16488
|
fnParams,
|
|
16257
16489
|
b.block(
|
|
16258
|
-
ctx.nativeReads
|
|
16490
|
+
ctx.nativeReads && !isStaticHostComponent(node)
|
|
16259
16491
|
? wrapNativeReadScope(presentationBody, b.id('__s'), nativeReadNames(ctx))
|
|
16260
16492
|
: presentationBody,
|
|
16261
16493
|
),
|
|
@@ -16811,15 +17043,30 @@ function depPathKey(node) {
|
|
|
16811
17043
|
// props OBJECT with unchanged fields doesn't refetch), bare identifiers
|
|
16812
17044
|
// otherwise. Only outer runtime references become deps; locally declared
|
|
16813
17045
|
// bindings and erased syntax do not.
|
|
16814
|
-
|
|
17046
|
+
//
|
|
17047
|
+
// Deps evaluate eagerly on every render, so they may only perform reads the
|
|
17048
|
+
// authored render performs. A read inside a nested function (which may never
|
|
17049
|
+
// run during render) or behind a condition that cannot be replayed is
|
|
17050
|
+
// deferred: a module-bound root keeps its precise path as `root?.prop`, and an
|
|
17051
|
+
// ambient global (not bound in the enclosing scope chain) contributes no dep at
|
|
17052
|
+
// all, because it may not exist in this environment (`window` under SSR).
|
|
17053
|
+
// `isModuleBound` is a scope-aware `(name) => boolean` from
|
|
17054
|
+
// `moduleBoundCheckForDeps`, resolving through the expression's enclosing scope
|
|
17055
|
+
// chain so that sibling-function parameters never leak into the decision.
|
|
17056
|
+
function collectDepPaths(expr, coarsenDepRoots = null, isModuleBound = null) {
|
|
16815
17057
|
const deps = [];
|
|
16816
17058
|
const seen = new Set();
|
|
16817
17059
|
const lexical = createLexicalAnalysis(expr);
|
|
16818
17060
|
let guards = null;
|
|
16819
17061
|
let nextGuard = 0;
|
|
17062
|
+
let deferred = 0;
|
|
17063
|
+
let ownArguments = 0;
|
|
16820
17064
|
const isFree = (node, parent, key) =>
|
|
16821
17065
|
isIdentifierReference(node, parent, key, lexical) &&
|
|
16822
|
-
!lexical.isBound(lexical.nodeScopes.get(node) ?? lexical.rootScope, node.name)
|
|
17066
|
+
!lexical.isBound(lexical.nodeScopes.get(node) ?? lexical.rootScope, node.name) &&
|
|
17067
|
+
!(ownArguments > 0 && node.name === 'arguments');
|
|
17068
|
+
const deferredAmbient = (name) =>
|
|
17069
|
+
deferred > 0 && (isModuleBound === null || !isModuleBound(name));
|
|
16823
17070
|
const push = (node, key) => {
|
|
16824
17071
|
if (coarsenDepRoots !== null) {
|
|
16825
17072
|
const member = depPathMember(node);
|
|
@@ -16892,10 +17139,16 @@ function collectDepPaths(expr, coarsenDepRoots = null) {
|
|
|
16892
17139
|
return null;
|
|
16893
17140
|
}
|
|
16894
17141
|
|
|
17142
|
+
function walkDeferred(node, parent, key) {
|
|
17143
|
+
deferred++;
|
|
17144
|
+
walk(node, parent, key);
|
|
17145
|
+
deferred--;
|
|
17146
|
+
}
|
|
17147
|
+
|
|
16895
17148
|
function walkGuarded(node, test, truthy) {
|
|
16896
17149
|
const guard = necessaryTypeofGuard(test, truthy);
|
|
16897
17150
|
if (guard === null) {
|
|
16898
|
-
|
|
17151
|
+
walkDeferred(node, null, null);
|
|
16899
17152
|
return;
|
|
16900
17153
|
}
|
|
16901
17154
|
const previous = guards;
|
|
@@ -16944,15 +17197,33 @@ function collectDepPaths(expr, coarsenDepRoots = null) {
|
|
|
16944
17197
|
if (n.operator === '&&' || n.operator === '||') {
|
|
16945
17198
|
walkGuarded(n.right, n.left, n.operator === '&&');
|
|
16946
17199
|
} else {
|
|
16947
|
-
|
|
17200
|
+
walkDeferred(n.right, n, 'right');
|
|
16948
17201
|
}
|
|
16949
17202
|
return;
|
|
17203
|
+
case 'FunctionExpression':
|
|
17204
|
+
case 'FunctionDeclaration':
|
|
17205
|
+
case 'ArrowFunctionExpression': {
|
|
17206
|
+
// A non-arrow function owns `arguments`; it is never the component's.
|
|
17207
|
+
const own = n.type !== 'ArrowFunctionExpression';
|
|
17208
|
+
if (own) ownArguments++;
|
|
17209
|
+
deferred++;
|
|
17210
|
+
forEachRuntimeAstChild(n, (child, childKey) => walk(child, n, childKey));
|
|
17211
|
+
deferred--;
|
|
17212
|
+
if (own) ownArguments--;
|
|
17213
|
+
return;
|
|
17214
|
+
}
|
|
17215
|
+
case 'PropertyDefinition':
|
|
17216
|
+
// Instance field initializers run per construction, not at render.
|
|
17217
|
+
if (n.computed) walk(n.key, n, 'key');
|
|
17218
|
+
if (n.static) walk(n.value, n, 'value');
|
|
17219
|
+
else walkDeferred(n.value, n, 'value');
|
|
17220
|
+
return;
|
|
16950
17221
|
case 'MetaProperty':
|
|
16951
17222
|
// `import.meta` and `new.target` contain syntax tokens, not free
|
|
16952
17223
|
// bindings. Visiting their Identifier children creates invalid deps.
|
|
16953
17224
|
return;
|
|
16954
17225
|
case 'Identifier':
|
|
16955
|
-
if (isFree(n, parent, key)) push(b.id(n.name), depPathKey(n));
|
|
17226
|
+
if (isFree(n, parent, key) && !deferredAmbient(n.name)) push(b.id(n.name), depPathKey(n));
|
|
16956
17227
|
return;
|
|
16957
17228
|
case 'UnaryExpression': {
|
|
16958
17229
|
const argument = unwrapTsExpr(n.argument);
|
|
@@ -16969,14 +17240,14 @@ function collectDepPaths(expr, coarsenDepRoots = null) {
|
|
|
16969
17240
|
case 'MemberExpression': {
|
|
16970
17241
|
const propertyName = staticDepMemberName(n);
|
|
16971
17242
|
if (n.object.type === 'Identifier' && propertyName !== null) {
|
|
16972
|
-
if (isFree(n.object, n, 'object')) {
|
|
17243
|
+
if (isFree(n.object, n, 'object') && !deferredAmbient(n.object.name)) {
|
|
16973
17244
|
const member = b.member(
|
|
16974
17245
|
b.id(n.object.name),
|
|
16975
17246
|
n.computed
|
|
16976
17247
|
? b.literal(propertyName, JSON.stringify(propertyName))
|
|
16977
17248
|
: b.id(propertyName),
|
|
16978
17249
|
n.computed,
|
|
16979
|
-
n.optional === true,
|
|
17250
|
+
n.optional === true || deferred > 0,
|
|
16980
17251
|
);
|
|
16981
17252
|
// Optional MemberExpressions must remain inside a ChainExpression.
|
|
16982
17253
|
// Besides keeping the synthesized tree valid ESTree, the wrapper
|
|
@@ -16993,6 +17264,19 @@ function collectDepPaths(expr, coarsenDepRoots = null) {
|
|
|
16993
17264
|
}
|
|
16994
17265
|
}
|
|
16995
17266
|
|
|
17267
|
+
// Scope-aware module-bound check: returns a function `(name) => boolean` that
|
|
17268
|
+
// resolves through the scope chain at `exprNode`'s position in the module AST.
|
|
17269
|
+
// Nested parameters and catch bindings from unrelated functions are invisible,
|
|
17270
|
+
// so a shadowed global (e.g. `function fmt(document)`) does not trick
|
|
17271
|
+
// deferred-ambient into emitting an eager dep for an undeclared name.
|
|
17272
|
+
function moduleBoundCheckForDeps(ctx, exprNode) {
|
|
17273
|
+
if (ctx.activityModuleAst == null) return null;
|
|
17274
|
+
const lexical = (ctx.activityLexical ??= createLexicalAnalysis(ctx.activityModuleAst));
|
|
17275
|
+
const scope = lexical.nodeScopes.get(exprNode);
|
|
17276
|
+
if (scope === undefined) return null;
|
|
17277
|
+
return (name) => lexical.isBound(scope, name);
|
|
17278
|
+
}
|
|
17279
|
+
|
|
16996
17280
|
// A `use()` argument that needs no memoization: already-stable references
|
|
16997
17281
|
// (identifiers, static member chains) and literals.
|
|
16998
17282
|
function isTrivialUseArg(n) {
|
|
@@ -17228,7 +17512,7 @@ function makeCreationMemoCall(
|
|
|
17228
17512
|
// promise — and the derived creation would never refresh when its upstream
|
|
17229
17513
|
// promise does. Coarsen member deps rooted at render-created locals to the
|
|
17230
17514
|
// bare identifier (dedup follows).
|
|
17231
|
-
const deps = collectDepPaths(expr, coarsenDepRoots);
|
|
17515
|
+
const deps = collectDepPaths(expr, coarsenDepRoots, moduleBoundCheckForDeps(ctx, expr));
|
|
17232
17516
|
// Server mirror: `puMemo` — keyed CROSS-PASS creation cache (a fresh
|
|
17233
17517
|
// SSRScope per pass makes client useMemo semantics useless there).
|
|
17234
17518
|
const memoHelper = ctx.nativeReads
|
|
@@ -17938,7 +18222,7 @@ function parallelUseWalkJsx(nodes, ctx, componentName, creations, warmChildren,
|
|
|
17938
18222
|
kind: 'useMemo',
|
|
17939
18223
|
node: expr,
|
|
17940
18224
|
});
|
|
17941
|
-
const deps = collectDepPaths(expr);
|
|
18225
|
+
const deps = collectDepPaths(expr, null, moduleBoundCheckForDeps(ctx, expr));
|
|
17942
18226
|
const memoAlias = requireRuntimeForContext(ctx, ctx.nativeReads ? 'nativePuMemo' : 'puMemo');
|
|
17943
18227
|
changed = true;
|
|
17944
18228
|
// The minted prop-memo wrapper maps to the authored prop expression.
|
|
@@ -23512,9 +23796,10 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
23512
23796
|
const primitiveProofs = new Set();
|
|
23513
23797
|
const intrinsicCalls = [];
|
|
23514
23798
|
const writes = [];
|
|
23515
|
-
|
|
23516
|
-
const
|
|
23517
|
-
const
|
|
23799
|
+
const mutatorReferences = [];
|
|
23800
|
+
const destructuredMutators = new WeakSet();
|
|
23801
|
+
const globalValueUses = [];
|
|
23802
|
+
let globalValueDestructured = false;
|
|
23518
23803
|
const ambientIntrinsicValues = new Set();
|
|
23519
23804
|
const constantDeclarations = [];
|
|
23520
23805
|
const references = [];
|
|
@@ -23557,38 +23842,38 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
23557
23842
|
if (primitiveRanges?.delete(range)) primitiveProofs.add(expr);
|
|
23558
23843
|
}
|
|
23559
23844
|
if (inspectIntrinsicCalls) {
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
(node.type === 'Property' && parent?.type === 'ObjectPattern')
|
|
23563
|
-
) {
|
|
23564
|
-
const property = node.type === 'MemberExpression' ? node.property : node.key;
|
|
23565
|
-
const method = node.computed ? property?.value : (property?.name ?? property?.value);
|
|
23566
|
-
if (
|
|
23567
|
-
INTRINSIC_MUTATION_METHODS.has(method) ||
|
|
23568
|
-
(node.computed && property?.type !== 'Literal')
|
|
23569
|
-
) {
|
|
23570
|
-
// Unknown computed references may extract a mutator or sit under a
|
|
23571
|
-
// TypeScript/optional-chain wrapper before invocation. Decline the new
|
|
23572
|
-
// intrinsic-local proof even for unrelated dynamic property reads;
|
|
23573
|
-
// this is rejection only, not built-in name admission.
|
|
23574
|
-
intrinsicMutationReference = true;
|
|
23575
|
-
}
|
|
23576
|
-
}
|
|
23577
|
-
if (
|
|
23578
|
-
node.type === 'VariableDeclarator' &&
|
|
23579
|
-
parent?.type === 'VariableDeclaration' &&
|
|
23580
|
-
parent.kind === 'const' &&
|
|
23581
|
-
node.id?.type === 'Identifier' &&
|
|
23582
|
-
node.init
|
|
23583
|
-
) {
|
|
23584
|
-
intrinsicAliases.push(node);
|
|
23585
|
-
}
|
|
23845
|
+
// Mutator references: `Reflect.set`, `obj[key]`, `const { set } = R`.
|
|
23846
|
+
// The receiver decides later whether one can reach a text intrinsic.
|
|
23586
23847
|
if (node.type === 'MemberExpression' && isPossibleIntrinsicMutator(node)) {
|
|
23587
|
-
|
|
23848
|
+
mutatorReferences.push({ reference: node, receiver: node.object, parent });
|
|
23588
23849
|
} else if (node.type === 'VariableDeclarator' && node.id?.type === 'ObjectPattern') {
|
|
23589
23850
|
for (const property of node.id.properties || []) {
|
|
23590
23851
|
if (property.type === 'Property' && isPossibleIntrinsicMutator(property)) {
|
|
23591
|
-
|
|
23852
|
+
destructuredMutators.add(property);
|
|
23853
|
+
mutatorReferences.push({ reference: property, receiver: node.init, parent: null });
|
|
23854
|
+
}
|
|
23855
|
+
}
|
|
23856
|
+
} else if (
|
|
23857
|
+
node.type === 'Property' &&
|
|
23858
|
+
parent?.type === 'ObjectPattern' &&
|
|
23859
|
+
!destructuredMutators.has(node)
|
|
23860
|
+
) {
|
|
23861
|
+
// Parameter/assignment patterns hide their source: fail closed.
|
|
23862
|
+
if (isPossibleIntrinsicMutator(node)) {
|
|
23863
|
+
mutatorReferences.push({ reference: node, receiver: null, parent: null });
|
|
23864
|
+
}
|
|
23865
|
+
if (GLOBAL_VALUE_MEMBERS.has(staticPropertyKeyName(node))) globalValueDestructured = true;
|
|
23866
|
+
}
|
|
23867
|
+
// Where a global object, Object, or Reflect flows as a value, a later
|
|
23868
|
+
// alias can reach it; wrapper parents defer to the outermost wrapper.
|
|
23869
|
+
if (!TRANSPARENT_VALUE_WRAPPERS.has(parent?.type)) {
|
|
23870
|
+
const value = unwrapValueExpression(node);
|
|
23871
|
+
if (
|
|
23872
|
+
(value?.type === 'Identifier' && GLOBAL_VALUE_NAMES.has(value.name)) ||
|
|
23873
|
+
(value?.type === 'MemberExpression' && GLOBAL_VALUE_NAMES.has(staticMemberName(value)))
|
|
23874
|
+
) {
|
|
23875
|
+
if (!isNonEscapingValueUse(parent, key)) {
|
|
23876
|
+
globalValueUses.push({ node, value, parent, key });
|
|
23592
23877
|
}
|
|
23593
23878
|
}
|
|
23594
23879
|
}
|
|
@@ -23637,165 +23922,52 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
23637
23922
|
);
|
|
23638
23923
|
}
|
|
23639
23924
|
let lexical = null;
|
|
23925
|
+
// One decision guards every intrinsic-derived proof: inline calls, supplied
|
|
23926
|
+
// type facts, and primitive locals. TypeScript keeps a built-in's declared
|
|
23927
|
+
// primitive return type after an asserted replacement, so once a mutation
|
|
23928
|
+
// is visible none of those proofs survive. A write naming a text intrinsic
|
|
23929
|
+
// is always visible; mutator and computed references are visible when
|
|
23930
|
+
// their receiver is a global object, Object, or Reflect, or whenever such a
|
|
23931
|
+
// value escapes into a binding, argument, return, or property where an
|
|
23932
|
+
// alias could reach it. Values from other modules stay outside this proof.
|
|
23933
|
+
let mutationVisible = false;
|
|
23640
23934
|
if (
|
|
23641
|
-
intrinsicCalls.length > 0 ||
|
|
23642
|
-
(
|
|
23643
|
-
(writes.length > 0 || intrinsicMutationReferences.length > 0))
|
|
23935
|
+
(intrinsicCalls.length > 0 || stringProofs.size > 0 || primitiveProofs.size > 0) &&
|
|
23936
|
+
(writes.length > 0 || mutatorReferences.length > 0)
|
|
23644
23937
|
) {
|
|
23645
|
-
// The existing scope analysis understands var hoisting, parameter defaults,
|
|
23646
|
-
// imports, catch bindings, and TSRX @for/@try scopes. Pay for it only when
|
|
23647
|
-
// an intrinsic candidate or a possible proof-invalidating write needs it.
|
|
23648
23938
|
lexical = createLexicalAnalysis(ast);
|
|
23649
|
-
|
|
23650
|
-
|
|
23651
|
-
|
|
23652
|
-
|
|
23653
|
-
|
|
23654
|
-
|
|
23655
|
-
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
23660
|
-
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23667
|
-
|
|
23668
|
-
|
|
23669
|
-
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
return scope === undefined ? undefined : aliases.get(scope)?.get(node.name);
|
|
23673
|
-
};
|
|
23674
|
-
const intrinsicMemberName = (node) => {
|
|
23675
|
-
const property = node.computed ? unwrapTsExpr(node.property) : node.property;
|
|
23676
|
-
return node.computed
|
|
23677
|
-
? property?.type === 'Literal' && typeof property.value === 'string'
|
|
23678
|
-
? property.value
|
|
23679
|
-
: null
|
|
23680
|
-
: property?.type === 'Identifier'
|
|
23681
|
-
? property.name
|
|
23682
|
-
: null;
|
|
23683
|
-
};
|
|
23684
|
-
const globalOrigin = (expression, seen = new Set(), constructors = false) => {
|
|
23685
|
-
const node = unwrapTsExpr(expression);
|
|
23686
|
-
if (!node || seen.has(node)) return null;
|
|
23687
|
-
seen.add(node);
|
|
23688
|
-
if (node.type === 'Identifier') {
|
|
23689
|
-
const scope = lexical.nodeScopes.get(node);
|
|
23690
|
-
if (scope === undefined) return null;
|
|
23691
|
-
if (!lexical.isBound(scope, node.name)) {
|
|
23692
|
-
return INLINE_INTRINSIC_MUTATION_GLOBALS.has(node.name) ||
|
|
23693
|
-
(constructors && TEXT_INTRINSICS.has(node.name))
|
|
23694
|
-
? node.name
|
|
23695
|
-
: null;
|
|
23696
|
-
}
|
|
23697
|
-
return globalOrigin(initializer(node), seen, constructors);
|
|
23698
|
-
}
|
|
23699
|
-
if (node.type === 'ChainExpression') return globalOrigin(node.expression, seen, constructors);
|
|
23700
|
-
if (node.type === 'MemberExpression') {
|
|
23701
|
-
const name = intrinsicMemberName(node);
|
|
23702
|
-
if (name !== 'Object' && name !== 'Reflect' && !(constructors && TEXT_INTRINSICS.has(name)))
|
|
23703
|
-
return null;
|
|
23704
|
-
const origin = globalOrigin(node.object, seen);
|
|
23705
|
-
return origin !== null && INLINE_INTRINSIC_GLOBAL_RECEIVERS.has(origin) ? name : null;
|
|
23706
|
-
}
|
|
23707
|
-
return null;
|
|
23708
|
-
};
|
|
23709
|
-
const freshTarget = (expression, seen = new Set()) => {
|
|
23710
|
-
const node = unwrapTsExpr(expression);
|
|
23711
|
-
if (!node || seen.has(node)) return false;
|
|
23712
|
-
seen.add(node);
|
|
23713
|
-
if (node.type === 'ObjectExpression' || node.type === 'ArrayExpression') return true;
|
|
23714
|
-
return node.type === 'Identifier' && freshTarget(initializer(node), seen);
|
|
23715
|
-
};
|
|
23716
|
-
const visibleMutation =
|
|
23717
|
-
intrinsicMutationReferences.some(([receiver, reference, parent]) => {
|
|
23718
|
-
const origin = globalOrigin(receiver);
|
|
23719
|
-
const key = reference.type === 'Property' ? reference.key : reference.property;
|
|
23720
|
-
const property = reference.computed ? unwrapTsExpr(key) : key;
|
|
23721
|
-
const method = reference.computed ? property?.value : (property?.name ?? property?.value);
|
|
23722
|
-
if (origin === 'Object' || origin === 'Reflect') {
|
|
23723
|
-
// A normal native call targeting a fresh literal cannot directly write
|
|
23724
|
-
// a global constructor. Unknown getter/callback effects retain the
|
|
23725
|
-
// existing builtin assumptions; wrapped/extracted calls fail closed.
|
|
23726
|
-
const target = parent?.arguments?.[0];
|
|
23727
|
-
return !(
|
|
23728
|
-
parent?.type === 'CallExpression' &&
|
|
23729
|
-
parent.optional !== true &&
|
|
23730
|
-
parent.callee === reference &&
|
|
23731
|
-
reference.optional !== true &&
|
|
23732
|
-
INLINE_INTRINSIC_MUTATION_METHODS.has(method) &&
|
|
23733
|
-
freshTarget(target)
|
|
23734
|
-
);
|
|
23735
|
-
}
|
|
23736
|
-
return (
|
|
23737
|
-
origin !== null &&
|
|
23738
|
-
INLINE_INTRINSIC_GLOBAL_RECEIVERS.has(origin) &&
|
|
23739
|
-
(method === '__defineGetter__' ||
|
|
23740
|
-
method === '__defineSetter__' ||
|
|
23741
|
-
(reference.computed && property?.type !== 'Literal'))
|
|
23742
|
-
);
|
|
23743
|
-
}) ||
|
|
23744
|
-
writes.some((target) => {
|
|
23745
|
-
const node = unwrapTsExpr(target);
|
|
23746
|
-
if (node?.type !== 'MemberExpression') return false;
|
|
23747
|
-
const name = node.computed ? node.property?.value : node.property?.name;
|
|
23748
|
-
if (!TEXT_INTRINSICS.has(name) && !(node.computed && node.property?.type !== 'Literal'))
|
|
23749
|
-
return false;
|
|
23750
|
-
const origin = globalOrigin(node.object);
|
|
23751
|
-
return origin !== null && INLINE_INTRINSIC_GLOBAL_RECEIVERS.has(origin);
|
|
23752
|
-
});
|
|
23753
|
-
const unsafeCalls = new WeakSet();
|
|
23939
|
+
mutationVisible = isTextIntrinsicMutationVisible(
|
|
23940
|
+
writes,
|
|
23941
|
+
mutatorReferences,
|
|
23942
|
+
globalValueDestructured ||
|
|
23943
|
+
globalValueUses.some(
|
|
23944
|
+
({ node, value, parent, key }) =>
|
|
23945
|
+
isGlobalValue(value, lexical) &&
|
|
23946
|
+
(value !== node ||
|
|
23947
|
+
value.type !== 'Identifier' ||
|
|
23948
|
+
isIdentifierReference(node, parent, key, lexical)),
|
|
23949
|
+
),
|
|
23950
|
+
lexical,
|
|
23951
|
+
);
|
|
23952
|
+
}
|
|
23953
|
+
if (mutationVisible) {
|
|
23954
|
+
// Supplied ranges were still validated above; explicit text syntax keeps
|
|
23955
|
+
// its existing coercion meaning through isKnownStringExpression.
|
|
23956
|
+
stringProofs.clear();
|
|
23957
|
+
primitiveProofs.clear();
|
|
23958
|
+
} else if (intrinsicCalls.length > 0) {
|
|
23959
|
+
// The existing scope analysis understands var hoisting, parameter defaults,
|
|
23960
|
+
// imports, catch bindings, and TSRX @for/@try scopes.
|
|
23961
|
+
lexical ??= createLexicalAnalysis(ast);
|
|
23754
23962
|
for (const call of intrinsicCalls) {
|
|
23755
23963
|
const name = call.callee.name;
|
|
23756
|
-
if (ambientIntrinsicValues.has(name)) continue;
|
|
23964
|
+
if (ambientIntrinsicValues.has(name) || call.optional === true) continue;
|
|
23757
23965
|
const scope = lexical.nodeScopes.get(call);
|
|
23758
23966
|
if (scope !== undefined && !lexical.isBound(scope, name)) {
|
|
23759
|
-
if (visibleMutation) {
|
|
23760
|
-
unsafeCalls.add(call);
|
|
23761
|
-
continue;
|
|
23762
|
-
}
|
|
23763
|
-
if (call.optional === true) continue;
|
|
23764
23967
|
if (name === 'String' || name === 'Date') stringProofs.add(call);
|
|
23765
23968
|
else primitiveProofs.add(call);
|
|
23766
23969
|
}
|
|
23767
23970
|
}
|
|
23768
|
-
if (visibleMutation) {
|
|
23769
|
-
// TypeScript still describes the built-in return type after an asserted
|
|
23770
|
-
// replacement. Drop only supplied proofs that depend on these calls;
|
|
23771
|
-
// unrelated typed scalars and JavaScript's own coercion guarantees survive.
|
|
23772
|
-
const unsafeProof = (node, seen = new WeakSet()) => {
|
|
23773
|
-
if (!node || typeof node !== 'object' || seen.has(node)) return false;
|
|
23774
|
-
seen.add(node);
|
|
23775
|
-
if (unsafeCalls.has(node)) return true;
|
|
23776
|
-
if (node.type === 'CallExpression') {
|
|
23777
|
-
let callee = unwrapTsExpr(node.callee);
|
|
23778
|
-
while (callee?.type === 'ChainExpression') callee = unwrapTsExpr(callee.expression);
|
|
23779
|
-
if (callee?.type === 'MemberExpression') {
|
|
23780
|
-
const method = intrinsicMemberName(callee);
|
|
23781
|
-
if (method === 'call' || method === 'apply') callee = callee.object;
|
|
23782
|
-
}
|
|
23783
|
-
// Supplied facts may describe a saved constructor or its Function
|
|
23784
|
-
// call/apply result. Invalidation grants no new primitive admission.
|
|
23785
|
-
if (TEXT_INTRINSICS.has(globalOrigin(callee, new Set(), true))) return true;
|
|
23786
|
-
}
|
|
23787
|
-
if (node.type === 'Identifier' && unsafeProof(initializer(node), seen)) return true;
|
|
23788
|
-
if (Array.isArray(node)) return node.some((child) => unsafeProof(child, seen));
|
|
23789
|
-
for (const key in node) {
|
|
23790
|
-
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
23791
|
-
if (unsafeProof(node[key], seen)) return true;
|
|
23792
|
-
}
|
|
23793
|
-
return false;
|
|
23794
|
-
};
|
|
23795
|
-
for (const proofs of [stringProofs, primitiveProofs]) {
|
|
23796
|
-
for (const proof of proofs) if (unsafeProof(proof)) proofs.delete(proof);
|
|
23797
|
-
}
|
|
23798
|
-
}
|
|
23799
23971
|
}
|
|
23800
23972
|
const valueProofs = new Set();
|
|
23801
23973
|
if (
|
|
@@ -23805,11 +23977,8 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
23805
23977
|
constantDeclarations.some((declaration) => isPrimitiveValueExpression(declaration.init)))
|
|
23806
23978
|
) {
|
|
23807
23979
|
lexical ??= createLexicalAnalysis(ast);
|
|
23808
|
-
//
|
|
23809
|
-
|
|
23810
|
-
// operator/template primitives and existing child proofs remain.
|
|
23811
|
-
const intrinsicLocalResultsSafe =
|
|
23812
|
-
!intrinsicMutationReference && !writes.some(mayWriteTextIntrinsicMember);
|
|
23980
|
+
// Operator/template primitives stay provable after a visible mutation.
|
|
23981
|
+
const intrinsicLocalResultsSafe = !mutationVisible;
|
|
23813
23982
|
const reassigned = collectReassignedBindings(ast);
|
|
23814
23983
|
const declarationsByScope = new Map();
|
|
23815
23984
|
for (const declaration of constantDeclarations) {
|
|
@@ -23915,94 +24084,151 @@ const INTRINSIC_MUTATION_METHODS = new Set([
|
|
|
23915
24084
|
'setPrototypeOf',
|
|
23916
24085
|
'deleteProperty',
|
|
23917
24086
|
]);
|
|
24087
|
+
// Unbound names that may hold the global object, Object, or Reflect. Member
|
|
24088
|
+
// reads of these names reach the same values only through a global receiver
|
|
24089
|
+
// (`window.top`), except for the unambiguous names below (`x.Reflect`).
|
|
24090
|
+
const GLOBAL_OBJECT_NAMES = new Set([
|
|
24091
|
+
'globalThis',
|
|
24092
|
+
'window',
|
|
24093
|
+
'self',
|
|
24094
|
+
'global',
|
|
24095
|
+
'frames',
|
|
24096
|
+
'parent',
|
|
24097
|
+
'top',
|
|
24098
|
+
]);
|
|
24099
|
+
const GLOBAL_VALUE_NAMES = new Set([...GLOBAL_OBJECT_NAMES, 'Object', 'Reflect']);
|
|
24100
|
+
const GLOBAL_VALUE_MEMBERS = new Set(['globalThis', 'window', 'Object', 'Reflect']);
|
|
24101
|
+
const TRANSPARENT_VALUE_WRAPPERS = new Set([
|
|
24102
|
+
'TSAsExpression',
|
|
24103
|
+
'TSNonNullExpression',
|
|
24104
|
+
'TSTypeAssertion',
|
|
24105
|
+
'TSSatisfiesExpression',
|
|
24106
|
+
'ParenthesizedExpression',
|
|
24107
|
+
'ChainExpression',
|
|
24108
|
+
]);
|
|
23918
24109
|
|
|
23919
|
-
function
|
|
23920
|
-
|
|
23921
|
-
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
return (target.elements || []).some(mayWriteTextIntrinsicMember);
|
|
23931
|
-
}
|
|
23932
|
-
if (target.type === 'ObjectPattern') {
|
|
23933
|
-
return (target.properties || []).some((property) =>
|
|
23934
|
-
mayWriteTextIntrinsicMember(property.argument ?? property.value),
|
|
23935
|
-
);
|
|
24110
|
+
function unwrapValueExpression(node) {
|
|
24111
|
+
while (node && TRANSPARENT_VALUE_WRAPPERS.has(node.type)) node = node.expression;
|
|
24112
|
+
return node;
|
|
24113
|
+
}
|
|
24114
|
+
|
|
24115
|
+
function staticMemberName(node) {
|
|
24116
|
+
const property = node.computed ? unwrapTsExpr(node.property) : node.property;
|
|
24117
|
+
if (node.computed) {
|
|
24118
|
+
return property?.type === 'Literal' && typeof property.value === 'string'
|
|
24119
|
+
? property.value
|
|
24120
|
+
: null;
|
|
23936
24121
|
}
|
|
23937
|
-
return
|
|
24122
|
+
return property?.type === 'Identifier' ? property.name : null;
|
|
23938
24123
|
}
|
|
23939
24124
|
|
|
23940
|
-
|
|
23941
|
-
|
|
23942
|
-
'
|
|
23943
|
-
'
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
24125
|
+
function staticPropertyKeyName(property) {
|
|
24126
|
+
const key = property.computed ? unwrapTsExpr(property.key) : property.key;
|
|
24127
|
+
if (key?.type === 'Identifier' && !property.computed) return key.name;
|
|
24128
|
+
return key?.type === 'Literal' && typeof key.value === 'string' ? key.value : null;
|
|
24129
|
+
}
|
|
24130
|
+
|
|
24131
|
+
// A use that reads through, tests, or discards a value cannot alias it.
|
|
24132
|
+
function isNonEscapingValueUse(parent, key) {
|
|
24133
|
+
switch (parent?.type) {
|
|
24134
|
+
case 'MemberExpression':
|
|
24135
|
+
return key === 'object';
|
|
24136
|
+
case 'UnaryExpression':
|
|
24137
|
+
case 'BinaryExpression':
|
|
24138
|
+
case 'ExpressionStatement':
|
|
24139
|
+
return true;
|
|
24140
|
+
case 'CallExpression':
|
|
24141
|
+
case 'NewExpression':
|
|
24142
|
+
return key === 'callee';
|
|
24143
|
+
case 'IfStatement':
|
|
24144
|
+
case 'ConditionalExpression':
|
|
24145
|
+
case 'WhileStatement':
|
|
24146
|
+
case 'DoWhileStatement':
|
|
24147
|
+
case 'ForStatement':
|
|
24148
|
+
return key === 'test';
|
|
24149
|
+
default:
|
|
24150
|
+
return false;
|
|
24151
|
+
}
|
|
24152
|
+
}
|
|
24153
|
+
|
|
24154
|
+
function isGlobalValue(expression, lexical) {
|
|
24155
|
+
const node = unwrapValueExpression(expression);
|
|
24156
|
+
if (node?.type === 'Identifier') {
|
|
24157
|
+
if (!GLOBAL_VALUE_NAMES.has(node.name)) return false;
|
|
24158
|
+
const scope = lexical.nodeScopes.get(node);
|
|
24159
|
+
return scope !== undefined && !lexical.isBound(scope, node.name);
|
|
24160
|
+
}
|
|
24161
|
+
if (node?.type !== 'MemberExpression') return false;
|
|
24162
|
+
const name = staticMemberName(node);
|
|
24163
|
+
return (
|
|
24164
|
+
GLOBAL_VALUE_MEMBERS.has(name) ||
|
|
24165
|
+
(GLOBAL_VALUE_NAMES.has(name) && isGlobalValue(node.object, lexical))
|
|
24166
|
+
);
|
|
24167
|
+
}
|
|
23956
24168
|
|
|
23957
24169
|
// Inspect the reference itself: TS/optional wrappers and extracted methods may
|
|
23958
|
-
// hide its eventual call.
|
|
23959
|
-
// and application .set methods from invalidating intrinsic-result proofs.
|
|
24170
|
+
// hide its eventual call.
|
|
23960
24171
|
function isPossibleIntrinsicMutator(node) {
|
|
23961
24172
|
const key = node.type === 'Property' ? node.key : node.property;
|
|
23962
24173
|
const property = node.computed ? unwrapTsExpr(key) : key;
|
|
23963
24174
|
const name = node.computed ? property?.value : (property?.name ?? property?.value);
|
|
23964
|
-
return (
|
|
23965
|
-
INLINE_INTRINSIC_MUTATION_METHODS.has(name) || (node.computed && property?.type !== 'Literal')
|
|
23966
|
-
);
|
|
24175
|
+
return INTRINSIC_MUTATION_METHODS.has(name) || (node.computed && property?.type !== 'Literal');
|
|
23967
24176
|
}
|
|
23968
24177
|
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
// may return an element rather than the built-in's primitive result.
|
|
23972
|
-
function writesGlobalTextIntrinsic(target, lexical) {
|
|
23973
|
-
target = unwrapTsExpr(target);
|
|
23974
|
-
if (!target || typeof target !== 'object') return false;
|
|
23975
|
-
const unbound = (node, name) => {
|
|
24178
|
+
function isTextIntrinsicMutationVisible(writes, mutatorReferences, globalValueEscapes, lexical) {
|
|
24179
|
+
const unboundIntrinsic = (node) => {
|
|
23976
24180
|
const scope = lexical.nodeScopes.get(node);
|
|
23977
|
-
return scope !== undefined && !lexical.isBound(scope, name);
|
|
23978
|
-
};
|
|
23979
|
-
if (target.type === 'Identifier') {
|
|
23980
|
-
return TEXT_INTRINSICS.has(target.name) && unbound(target, target.name);
|
|
23981
|
-
}
|
|
23982
|
-
if (target.type === 'MemberExpression') {
|
|
23983
|
-
const name = target.computed ? target.property?.value : target.property?.name;
|
|
23984
|
-
const object = target.object;
|
|
23985
24181
|
return (
|
|
23986
|
-
TEXT_INTRINSICS.has(name) &&
|
|
23987
|
-
object?.type === 'Identifier' &&
|
|
23988
|
-
(object.name === 'globalThis' ||
|
|
23989
|
-
object.name === 'window' ||
|
|
23990
|
-
object.name === 'self' ||
|
|
23991
|
-
object.name === 'global') &&
|
|
23992
|
-
unbound(object, object.name)
|
|
24182
|
+
TEXT_INTRINSICS.has(node.name) && scope !== undefined && !lexical.isBound(scope, node.name)
|
|
23993
24183
|
);
|
|
23994
|
-
}
|
|
23995
|
-
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
|
|
23999
|
-
|
|
24000
|
-
|
|
24001
|
-
|
|
24002
|
-
|
|
24184
|
+
};
|
|
24185
|
+
const mayReachIntrinsic = (receiver) =>
|
|
24186
|
+
receiver == null || globalValueEscapes || isGlobalValue(receiver, lexical);
|
|
24187
|
+
const writesIntrinsic = (target) => {
|
|
24188
|
+
target = unwrapTsExpr(target);
|
|
24189
|
+
if (!target || typeof target !== 'object') return false;
|
|
24190
|
+
if (target.type === 'Identifier') return unboundIntrinsic(target);
|
|
24191
|
+
if (target.type === 'MemberExpression') {
|
|
24192
|
+
// A named write is visible on any receiver; a computed key only when
|
|
24193
|
+
// its receiver can be a global object.
|
|
24194
|
+
const property = target.computed ? unwrapTsExpr(target.property) : target.property;
|
|
24195
|
+
if (!target.computed) return TEXT_INTRINSICS.has(property?.name);
|
|
24196
|
+
if (property?.type === 'Literal') return TEXT_INTRINSICS.has(property.value);
|
|
24197
|
+
return mayReachIntrinsic(target.object);
|
|
24198
|
+
}
|
|
24199
|
+
if (target.type === 'AssignmentPattern') return writesIntrinsic(target.left);
|
|
24200
|
+
if (target.type === 'RestElement') return writesIntrinsic(target.argument);
|
|
24201
|
+
if (target.type === 'ArrayPattern') return (target.elements || []).some(writesIntrinsic);
|
|
24202
|
+
if (target.type === 'ObjectPattern') {
|
|
24203
|
+
return (target.properties || []).some((property) =>
|
|
24204
|
+
writesIntrinsic(property.argument ?? property.value),
|
|
24205
|
+
);
|
|
24206
|
+
}
|
|
24207
|
+
return false;
|
|
24208
|
+
};
|
|
24209
|
+
if (writes.some(writesIntrinsic)) return true;
|
|
24210
|
+
return mutatorReferences.some(({ reference, receiver, parent }) => {
|
|
24211
|
+
const key = reference.type === 'Property' ? reference.key : reference.property;
|
|
24212
|
+
const property = reference.computed ? unwrapTsExpr(key) : key;
|
|
24213
|
+
const method = reference.computed ? property?.value : (property?.name ?? property?.value);
|
|
24214
|
+
// These install accessors on whichever object receives them.
|
|
24215
|
+
if (method === '__defineGetter__' || method === '__defineSetter__') return true;
|
|
24216
|
+
if (!mayReachIntrinsic(receiver)) return false;
|
|
24217
|
+
if (globalValueEscapes || receiver == null) return true;
|
|
24218
|
+
const target = unwrapValueExpression(receiver);
|
|
24219
|
+
const receiverName = target.type === 'Identifier' ? target.name : staticMemberName(target);
|
|
24220
|
+
if (receiverName !== 'Object' && receiverName !== 'Reflect') return true;
|
|
24221
|
+
// A direct native call whose arguments hold no global value (any such
|
|
24222
|
+
// argument escapes) cannot target a global object. Wrapped, optional,
|
|
24223
|
+
// and extracted methods hide their eventual target.
|
|
24224
|
+
return !(
|
|
24225
|
+
parent?.type === 'CallExpression' &&
|
|
24226
|
+
parent.callee === reference &&
|
|
24227
|
+
parent.optional !== true &&
|
|
24228
|
+
reference.optional !== true &&
|
|
24229
|
+
INTRINSIC_MUTATION_METHODS.has(method)
|
|
24003
24230
|
);
|
|
24004
|
-
}
|
|
24005
|
-
return false;
|
|
24231
|
+
});
|
|
24006
24232
|
}
|
|
24007
24233
|
|
|
24008
24234
|
// Syntax/annotation proof shared by text bindings and non-text optimizations.
|
|
@@ -24321,18 +24547,62 @@ const TS_TYPE_PROPS = [
|
|
|
24321
24547
|
'declare', // `declare` modifier
|
|
24322
24548
|
'override', // class member `override`
|
|
24323
24549
|
'implements', // `class X implements I` list
|
|
24550
|
+
'abstract', // `abstract class X`
|
|
24324
24551
|
];
|
|
24325
24552
|
|
|
24553
|
+
// Class members with no runtime existence: index signatures, abstract members,
|
|
24554
|
+
// and method/constructor overload signatures (a body-less function value).
|
|
24555
|
+
// The native parser has TSAbstract* node types and TSEmptyBodyFunctionExpression
|
|
24556
|
+
// values; the JavaScript parser flags ordinary members `abstract` and gives
|
|
24557
|
+
// overloads a TSDeclareMethod value.
|
|
24558
|
+
function isTypeOnlyClassElement(node) {
|
|
24559
|
+
if (node == null) return false;
|
|
24560
|
+
switch (node.type) {
|
|
24561
|
+
case 'TSIndexSignature':
|
|
24562
|
+
case 'TSAbstractMethodDefinition':
|
|
24563
|
+
case 'TSAbstractPropertyDefinition':
|
|
24564
|
+
case 'TSAbstractAccessorProperty':
|
|
24565
|
+
return true;
|
|
24566
|
+
case 'MethodDefinition':
|
|
24567
|
+
return (
|
|
24568
|
+
node.abstract === true ||
|
|
24569
|
+
node.value?.type === 'TSEmptyBodyFunctionExpression' ||
|
|
24570
|
+
node.value?.type === 'TSDeclareMethod'
|
|
24571
|
+
);
|
|
24572
|
+
case 'PropertyDefinition':
|
|
24573
|
+
case 'AccessorProperty':
|
|
24574
|
+
return node.abstract === true;
|
|
24575
|
+
}
|
|
24576
|
+
return false;
|
|
24577
|
+
}
|
|
24578
|
+
|
|
24326
24579
|
// Copy-on-write: stripped shapes are shallow copies; nodes with no TS-only
|
|
24327
24580
|
// surface are returned by reference, so printing an already-plain subtree
|
|
24328
24581
|
// allocates nothing and the (possibly parser-owned, frozen-under-tests) input
|
|
24329
|
-
// is never written to.
|
|
24330
|
-
|
|
24582
|
+
// is never written to. TypeScript declarations with runtime semantics (enums,
|
|
24583
|
+
// value namespaces, import aliases, parameter properties) are lowered here too,
|
|
24584
|
+
// so every emitted module is plain JavaScript (see typescript-lowering.js).
|
|
24585
|
+
// `env` is `{ filename, enums }`: the filename for diagnostics and the enum
|
|
24586
|
+
// values visible from enclosing statement lists (for constant folding);
|
|
24587
|
+
// `topLevel` marks the Program body.
|
|
24588
|
+
function stripTsOnlyWrappers(node, env, topLevel = false) {
|
|
24331
24589
|
if (node === null || typeof node !== 'object') return node;
|
|
24332
24590
|
if (Array.isArray(node)) {
|
|
24333
24591
|
let out = null;
|
|
24334
24592
|
for (let i = 0; i < node.length; i++) {
|
|
24335
24593
|
const item = node[i];
|
|
24594
|
+
// A statement list holding an enum/namespace/import alias is lowered as
|
|
24595
|
+
// a whole: declaration merging and namespace exports span its items.
|
|
24596
|
+
if (needsTypeScriptStatementLowering(item)) {
|
|
24597
|
+
return lowerTypeScriptStatements(node, {
|
|
24598
|
+
topLevel,
|
|
24599
|
+
filename: env.filename,
|
|
24600
|
+
enums: env.enums,
|
|
24601
|
+
isTypeOnlyStatement,
|
|
24602
|
+
strip: (statement, enums) =>
|
|
24603
|
+
stripTsOnlyWrappers(statement, { filename: env.filename, enums }),
|
|
24604
|
+
});
|
|
24605
|
+
}
|
|
24336
24606
|
// Type-only STATEMENTS nested below module scope (a `type X = …` or
|
|
24337
24607
|
// `interface I {}` inside a function body) never hit the top-level
|
|
24338
24608
|
// `ast.body` filter, and stripping their annotations below would
|
|
@@ -24341,11 +24611,11 @@ function stripTsOnlyWrappers(node) {
|
|
|
24341
24611
|
// statement-only, so pruning from any AST array is safe (sparse
|
|
24342
24612
|
// ArrayExpression holes are `null` and isTypeOnlyStatement keeps
|
|
24343
24613
|
// them). Checked BEFORE the per-node strip so `declare` is intact.
|
|
24344
|
-
if (isTypeOnlyStatement(item)) {
|
|
24614
|
+
if (isTypeOnlyStatement(item) || isTypeOnlyClassElement(item)) {
|
|
24345
24615
|
if (out === null) out = node.slice(0, i);
|
|
24346
24616
|
continue;
|
|
24347
24617
|
}
|
|
24348
|
-
const mapped = stripTsOnlyWrappers(item);
|
|
24618
|
+
const mapped = stripTsOnlyWrappers(item, env);
|
|
24349
24619
|
if (out === null && mapped !== item) out = node.slice(0, i);
|
|
24350
24620
|
if (out !== null) out.push(mapped);
|
|
24351
24621
|
}
|
|
@@ -24358,7 +24628,10 @@ function stripTsOnlyWrappers(node) {
|
|
|
24358
24628
|
node.type === 'TSSatisfiesExpression' ||
|
|
24359
24629
|
node.type === 'TSInstantiationExpression'
|
|
24360
24630
|
) {
|
|
24361
|
-
return stripTsOnlyWrappers(node.expression);
|
|
24631
|
+
return stripTsOnlyWrappers(node.expression, env);
|
|
24632
|
+
}
|
|
24633
|
+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
24634
|
+
node = lowerParameterProperties(node, env.filename);
|
|
24362
24635
|
}
|
|
24363
24636
|
let out = null;
|
|
24364
24637
|
// A TS `this` parameter (`function f(this: Foo, …)`) is type-only and is
|
|
@@ -24405,7 +24678,7 @@ function stripTsOnlyWrappers(node) {
|
|
|
24405
24678
|
continue;
|
|
24406
24679
|
const child = (out ?? node)[key];
|
|
24407
24680
|
if (child === null || typeof child !== 'object') continue;
|
|
24408
|
-
const mapped = stripTsOnlyWrappers(child);
|
|
24681
|
+
const mapped = stripTsOnlyWrappers(child, env, key === 'body' && node.type === 'Program');
|
|
24409
24682
|
if (mapped !== child) {
|
|
24410
24683
|
if (out === null) out = { ...node };
|
|
24411
24684
|
out[key] = mapped;
|
|
@@ -25237,6 +25510,8 @@ function planJsx(
|
|
|
25237
25510
|
if (signalHelper !== null) {
|
|
25238
25511
|
ctx.runtimeNeeded.add(signalHelper);
|
|
25239
25512
|
registerAttrLoweringOrigin(ctx, b.nameOrigin, signalHelper, b.name);
|
|
25513
|
+
if (signalHelper === 'bindSignalAttribute' && b.kind !== 'class')
|
|
25514
|
+
ctx.runtimeNeeded.add(attrBindingHelper(b));
|
|
25240
25515
|
}
|
|
25241
25516
|
if (!b.signalDirect && (b.kind === 'text' || b.kind === 'textOnlyChild')) {
|
|
25242
25517
|
ctx.runtimeNeeded.add('setText');
|
|
@@ -25718,10 +25993,10 @@ function planJsx(
|
|
|
25718
25993
|
? 'fastMapSlot'
|
|
25719
25994
|
: 'mapSlot'
|
|
25720
25995
|
: fc.keyedSelectionIndex >= 0
|
|
25721
|
-
? fc.hostMountSafe
|
|
25996
|
+
? fc.hostMountSafe && fc.signalSite === null
|
|
25722
25997
|
? 'fastKeyedForBlock'
|
|
25723
25998
|
: 'keyedForBlock'
|
|
25724
|
-
: fc.hostMountSafe
|
|
25999
|
+
: fc.hostMountSafe && fc.signalSite === null
|
|
25725
26000
|
? 'fastForBlock'
|
|
25726
26001
|
: 'forBlock';
|
|
25727
26002
|
ctx.runtimeNeeded.add(forHelper);
|
|
@@ -25832,6 +26107,12 @@ function planJsx(
|
|
|
25832
26107
|
while (tailArgs.length < 7) tailArgs.push(undefinedNode());
|
|
25833
26108
|
tailArgs.push(helperRefNode(fc.selectionHelper));
|
|
25834
26109
|
}
|
|
26110
|
+
if (fc.signalSite !== null) {
|
|
26111
|
+
while (tailArgs.length < (fc.keyedSelectionIndex >= 0 ? 8 : 7)) {
|
|
26112
|
+
tailArgs.push(undefinedNode());
|
|
26113
|
+
}
|
|
26114
|
+
tailArgs.push(b.literal(fc.signalSite));
|
|
26115
|
+
}
|
|
25835
26116
|
if (isMappedList) {
|
|
25836
26117
|
const nativeName = allocCompilerName(ctx, '__mapNative');
|
|
25837
26118
|
if (fc.autoMemoDeps !== null) {
|
|
@@ -26951,10 +27232,19 @@ function directSignalBindingArgs(bind, host, previous, value = bind.expr, previo
|
|
|
26951
27232
|
attrLoweringToken(b.literal(bind.name), bind),
|
|
26952
27233
|
bind.expr,
|
|
26953
27234
|
b.literal(bind.signalSite, JSON.stringify(bind.signalSite)),
|
|
26954
|
-
|
|
27235
|
+
directSignalAttributeWriter(bind),
|
|
26955
27236
|
];
|
|
26956
27237
|
}
|
|
26957
27238
|
|
|
27239
|
+
// Class composition selects its writer from the element's namespace at runtime.
|
|
27240
|
+
// Every other attribute passes the writer its ordinary binding would call, so a
|
|
27241
|
+
// statically admitted name never retains the generic setAttribute route.
|
|
27242
|
+
function directSignalAttributeWriter(bind) {
|
|
27243
|
+
return bind.kind === 'class'
|
|
27244
|
+
? b.literal('class', JSON.stringify('class'))
|
|
27245
|
+
: b.id(`_$${attrBindingHelper(bind)}`);
|
|
27246
|
+
}
|
|
27247
|
+
|
|
26958
27248
|
function emitBindingMount(bind, elVar, bag) {
|
|
26959
27249
|
const knownExpression = resolveKnownAttributeSource(bind.expr, (source) =>
|
|
26960
27250
|
b.id(bag.local(`_prev$${source.binding.id}`)),
|
|
@@ -27073,7 +27363,16 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
27073
27363
|
return st(
|
|
27074
27364
|
b.block([
|
|
27075
27365
|
...mountHost(),
|
|
27076
|
-
b.stmt(
|
|
27366
|
+
b.stmt(
|
|
27367
|
+
b.call(
|
|
27368
|
+
bind.helper,
|
|
27369
|
+
b.id('__s'),
|
|
27370
|
+
b.literal(bind.slotIndex),
|
|
27371
|
+
el(),
|
|
27372
|
+
bind.expr,
|
|
27373
|
+
...(bind.literal ? [b.literal(1)] : []),
|
|
27374
|
+
),
|
|
27375
|
+
),
|
|
27077
27376
|
]),
|
|
27078
27377
|
);
|
|
27079
27378
|
}
|
|
@@ -27571,7 +27870,16 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
|
|
|
27571
27870
|
}
|
|
27572
27871
|
case 'nativeStyle': {
|
|
27573
27872
|
return st(
|
|
27574
|
-
b.stmt(
|
|
27873
|
+
b.stmt(
|
|
27874
|
+
b.call(
|
|
27875
|
+
bind.helper,
|
|
27876
|
+
b.id('__s'),
|
|
27877
|
+
b.literal(bind.slotIndex),
|
|
27878
|
+
F('_el'),
|
|
27879
|
+
bind.expr,
|
|
27880
|
+
...(bind.literal ? [b.literal(1)] : []),
|
|
27881
|
+
),
|
|
27882
|
+
),
|
|
27575
27883
|
);
|
|
27576
27884
|
}
|
|
27577
27885
|
case 'nativeChangeRuntime': {
|
|
@@ -29033,6 +29341,7 @@ function emitElementHtml(
|
|
|
29033
29341
|
bindings.push({
|
|
29034
29342
|
id: bindings.length,
|
|
29035
29343
|
kind: 'nativeStyle',
|
|
29344
|
+
literal: isPlainStyleObjectLiteral(inner),
|
|
29036
29345
|
expr: tsrxExprNode(inner, ctx, componentName, inlinedSubs),
|
|
29037
29346
|
path,
|
|
29038
29347
|
ns: hostNs,
|
|
@@ -30831,8 +31140,8 @@ function requireCompiledHydrateAlias(ctx) {
|
|
|
30831
31140
|
return alias;
|
|
30832
31141
|
}
|
|
30833
31142
|
|
|
30834
|
-
function
|
|
30835
|
-
if (
|
|
31143
|
+
function hydrateChildrenCaptures(node, children, ctx) {
|
|
31144
|
+
if (ctx.mode === 'server') return undefined;
|
|
30836
31145
|
const tag = node.openingElement?.name ?? node.id;
|
|
30837
31146
|
if (tag?.type !== 'JSXIdentifier' || ctx.octaneImportLocals?.get(tag.name) !== 'Hydrate')
|
|
30838
31147
|
return undefined;
|
|
@@ -30858,7 +31167,7 @@ function hydrateDiagnosticChildrenCaptures(node, children, ctx) {
|
|
|
30858
31167
|
for (const name of roots) covered.add(name);
|
|
30859
31168
|
}
|
|
30860
31169
|
for (const name of free) if (!covered.has(name)) captures.push(b.id(name));
|
|
30861
|
-
// Property reads stay deferred until activation;
|
|
31170
|
+
// Property reads stay deferred until activation; comparing captures
|
|
30862
31171
|
// must not evaluate a dormant child's getters or call its render function.
|
|
30863
31172
|
return inheritOriginLoc(b.arrow([], b.array(captures)), node);
|
|
30864
31173
|
}
|
|
@@ -31052,8 +31361,8 @@ function makeCompCall(
|
|
|
31052
31361
|
ctx.runtimeNeeded.add('markChildrenBlock');
|
|
31053
31362
|
hasChildrenProp = true;
|
|
31054
31363
|
const childrenArgs = [b.id(childrenHelperName)];
|
|
31055
|
-
const
|
|
31056
|
-
if ((ctx.autoMemo && ctx.mode !== 'server') ||
|
|
31364
|
+
const hydrateCaptures = hydrateChildrenCaptures(node, children, ctx);
|
|
31365
|
+
if ((ctx.autoMemo && ctx.mode !== 'server') || hydrateCaptures !== undefined) {
|
|
31057
31366
|
// These functions close over parent locals and are recreated on every
|
|
31058
31367
|
// render. A module-owned token distinguishes a real Provider body
|
|
31059
31368
|
// handoff from fresh captures without invalidating ordinary cache hits.
|
|
@@ -31061,8 +31370,7 @@ function makeCompCall(
|
|
|
31061
31370
|
ctx.hoistedHelpers.push(inheritOriginLoc(b.const(bodyIdentity, b.object([])), node));
|
|
31062
31371
|
childrenArgs.push(b.id(bodyIdentity));
|
|
31063
31372
|
}
|
|
31064
|
-
if (
|
|
31065
|
-
childrenArgs.push(diagnosticCaptures ?? b.literal(null));
|
|
31373
|
+
if (hydrateCaptures !== undefined) childrenArgs.push(hydrateCaptures ?? b.literal(null));
|
|
31066
31374
|
let childrenValue = b.call('_$markChildrenBlock', ...childrenArgs);
|
|
31067
31375
|
if (ctx.presentationHydration?.structural && node._octaneBindingSite)
|
|
31068
31376
|
childrenValue = b.call(
|
|
@@ -31835,10 +32143,21 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
31835
32143
|
if (ctx.currentComponentLocals) {
|
|
31836
32144
|
const bodyScope = new Set([itemName]);
|
|
31837
32145
|
if (node.index) bodyScope.add(node.index.name);
|
|
31838
|
-
|
|
32146
|
+
// A destructured header runs in the item helper's synthesized prologue, so
|
|
32147
|
+
// its defaults and computed keys are row reads too, and its fields bind
|
|
32148
|
+
// there rather than reading as free.
|
|
32149
|
+
const analyzedStmts =
|
|
32150
|
+
destructureInjection.length > 0 ? [...destructureInjection, ...subStmts] : subStmts;
|
|
32151
|
+
const bodyAst = b.block(analyzedStmts);
|
|
31839
32152
|
const free = collectFreeIdentifiers(bodyAst, bodyScope);
|
|
31840
32153
|
let hasParentClosure = false;
|
|
31841
32154
|
let hasHook = false;
|
|
32155
|
+
// PURE and DEP-PURE skip a survivor on item identity (plus the deps
|
|
32156
|
+
// tuple). A module `let` or a mutable global (`location.pathname`) can
|
|
32157
|
+
// change between parent renders with neither moving, so a row reading one
|
|
32158
|
+
// must re-render, exactly as the item-memo and whole-list proofs fail closed.
|
|
32159
|
+
let hasAmbientRead = false;
|
|
32160
|
+
let ambientCandidates = null;
|
|
31842
32161
|
const seenDeps = new Set();
|
|
31843
32162
|
for (const name of free) {
|
|
31844
32163
|
if (HOOK_NAMES.has(name) || name === 'use' || name === 'useContext') {
|
|
@@ -31850,8 +32169,20 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
31850
32169
|
seenDeps.add(name);
|
|
31851
32170
|
depNames.push(name);
|
|
31852
32171
|
}
|
|
32172
|
+
} else if (!ctx.importedNames.has(name) && isUnwitnessedAmbientRead(name, ctx)) {
|
|
32173
|
+
(ambientCandidates ??= []).push(name);
|
|
31853
32174
|
}
|
|
31854
32175
|
}
|
|
32176
|
+
if (ambientCandidates !== null) {
|
|
32177
|
+
// An inline event handler runs after commit, so a read confined to one
|
|
32178
|
+
// (`onClick={() => window.open(item.url)}`) never reaches row output.
|
|
32179
|
+
const renderFree = collectFreeIdentifiers(
|
|
32180
|
+
bodyAst,
|
|
32181
|
+
bodyScope,
|
|
32182
|
+
collectEventHandlerClosures(bodyAst),
|
|
32183
|
+
);
|
|
32184
|
+
hasAmbientRead = ambientCandidates.some((name) => renderFree.has(name));
|
|
32185
|
+
}
|
|
31855
32186
|
const hasNestedComp = containsComponentCallOrControlFlow(subStmts);
|
|
31856
32187
|
// Compatibility mode keeps method calls live: an unchanged receiver can
|
|
31857
32188
|
// hide mutable state (`header.column.getIsSorted()` flips while `header`
|
|
@@ -31859,7 +32190,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
31859
32190
|
// and admit every user-authored call shape. Both modes still witness every
|
|
31860
32191
|
// capture, including callbacks that need the latest parent state. Actual
|
|
31861
32192
|
// setup hooks remain outside this item-region proof.
|
|
31862
|
-
const hasRenderCall = containsRenderCall(
|
|
32193
|
+
const hasRenderCall = containsRenderCall(analyzedStmts, ctx);
|
|
31863
32194
|
itemMemo =
|
|
31864
32195
|
ctx.autoMemo === true &&
|
|
31865
32196
|
hasNestedComp &&
|
|
@@ -32008,7 +32339,11 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32008
32339
|
!containsAutoMemoUnsafeStructure(subStmts, ctx) &&
|
|
32009
32340
|
!containsImportedMemberRead(bodyAst, ctx.importedNames);
|
|
32010
32341
|
const hostPure =
|
|
32011
|
-
!hasParentClosure &&
|
|
32342
|
+
!hasParentClosure &&
|
|
32343
|
+
!hasHook &&
|
|
32344
|
+
!hasRenderCall &&
|
|
32345
|
+
!hasAmbientRead &&
|
|
32346
|
+
(!hasNestedComp || hostConditionalPure);
|
|
32012
32347
|
const structuredHostDepEligible =
|
|
32013
32348
|
ctx.autoMemo === true &&
|
|
32014
32349
|
hasNestedComp &&
|
|
@@ -32033,6 +32368,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32033
32368
|
const hostDepEligible =
|
|
32034
32369
|
!hostPure &&
|
|
32035
32370
|
!hasHook &&
|
|
32371
|
+
!hasAmbientRead &&
|
|
32036
32372
|
hasParentClosure &&
|
|
32037
32373
|
(!hasNestedComp || structuredHostDepEligible) &&
|
|
32038
32374
|
!hasRenderCall;
|
|
@@ -32210,6 +32546,10 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32210
32546
|
);
|
|
32211
32547
|
|
|
32212
32548
|
const mapCall = node.nativeArrayMap || null;
|
|
32549
|
+
const signalSite =
|
|
32550
|
+
mapCall === null && (ctx.signalBindingsUsed || ctx.nativeReads) && containsRenderCall(subStmts)
|
|
32551
|
+
? ssrControlKey('for', node)
|
|
32552
|
+
: null;
|
|
32213
32553
|
if (keyCapturesParent) autoMemoDeps = null;
|
|
32214
32554
|
return {
|
|
32215
32555
|
id: ctx.nextHelperId++,
|
|
@@ -32240,7 +32580,8 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32240
32580
|
keyHelper,
|
|
32241
32581
|
inlineKey: inlineKey ? keyFn : null,
|
|
32242
32582
|
bodyHelper: itemHelperName,
|
|
32243
|
-
|
|
32583
|
+
signalSite,
|
|
32584
|
+
selectionHelper: signalSite === null ? (keyedSelection?.helper ?? null) : null,
|
|
32244
32585
|
hasPerItemEventClosure,
|
|
32245
32586
|
pure,
|
|
32246
32587
|
singleRoot,
|
|
@@ -32253,13 +32594,15 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32253
32594
|
// Component-local entries remain the tuple prefix the helpers destructure;
|
|
32254
32595
|
// any appended import witnesses are comparison-only.
|
|
32255
32596
|
depEligible,
|
|
32256
|
-
|
|
32597
|
+
// Inline signals need their row owner on every survivor update, including
|
|
32598
|
+
// dependency changes that could otherwise use the lite selection path.
|
|
32599
|
+
requiresScope: requiresScope || signalSite !== null,
|
|
32257
32600
|
itemMemoWitnesses,
|
|
32258
32601
|
itemMemoFlags,
|
|
32259
32602
|
autoMemoDeps,
|
|
32260
32603
|
autoMemoWitnesses,
|
|
32261
32604
|
autoMemoContextAware,
|
|
32262
|
-
keyedSelectionIndex,
|
|
32605
|
+
keyedSelectionIndex: signalSite === null ? keyedSelectionIndex : -1,
|
|
32263
32606
|
depNames: runtimeDepNames,
|
|
32264
32607
|
// True only when the header binds NO `index <name>` — the body then can't
|
|
32265
32608
|
// observe an item's position, so a pure reorder (same item ref, position
|
|
@@ -32277,6 +32620,39 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32277
32620
|
};
|
|
32278
32621
|
}
|
|
32279
32622
|
|
|
32623
|
+
// Inline `onXxx={() => …}` / `onXxx={function () {…}}` handler closures under
|
|
32624
|
+
// `root`, as a node set for collectFreeIdentifiers' ignoreNodes.
|
|
32625
|
+
function collectEventHandlerClosures(root) {
|
|
32626
|
+
const closures = new Set();
|
|
32627
|
+
const stack = [root];
|
|
32628
|
+
while (stack.length > 0) {
|
|
32629
|
+
const node = stack.pop();
|
|
32630
|
+
if (!node || typeof node !== 'object') continue;
|
|
32631
|
+
if (Array.isArray(node)) {
|
|
32632
|
+
stack.push(...node);
|
|
32633
|
+
continue;
|
|
32634
|
+
}
|
|
32635
|
+
if (node.type === 'JSXAttribute' || node.type === 'Attribute') {
|
|
32636
|
+
const name = node.name?.name || node.name;
|
|
32637
|
+
const expression =
|
|
32638
|
+
node.value?.type === 'JSXExpressionContainer' ? node.value.expression : node.value;
|
|
32639
|
+
if (
|
|
32640
|
+
typeof name === 'string' &&
|
|
32641
|
+
/^on[A-Z]/.test(name) &&
|
|
32642
|
+
(expression?.type === 'ArrowFunctionExpression' ||
|
|
32643
|
+
expression?.type === 'FunctionExpression')
|
|
32644
|
+
) {
|
|
32645
|
+
closures.add(expression);
|
|
32646
|
+
continue;
|
|
32647
|
+
}
|
|
32648
|
+
}
|
|
32649
|
+
for (const key in node) {
|
|
32650
|
+
if (!AST_WALK_SKIP_KEYS.has(key)) stack.push(node[key]);
|
|
32651
|
+
}
|
|
32652
|
+
}
|
|
32653
|
+
return closures;
|
|
32654
|
+
}
|
|
32655
|
+
|
|
32280
32656
|
function mapCallbackHasEventClosure(callback) {
|
|
32281
32657
|
const stack = [callback.body];
|
|
32282
32658
|
while (stack.length > 0) {
|
|
@@ -33002,7 +33378,10 @@ const esrapCommentOptions = {
|
|
|
33002
33378
|
* literal raws are re-derived centrally before the print.
|
|
33003
33379
|
*/
|
|
33004
33380
|
function printNodeWithMap(node, ctx) {
|
|
33005
|
-
const printable = stripTsOnlyWrappers(escapeMultilineStringLiterals(node)
|
|
33381
|
+
const printable = stripTsOnlyWrappers(escapeMultilineStringLiterals(node), {
|
|
33382
|
+
filename: ctx.mapSourceName,
|
|
33383
|
+
enums: null,
|
|
33384
|
+
});
|
|
33006
33385
|
if (assertPrintedLocs()) assertNodeLocs(printable);
|
|
33007
33386
|
const { code, map } = esrapPrint(printable, withDeferredImports(esrapTsx(esrapCommentOptions)), {
|
|
33008
33387
|
sourceMapSource: ctx.mapSourceName,
|
|
@@ -33031,6 +33410,50 @@ function escapeMultilineStringLiterals(node) {
|
|
|
33031
33410
|
});
|
|
33032
33411
|
}
|
|
33033
33412
|
|
|
33413
|
+
// Props for a proven void root's `<C ... />` target, or null when the element
|
|
33414
|
+
// needs the general descriptor path: a `key` (or a spread that may carry one)
|
|
33415
|
+
// changes root identity, children need descriptor lowering, and a value that
|
|
33416
|
+
// can run user code must keep its deferred render-scope evaluation. The literal
|
|
33417
|
+
// is fresh, so it already has the descriptor's own-props copy shape.
|
|
33418
|
+
function voidRootElementProps(element) {
|
|
33419
|
+
if (element.children?.length > 0) return null;
|
|
33420
|
+
const properties = [];
|
|
33421
|
+
for (const attribute of element.openingElement.attributes) {
|
|
33422
|
+
if (
|
|
33423
|
+
attribute.type !== 'JSXAttribute' ||
|
|
33424
|
+
attribute.name?.type !== 'JSXIdentifier' ||
|
|
33425
|
+
attribute.name.name === 'key' ||
|
|
33426
|
+
// In a literal, `__proto__` sets the prototype, which the element path
|
|
33427
|
+
// then drops by copying own keys only. Keep that path's semantics.
|
|
33428
|
+
attribute.name.name === '__proto__'
|
|
33429
|
+
)
|
|
33430
|
+
return null;
|
|
33431
|
+
const value = attribute.value;
|
|
33432
|
+
let expression;
|
|
33433
|
+
if (value == null) expression = inheritOriginLoc(b.literal(true), attribute);
|
|
33434
|
+
else if (
|
|
33435
|
+
(value.type === 'Literal' || value.type === 'StringLiteral') &&
|
|
33436
|
+
typeof value.value === 'string'
|
|
33437
|
+
)
|
|
33438
|
+
expression = inheritOriginLoc(b.literal(value.value), value);
|
|
33439
|
+
else if (
|
|
33440
|
+
value.type === 'JSXExpressionContainer' &&
|
|
33441
|
+
(isStaticJsxValueExpression(value.expression) ||
|
|
33442
|
+
unwrapTsExpr(value.expression)?.type === 'Identifier')
|
|
33443
|
+
)
|
|
33444
|
+
expression = value.expression;
|
|
33445
|
+
else return null;
|
|
33446
|
+
const name = attribute.name.name;
|
|
33447
|
+
properties.push(
|
|
33448
|
+
inheritOriginLoc(
|
|
33449
|
+
b.prop('init', /^[A-Za-z_$][\w$]*$/.test(name) ? b.id(name) : b.literal(name), expression),
|
|
33450
|
+
attribute,
|
|
33451
|
+
),
|
|
33452
|
+
);
|
|
33453
|
+
}
|
|
33454
|
+
return inheritOriginLoc(b.object(properties), element);
|
|
33455
|
+
}
|
|
33456
|
+
|
|
33034
33457
|
// Identity-preserving copy-on-write map: `mutate` returns a replacement node
|
|
33035
33458
|
// (or null to keep descending). Untouched subtrees are returned BY REFERENCE —
|
|
33036
33459
|
// only spines above a replacement are rebuilt (as shallow copies), so mapping
|