octane 0.2.6 → 0.2.7
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/README.md +5 -0
- package/dist/cjs/runtime.cjs +435 -274
- package/dist/cjs/runtime.server.cjs +13 -11
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/bundler.js +6 -6
- package/dist/compiler/compile-universal.js +1 -1
- package/dist/compiler/compile.js +236 -113
- package/dist/compiler/index.d.ts +1 -3
- package/dist/compiler/native-read-diagnostics.js +43 -35
- package/dist/compiler/slot-hooks.d.ts +0 -1
- package/dist/compiler/slot-hooks.js +3 -3
- package/dist/compiler/text-type-facts.js +38 -24
- package/dist/compiler/typescript.d.ts +8 -1
- package/dist/compiler/typescript.js +48 -20
- package/dist/compiler/vite.d.ts +6 -2
- package/dist/compiler/vite.js +85 -5
- package/dist/compiler/volar.d.ts +1 -1
- package/dist/compiler/volar.js +12 -7
- package/dist/runtime.d.ts +11 -2
- package/dist/runtime.js +433 -274
- package/dist/runtime.server.d.ts +1 -1
- package/dist/runtime.server.js +13 -11
- package/dist/signals/index.d.ts +1 -1
- package/dist/signals/types.d.ts +1 -1
- package/dist/universal-core.js +59 -51
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -81,7 +81,11 @@ import { assertNoLiveClientOnlyImports } from './client-only-server.js';
|
|
|
81
81
|
import { nsForChildren, nsForSelf } from './jsx-namespace.js';
|
|
82
82
|
import { analyzeNativeChangeDiagnostics } from './native-change-diagnostics.js';
|
|
83
83
|
import { assertStrongMode } from './strong-mode.js';
|
|
84
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
assertNativeReadDiagnostics,
|
|
86
|
+
assertNativeReadOptions,
|
|
87
|
+
nativeReadOptions,
|
|
88
|
+
} from './native-read-diagnostics.js';
|
|
85
89
|
import {
|
|
86
90
|
captureNativeReadWitness,
|
|
87
91
|
wrapNativeReadScope,
|
|
@@ -444,8 +448,8 @@ function attrBindingHelper(bind) {
|
|
|
444
448
|
// repeating the guard and previous-value publication in every component.
|
|
445
449
|
// Certified repeated host rows select the ordinary writer and emit an inline
|
|
446
450
|
// guard instead, avoiding a call and cache publication on unchanged rows.
|
|
447
|
-
// Fresh classes
|
|
448
|
-
//
|
|
451
|
+
// Fresh classes compare their composed string in emitBindingUpdate; controlled
|
|
452
|
+
// form properties must reassert the live DOM value on every render.
|
|
449
453
|
function attrBindingUpdateHelper(bind, inlineBindingGuards = false) {
|
|
450
454
|
const helper = attrBindingHelper(bind);
|
|
451
455
|
if (inlineBindingGuards) return helper;
|
|
@@ -456,7 +460,11 @@ function attrBindingUpdateHelper(bind, inlineBindingGuards = false) {
|
|
|
456
460
|
case 'ariaAttr':
|
|
457
461
|
return `${helper}IfChanged`;
|
|
458
462
|
case 'class':
|
|
459
|
-
return bind.fresh
|
|
463
|
+
return bind.fresh
|
|
464
|
+
? bind.ns && bind.ns !== 'html'
|
|
465
|
+
? 'updateFreshClassAttr'
|
|
466
|
+
: 'updateFreshClassName'
|
|
467
|
+
: `${helper}IfChanged`;
|
|
460
468
|
default:
|
|
461
469
|
return helper;
|
|
462
470
|
}
|
|
@@ -1172,6 +1180,8 @@ const INTERNAL_CLIENT_RUNTIME_HELPERS = new Set([
|
|
|
1172
1180
|
'setAriaAttributeIfChanged',
|
|
1173
1181
|
'setClassNameIfChanged',
|
|
1174
1182
|
'setClassAttrIfChanged',
|
|
1183
|
+
'updateFreshClassName',
|
|
1184
|
+
'updateFreshClassAttr',
|
|
1175
1185
|
'textHoleUpdate',
|
|
1176
1186
|
'childTextHoleUpdate',
|
|
1177
1187
|
...HOOK_MEMO_RUNTIME_HELPERS,
|
|
@@ -8586,7 +8596,7 @@ function instrumentProfileComponents(ast, ctx) {
|
|
|
8586
8596
|
* Compile a .tsrx source string into JS targeting `octane`.
|
|
8587
8597
|
* @param {string} source
|
|
8588
8598
|
* @param {string} filename
|
|
8589
|
-
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, strong?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], valdiWriterFacts?: import('./compile-valdi.js').ValdiWriterFacts, textTypeFacts?: { version: 1, filename: string, sourceVersion: string, projectVersion: string, stringChildRanges: readonly (readonly [number, number])[] }, renderer?: { id: string, module: string, target: 'dom' | 'universal' | 'valdi', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal' | 'valdi', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
8599
|
+
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, strong?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], valdiWriterFacts?: import('./compile-valdi.js').ValdiWriterFacts, textTypeFacts?: { version: 1, filename: string, sourceVersion: string, projectVersion: string, stringChildRanges: readonly (readonly [number, number])[], primitiveTextChildRanges?: readonly (readonly [number, number])[] }, renderer?: { id: string, module: string, target: 'dom' | 'universal' | 'valdi', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal' | 'valdi', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
8590
8600
|
* `dev: true` emits client hydration source-location metadata (per-component
|
|
8591
8601
|
* `__s.locs`/`__s.locFile`) and, in server mode, source-located native-element
|
|
8592
8602
|
* scopes for invalid HTML nesting diagnostics. Both are strictly gated so
|
|
@@ -8634,7 +8644,6 @@ export function compileForBundler(source, filename, options) {
|
|
|
8634
8644
|
}
|
|
8635
8645
|
|
|
8636
8646
|
function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
8637
|
-
assertNativeReadOptions(options);
|
|
8638
8647
|
const mode = (options && options.mode) || 'client';
|
|
8639
8648
|
if (mode !== 'client' && mode !== 'server') {
|
|
8640
8649
|
throw new Error(`Unknown compile mode "${mode}" — expected 'client' or 'server'.`);
|
|
@@ -8645,6 +8654,7 @@ function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
|
8645
8654
|
);
|
|
8646
8655
|
analyzeTsrx(analyzedAst, cleanFilename);
|
|
8647
8656
|
adoptParserAst(analyzedAst);
|
|
8657
|
+
options = nativeReadOptions(analyzedAst, options);
|
|
8648
8658
|
assertNativeReadDiagnostics(analyzedAst, source, cleanFilename, options);
|
|
8649
8659
|
const strongModeEnabled =
|
|
8650
8660
|
assertStrongMode(analyzedAst, source, cleanFilename, options)?.enabled === true;
|
|
@@ -11050,7 +11060,7 @@ function ssrCompileBodyWithMapTemps(
|
|
|
11050
11060
|
// produce mergeable text nodes:
|
|
11051
11061
|
// 'static' — a static string-literal Text (bakes / serializes as literal text)
|
|
11052
11062
|
// 'empty' — a static literal that renders NOTHING (adjacency-transparent)
|
|
11053
|
-
// 'dyn' — a
|
|
11063
|
+
// 'dyn' — a proven primitive text hole (client `<!>` + htextSwap,
|
|
11054
11064
|
// server markerless ssrText)
|
|
11055
11065
|
// 'other' — everything else (elements, renderable `{expr}` holes, control
|
|
11056
11066
|
// flow — all serialize elements or `<!--[-->…<!--]-->` ranges
|
|
@@ -11059,9 +11069,7 @@ function textAdjacencyKind(node, ctx) {
|
|
|
11059
11069
|
if (node.type !== 'Text') return 'other';
|
|
11060
11070
|
const lit = staticTextLiteral(node.expression);
|
|
11061
11071
|
if (lit !== null) return lit === '' ? 'empty' : 'static';
|
|
11062
|
-
return
|
|
11063
|
-
? 'dyn'
|
|
11064
|
-
: 'other';
|
|
11072
|
+
return isKnownTextChildExpression(node.expression, ctx.knownStringChildLocals) ? 'dyn' : 'other';
|
|
11065
11073
|
}
|
|
11066
11074
|
|
|
11067
11075
|
// Does the child at `i` have a text-producing sibling next to it (looking
|
|
@@ -11361,13 +11369,14 @@ function ssrEmitNode(
|
|
|
11361
11369
|
: null,
|
|
11362
11370
|
);
|
|
11363
11371
|
}
|
|
11364
|
-
//
|
|
11372
|
+
// String assertions / templates / string concats / proven numeric results
|
|
11373
|
+
// → a text binding on both client and server.
|
|
11365
11374
|
// Everything else (`{children}`, `{<Comp/>}`, possibly-renderable values)
|
|
11366
11375
|
// → ssrChild, which RENDERS a component/element child (and coerces a
|
|
11367
11376
|
// primitive to text) — mirrors Ripple's `{expr}` vs `{expr as string}`.
|
|
11368
11377
|
// rewriteHookCalls: a `use(thenable)` in this hole bypasses the setup
|
|
11369
11378
|
// rewrite, so key it here too (else it collides with sibling/nested use()).
|
|
11370
|
-
if (
|
|
11379
|
+
if (isKnownTextChildExpression(expr, ctx.knownStringChildLocals)) {
|
|
11371
11380
|
// ssrTextPre = ssrText + the runtime leading-'\n' protection (the value
|
|
11372
11381
|
// isn't known at compile time here).
|
|
11373
11382
|
const fn =
|
|
@@ -12253,7 +12262,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
12253
12262
|
// markerless `childTextHole` mount: a primitive is the host's bare text, an object
|
|
12254
12263
|
// still gets a `<!--[-->…<!--]-->` block). Must match the client's only-child
|
|
12255
12264
|
// markerless condition exactly so both sides agree for hydration: a single `Text`
|
|
12256
|
-
// child that is neither a static literal (baked into HTML) nor
|
|
12265
|
+
// child that is neither a static literal (baked into HTML) nor proven text
|
|
12257
12266
|
// (emitted via `ssrText`).
|
|
12258
12267
|
const onlyChild0 =
|
|
12259
12268
|
normChildren.length === 1 && normChildren[0].type === 'Text' ? normChildren[0] : null;
|
|
@@ -12265,7 +12274,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
12265
12274
|
htmlSources.length === 0 &&
|
|
12266
12275
|
onlyChild0 !== null &&
|
|
12267
12276
|
staticTextLiteral(onlyChild0.expression) === null &&
|
|
12268
|
-
!
|
|
12277
|
+
!isKnownTextChildExpression(onlyChild0.expression, ctx.knownStringChildLocals)
|
|
12269
12278
|
) {
|
|
12270
12279
|
const childHelper =
|
|
12271
12280
|
tag === 'pre' || tag === 'textarea' || tag === 'listing' ? 'ssrChildTextPre' : 'ssrChildText';
|
|
@@ -14929,10 +14938,8 @@ function isMountStableInlineHandler(node, ctx) {
|
|
|
14929
14938
|
return true;
|
|
14930
14939
|
}
|
|
14931
14940
|
|
|
14932
|
-
// Object/array/function literals allocate a new identity on every evaluation
|
|
14933
|
-
//
|
|
14934
|
-
// class binding path, where dropping the dead previous-value field preserves
|
|
14935
|
-
// the exact setter frequency while shrinking both code and the binding bag.
|
|
14941
|
+
// Object/array/function literals allocate a new identity on every evaluation.
|
|
14942
|
+
// Class bindings compare their composed string instead of that fresh identity.
|
|
14936
14943
|
function isFreshBindingExpr(node) {
|
|
14937
14944
|
const value = unwrapTsExpr(node);
|
|
14938
14945
|
return (
|
|
@@ -18348,22 +18355,24 @@ function extractFragment(node, ctx, holeProps, parentNs = 'html') {
|
|
|
18348
18355
|
}
|
|
18349
18356
|
const hn = `h${holeProps.length}`;
|
|
18350
18357
|
if (expr && expr.type === 'TSAsExpression') {
|
|
18351
|
-
// Preserve
|
|
18358
|
+
// Preserve explicit string assertions. Numeric assertions and typed
|
|
18359
|
+
// primitive proofs need a synthetic text assertion on the renderer's
|
|
18360
|
+
// `props.hN` because it cannot see the original expression's proof.
|
|
18352
18361
|
holeProps.push(objectProp(hn, rewriteExtractedFragmentHole(expr.expression, ctx, childNs)));
|
|
18362
|
+
const annotation = isKnownTextChildExpression(expr, ctx.knownStringChildLocals)
|
|
18363
|
+
? b.ts_keyword_type('string')
|
|
18364
|
+
: expr.typeAnnotation;
|
|
18353
18365
|
newChildren.push(
|
|
18354
|
-
b.jsx_expression_container(
|
|
18355
|
-
b.ts_as(memberProps(hn, expr.expression), expr.typeAnnotation),
|
|
18356
|
-
),
|
|
18366
|
+
b.jsx_expression_container(b.ts_as(memberProps(hn, expr.expression), annotation)),
|
|
18357
18367
|
);
|
|
18358
18368
|
} else {
|
|
18359
18369
|
holeProps.push(objectProp(hn, rewriteExtractedFragmentHole(expr, ctx, childNs)));
|
|
18360
|
-
//
|
|
18361
|
-
//
|
|
18362
|
-
//
|
|
18363
|
-
//
|
|
18364
|
-
// a renderable childSlot, preserving byte-equality with the inline form.
|
|
18370
|
+
// The renderer only sees `props.hN`, so it cannot rediscover an inline
|
|
18371
|
+
// string or primitive-number proof. This synthetic cast preserves the
|
|
18372
|
+
// text binding across extraction; it is stripped from emitted JS and
|
|
18373
|
+
// does not change the authored expression or its runtime value.
|
|
18365
18374
|
const member = memberProps(hn, expr);
|
|
18366
|
-
const rendered =
|
|
18375
|
+
const rendered = isKnownTextChildExpression(expr, ctx.knownStringChildLocals)
|
|
18367
18376
|
? b.ts_as(member, b.ts_keyword_type('string'))
|
|
18368
18377
|
: member;
|
|
18369
18378
|
newChildren.push(b.jsx_expression_container(rendered));
|
|
@@ -20563,15 +20572,14 @@ function headElementArgNodes(node, index, ctx) {
|
|
|
20563
20572
|
];
|
|
20564
20573
|
}
|
|
20565
20574
|
|
|
20566
|
-
//
|
|
20567
|
-
//
|
|
20568
|
-
// selectors and
|
|
20569
|
-
// which is invisible to the resource contract (identity is the href).
|
|
20575
|
+
// Float resources contain global CSS. Preserve the parser's CSS source rather
|
|
20576
|
+
// than sending it through the scoped stylesheet renderer, which prunes unused
|
|
20577
|
+
// selectors and renames keyframes even without the scoping analysis pass.
|
|
20570
20578
|
/** @param {any} el @returns {any} */
|
|
20571
20579
|
function styleResourceCssExpression(el) {
|
|
20572
20580
|
const sheet = (el.children || []).find((c) => c && c.type === 'StyleSheet');
|
|
20573
20581
|
if (!sheet) return b.literal('');
|
|
20574
|
-
const css =
|
|
20582
|
+
const css = sheet.source;
|
|
20575
20583
|
return b.literal(css, JSON.stringify(css), el);
|
|
20576
20584
|
}
|
|
20577
20585
|
|
|
@@ -21180,17 +21188,30 @@ function staticTextLiteral(node) {
|
|
|
21180
21188
|
// to inherit a type proof. Ordinary copy-on-write expression rewrites preserve
|
|
21181
21189
|
// the metadata, and extractFragment explicitly carries it onto its props hole.
|
|
21182
21190
|
function applyStringChildProofs(ast, source, filename, facts) {
|
|
21183
|
-
const
|
|
21191
|
+
const typeRanges = createTextTypeFactsLookup(facts, filename, source);
|
|
21192
|
+
const typedRanges = typeRanges?.stringRanges ?? null;
|
|
21193
|
+
const primitiveRanges = typeRanges?.primitiveRanges ?? null;
|
|
21184
21194
|
// Avoid another AST walk for the overwhelmingly common syntax-only module.
|
|
21185
|
-
// Escaped identifiers can spell
|
|
21186
|
-
//
|
|
21187
|
-
const
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
|
|
21195
|
+
// Escaped identifiers can spell these built-ins too. The AST remains the
|
|
21196
|
+
// authority for calls; these substring checks only skip common files.
|
|
21197
|
+
const inspectIntrinsicCalls =
|
|
21198
|
+
source.includes('String') ||
|
|
21199
|
+
source.includes('Number') ||
|
|
21200
|
+
source.includes('BigInt') ||
|
|
21201
|
+
source.includes('Date') ||
|
|
21202
|
+
source.includes('\\u');
|
|
21203
|
+
if (
|
|
21204
|
+
(typedRanges === null || typedRanges.size === 0) &&
|
|
21205
|
+
(primitiveRanges === null || primitiveRanges.size === 0) &&
|
|
21206
|
+
!inspectIntrinsicCalls
|
|
21207
|
+
) {
|
|
21208
|
+
return ast;
|
|
21209
|
+
}
|
|
21210
|
+
const stringProofs = new Set();
|
|
21211
|
+
const primitiveProofs = new Set();
|
|
21212
|
+
const intrinsicCalls = [];
|
|
21192
21213
|
const writes = [];
|
|
21193
|
-
|
|
21214
|
+
const ambientIntrinsicValues = new Set();
|
|
21194
21215
|
const seen = new WeakSet();
|
|
21195
21216
|
const collect = (node, parent = null, key = null) => {
|
|
21196
21217
|
if (node === null || typeof node !== 'object') return;
|
|
@@ -21201,7 +21222,7 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21201
21222
|
if (seen.has(node)) return;
|
|
21202
21223
|
seen.add(node);
|
|
21203
21224
|
if (
|
|
21204
|
-
|
|
21225
|
+
(typedRanges !== null || primitiveRanges !== null) &&
|
|
21205
21226
|
node.type === 'JSXExpressionContainer' &&
|
|
21206
21227
|
((key === 'children' &&
|
|
21207
21228
|
(parent?.type === 'JSXElement' ||
|
|
@@ -21212,16 +21233,17 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21212
21233
|
) {
|
|
21213
21234
|
const expr = node.expression;
|
|
21214
21235
|
const range = `${expr?.start}:${expr?.end}`;
|
|
21215
|
-
if (
|
|
21236
|
+
if (typedRanges?.delete(range)) stringProofs.add(expr);
|
|
21237
|
+
if (primitiveRanges?.delete(range)) primitiveProofs.add(expr);
|
|
21216
21238
|
}
|
|
21217
|
-
if (
|
|
21239
|
+
if (inspectIntrinsicCalls) {
|
|
21218
21240
|
if (
|
|
21219
21241
|
node.type === 'CallExpression' &&
|
|
21220
21242
|
node.optional !== true &&
|
|
21221
21243
|
node.callee?.type === 'Identifier' &&
|
|
21222
|
-
node.callee.name
|
|
21244
|
+
TEXT_INTRINSICS.has(node.callee.name)
|
|
21223
21245
|
) {
|
|
21224
|
-
|
|
21246
|
+
intrinsicCalls.push(node);
|
|
21225
21247
|
}
|
|
21226
21248
|
if (node.type === 'AssignmentExpression') writes.push(node.left);
|
|
21227
21249
|
else if (node.type === 'UpdateExpression') writes.push(node.argument);
|
|
@@ -21236,11 +21258,13 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21236
21258
|
// Ambient VALUE declarations can describe a replaced global. Type-only
|
|
21237
21259
|
// imports/interfaces do not shadow the JavaScript built-in.
|
|
21238
21260
|
if (node.type === 'TSDeclareFunction' || node.declare === true) {
|
|
21239
|
-
if (node.id?.name
|
|
21261
|
+
if (TEXT_INTRINSICS.has(node.id?.name)) ambientIntrinsicValues.add(node.id.name);
|
|
21240
21262
|
if (node.type === 'VariableDeclaration') {
|
|
21241
21263
|
const names = new Set();
|
|
21242
21264
|
for (const declaration of node.declarations || []) collectBindings(declaration.id, names);
|
|
21243
|
-
|
|
21265
|
+
for (const name of names) {
|
|
21266
|
+
if (TEXT_INTRINSICS.has(name)) ambientIntrinsicValues.add(name);
|
|
21267
|
+
}
|
|
21244
21268
|
}
|
|
21245
21269
|
}
|
|
21246
21270
|
}
|
|
@@ -21251,33 +21275,43 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21251
21275
|
}
|
|
21252
21276
|
};
|
|
21253
21277
|
collect(ast);
|
|
21254
|
-
|
|
21278
|
+
const remainingRange =
|
|
21279
|
+
typedRanges?.values().next().value ?? primitiveRanges?.values().next().value;
|
|
21280
|
+
if (remainingRange !== undefined) {
|
|
21255
21281
|
throw new Error(
|
|
21256
|
-
`Invalid textTypeFacts for ${JSON.stringify(filename)}: range ${
|
|
21282
|
+
`Invalid textTypeFacts for ${JSON.stringify(filename)}: range ${remainingRange} is not an authored JSX child expression.`,
|
|
21257
21283
|
);
|
|
21258
21284
|
}
|
|
21259
21285
|
let lexical = null;
|
|
21260
|
-
if (
|
|
21286
|
+
if (
|
|
21287
|
+
intrinsicCalls.length > 0 ||
|
|
21288
|
+
((stringProofs.size > 0 || primitiveProofs.size > 0) && writes.length > 0)
|
|
21289
|
+
) {
|
|
21261
21290
|
// The existing scope analysis understands var hoisting, parameter defaults,
|
|
21262
21291
|
// imports, catch bindings, and TSRX @for/@try scopes. Pay for it only when
|
|
21263
21292
|
// an intrinsic candidate or a possible proof-invalidating write needs it.
|
|
21264
21293
|
lexical = createLexicalAnalysis(ast);
|
|
21265
|
-
if (writes.some((target) =>
|
|
21266
|
-
// TypeScript still
|
|
21267
|
-
// after an asserted replacement. Decline
|
|
21294
|
+
if (writes.some((target) => writesGlobalTextIntrinsic(target, lexical))) {
|
|
21295
|
+
// TypeScript still uses a built-in constructor's declared primitive
|
|
21296
|
+
// return type after an asserted replacement. Decline all new proofs in this
|
|
21268
21297
|
// rare module rather than attempting incomplete value-taint analysis.
|
|
21269
21298
|
// Supplied ranges were still validated above; explicit text syntax keeps
|
|
21270
21299
|
// its existing coercion meaning through isKnownStringExpression.
|
|
21271
21300
|
return ast;
|
|
21272
21301
|
}
|
|
21273
21302
|
}
|
|
21274
|
-
if (lexical !== null
|
|
21275
|
-
for (const call of
|
|
21303
|
+
if (lexical !== null) {
|
|
21304
|
+
for (const call of intrinsicCalls) {
|
|
21305
|
+
const name = call.callee.name;
|
|
21306
|
+
if (ambientIntrinsicValues.has(name)) continue;
|
|
21276
21307
|
const scope = lexical.nodeScopes.get(call);
|
|
21277
|
-
if (scope !== undefined && !lexical.isBound(scope,
|
|
21308
|
+
if (scope !== undefined && !lexical.isBound(scope, name)) {
|
|
21309
|
+
if (name === 'String' || name === 'Date') stringProofs.add(call);
|
|
21310
|
+
else primitiveProofs.add(call);
|
|
21311
|
+
}
|
|
21278
21312
|
}
|
|
21279
21313
|
}
|
|
21280
|
-
if (
|
|
21314
|
+
if (stringProofs.size === 0 && primitiveProofs.size === 0) return ast;
|
|
21281
21315
|
const rewritten = new WeakMap();
|
|
21282
21316
|
const rewrite = (node) => {
|
|
21283
21317
|
if (node === null || typeof node !== 'object') return node;
|
|
@@ -21303,8 +21337,10 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21303
21337
|
out[key] = next;
|
|
21304
21338
|
}
|
|
21305
21339
|
}
|
|
21306
|
-
if (
|
|
21340
|
+
if (stringProofs.has(node)) {
|
|
21307
21341
|
out = { ...out, metadata: { ...out.metadata, octane_string_child: true } };
|
|
21342
|
+
} else if (primitiveProofs.has(node)) {
|
|
21343
|
+
out = { ...out, metadata: { ...out.metadata, octane_primitive_text_child: true } };
|
|
21308
21344
|
}
|
|
21309
21345
|
}
|
|
21310
21346
|
rewritten.set(node, out);
|
|
@@ -21313,10 +21349,12 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21313
21349
|
return rewrite(ast);
|
|
21314
21350
|
}
|
|
21315
21351
|
|
|
21316
|
-
|
|
21317
|
-
|
|
21318
|
-
//
|
|
21319
|
-
|
|
21352
|
+
const TEXT_INTRINSICS = new Set(['String', 'Number', 'BigInt', 'Date']);
|
|
21353
|
+
|
|
21354
|
+
// A visible replacement of a global constructor invalidates inferred text
|
|
21355
|
+
// proofs for the module. Calls still evaluate the authored callee: a replacement
|
|
21356
|
+
// may return an element rather than the built-in's primitive result.
|
|
21357
|
+
function writesGlobalTextIntrinsic(target, lexical) {
|
|
21320
21358
|
if (!target || typeof target !== 'object') return false;
|
|
21321
21359
|
if (
|
|
21322
21360
|
target.type === 'TSAsExpression' ||
|
|
@@ -21324,20 +21362,20 @@ function writesGlobalString(target, lexical) {
|
|
|
21324
21362
|
target.type === 'TSNonNullExpression' ||
|
|
21325
21363
|
target.type === 'ParenthesizedExpression'
|
|
21326
21364
|
) {
|
|
21327
|
-
return
|
|
21365
|
+
return writesGlobalTextIntrinsic(target.expression, lexical);
|
|
21328
21366
|
}
|
|
21329
21367
|
const unbound = (node, name) => {
|
|
21330
21368
|
const scope = lexical.nodeScopes.get(node);
|
|
21331
21369
|
return scope !== undefined && !lexical.isBound(scope, name);
|
|
21332
21370
|
};
|
|
21333
21371
|
if (target.type === 'Identifier') {
|
|
21334
|
-
return target.name
|
|
21372
|
+
return TEXT_INTRINSICS.has(target.name) && unbound(target, target.name);
|
|
21335
21373
|
}
|
|
21336
21374
|
if (target.type === 'MemberExpression') {
|
|
21337
21375
|
const name = target.computed ? target.property?.value : target.property?.name;
|
|
21338
21376
|
const object = target.object;
|
|
21339
21377
|
return (
|
|
21340
|
-
name
|
|
21378
|
+
TEXT_INTRINSICS.has(name) &&
|
|
21341
21379
|
object?.type === 'Identifier' &&
|
|
21342
21380
|
(object.name === 'globalThis' ||
|
|
21343
21381
|
object.name === 'window' ||
|
|
@@ -21346,14 +21384,14 @@ function writesGlobalString(target, lexical) {
|
|
|
21346
21384
|
unbound(object, object.name)
|
|
21347
21385
|
);
|
|
21348
21386
|
}
|
|
21349
|
-
if (target.type === 'AssignmentPattern') return
|
|
21350
|
-
if (target.type === 'RestElement') return
|
|
21387
|
+
if (target.type === 'AssignmentPattern') return writesGlobalTextIntrinsic(target.left, lexical);
|
|
21388
|
+
if (target.type === 'RestElement') return writesGlobalTextIntrinsic(target.argument, lexical);
|
|
21351
21389
|
if (target.type === 'ArrayPattern') {
|
|
21352
|
-
return (target.elements || []).some((element) =>
|
|
21390
|
+
return (target.elements || []).some((element) => writesGlobalTextIntrinsic(element, lexical));
|
|
21353
21391
|
}
|
|
21354
21392
|
if (target.type === 'ObjectPattern') {
|
|
21355
21393
|
return (target.properties || []).some((property) =>
|
|
21356
|
-
|
|
21394
|
+
writesGlobalTextIntrinsic(property.argument ?? property.value, lexical),
|
|
21357
21395
|
);
|
|
21358
21396
|
}
|
|
21359
21397
|
return false;
|
|
@@ -21366,6 +21404,7 @@ function writesGlobalString(target, lexical) {
|
|
|
21366
21404
|
// Recognised shapes:
|
|
21367
21405
|
// - String Literal: 'foo' / "bar"
|
|
21368
21406
|
// - TemplateLiteral: `${x}-${y}` (always coerces to string)
|
|
21407
|
+
// - `typeof x`: always a string, even for unknown x
|
|
21369
21408
|
// - `as string` / `<string>x`: user-asserted string-typed expression
|
|
21370
21409
|
// - `satisfies string`: same intent
|
|
21371
21410
|
// - Wrappers (`!`, instantiation): peel and check inside
|
|
@@ -21380,6 +21419,7 @@ function isKnownStringExpression(node, locals, childProofs = false) {
|
|
|
21380
21419
|
return typeof node.value === 'string';
|
|
21381
21420
|
}
|
|
21382
21421
|
if (node.type === 'TemplateLiteral') return true;
|
|
21422
|
+
if (node.type === 'UnaryExpression' && node.operator === 'typeof') return true;
|
|
21383
21423
|
// An identifier the compiler has tracked back to a string in this component's
|
|
21384
21424
|
// scope: a `const` bound to a provably-string expression, a `const x: string`,
|
|
21385
21425
|
// or a `string`-typed param — see collectKnownStringLocals. `locals` is
|
|
@@ -21423,6 +21463,15 @@ function isKnownStringExpression(node, locals, childProofs = false) {
|
|
|
21423
21463
|
isKnownStringExpression(node.alternate, locals, childProofs)
|
|
21424
21464
|
);
|
|
21425
21465
|
}
|
|
21466
|
+
if (node.type === 'LogicalExpression') {
|
|
21467
|
+
return (
|
|
21468
|
+
isKnownStringExpression(node.left, locals, childProofs) &&
|
|
21469
|
+
isKnownStringExpression(node.right, locals, childProofs)
|
|
21470
|
+
);
|
|
21471
|
+
}
|
|
21472
|
+
if (node.type === 'SequenceExpression') {
|
|
21473
|
+
return isKnownStringExpression(node.expressions.at(-1), locals, childProofs);
|
|
21474
|
+
}
|
|
21426
21475
|
return false;
|
|
21427
21476
|
}
|
|
21428
21477
|
|
|
@@ -21433,6 +21482,75 @@ function isKnownStringChildExpression(node, locals) {
|
|
|
21433
21482
|
return isKnownStringExpression(node, locals, true);
|
|
21434
21483
|
}
|
|
21435
21484
|
|
|
21485
|
+
// Only child positions may use the primitive-text binding for numbers and
|
|
21486
|
+
// bigints. These expressions are primitives (or throw before returning) under
|
|
21487
|
+
// JavaScript's own operators. Keep the proof distinct from "known string":
|
|
21488
|
+
// numeric results cannot establish that `value + anotherValue` concatenates.
|
|
21489
|
+
function isKnownTextChildExpression(node, stringLocals) {
|
|
21490
|
+
if (isKnownStringChildExpression(node, stringLocals)) return true;
|
|
21491
|
+
if (node == null || typeof node !== 'object') return false;
|
|
21492
|
+
if (node.metadata?.octane_primitive_text_child === true) return true;
|
|
21493
|
+
if (node.type === 'Literal' || node.type === 'NumericLiteral' || node.type === 'BigIntLiteral') {
|
|
21494
|
+
return (
|
|
21495
|
+
typeof node.value === 'number' ||
|
|
21496
|
+
typeof node.value === 'bigint' ||
|
|
21497
|
+
node.type === 'BigIntLiteral'
|
|
21498
|
+
);
|
|
21499
|
+
}
|
|
21500
|
+
if (node.type === 'UnaryExpression') {
|
|
21501
|
+
return node.operator === '+' || node.operator === '-' || node.operator === '~';
|
|
21502
|
+
}
|
|
21503
|
+
if (node.type === 'BinaryExpression') {
|
|
21504
|
+
if (node.operator === '+') {
|
|
21505
|
+
return (
|
|
21506
|
+
isKnownTextChildExpression(node.left, stringLocals) &&
|
|
21507
|
+
isKnownTextChildExpression(node.right, stringLocals)
|
|
21508
|
+
);
|
|
21509
|
+
}
|
|
21510
|
+
return NUMERIC_TEXT_OPERATORS.has(node.operator);
|
|
21511
|
+
}
|
|
21512
|
+
if (node.type === 'ConditionalExpression') {
|
|
21513
|
+
return (
|
|
21514
|
+
isKnownTextChildExpression(node.consequent, stringLocals) &&
|
|
21515
|
+
isKnownTextChildExpression(node.alternate, stringLocals)
|
|
21516
|
+
);
|
|
21517
|
+
}
|
|
21518
|
+
if (node.type === 'LogicalExpression') {
|
|
21519
|
+
return (
|
|
21520
|
+
isKnownTextChildExpression(node.left, stringLocals) &&
|
|
21521
|
+
isKnownTextChildExpression(node.right, stringLocals)
|
|
21522
|
+
);
|
|
21523
|
+
}
|
|
21524
|
+
if (node.type === 'SequenceExpression') {
|
|
21525
|
+
return isKnownTextChildExpression(node.expressions.at(-1), stringLocals);
|
|
21526
|
+
}
|
|
21527
|
+
if (
|
|
21528
|
+
node.type === 'TSAsExpression' ||
|
|
21529
|
+
node.type === 'TSTypeAssertion' ||
|
|
21530
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
21531
|
+
node.type === 'TSNonNullExpression' ||
|
|
21532
|
+
node.type === 'TSInstantiationExpression' ||
|
|
21533
|
+
node.type === 'ParenthesizedExpression'
|
|
21534
|
+
) {
|
|
21535
|
+
return isKnownTextChildExpression(node.expression, stringLocals);
|
|
21536
|
+
}
|
|
21537
|
+
return false;
|
|
21538
|
+
}
|
|
21539
|
+
|
|
21540
|
+
const NUMERIC_TEXT_OPERATORS = new Set([
|
|
21541
|
+
'-',
|
|
21542
|
+
'*',
|
|
21543
|
+
'/',
|
|
21544
|
+
'%',
|
|
21545
|
+
'**',
|
|
21546
|
+
'<<',
|
|
21547
|
+
'>>',
|
|
21548
|
+
'>>>',
|
|
21549
|
+
'|',
|
|
21550
|
+
'&',
|
|
21551
|
+
'^',
|
|
21552
|
+
]);
|
|
21553
|
+
|
|
21436
21554
|
// Annotation check: does a TS type annotation resolve to `string`? Accepts both a
|
|
21437
21555
|
// bare type node and a `TSTypeAnnotation` wrapper (`x: string`).
|
|
21438
21556
|
function isStringTypeAnnotation(ann) {
|
|
@@ -22020,7 +22138,7 @@ function planJsx(
|
|
|
22020
22138
|
const coalesceChildRoot =
|
|
22021
22139
|
jsxNodes.length === 1 &&
|
|
22022
22140
|
((jsxNodes[0].type === 'Text' &&
|
|
22023
|
-
!
|
|
22141
|
+
!isKnownTextChildExpression(jsxNodes[0].expression, ctx.knownStringChildLocals)) ||
|
|
22024
22142
|
jsxNodes[0].type === 'TSRXExpression');
|
|
22025
22143
|
// Top-level control-flow directives (@if/@for/@switch/@try/<Activity>). In a
|
|
22026
22144
|
// body that ALSO has static template roots, each construct emits a `<!>`
|
|
@@ -22467,6 +22585,8 @@ function planJsx(
|
|
|
22467
22585
|
// a mount-only import for deferred writes.
|
|
22468
22586
|
const attrHelper = attrBindingHelper(b);
|
|
22469
22587
|
if (attrHelper !== null) {
|
|
22588
|
+
if (b.kind === 'class' && b.fresh && (!b.deferred || (!b.mountOnly && inlineBindingGuards)))
|
|
22589
|
+
ctx.runtimeNeeded.add('normalizeClass');
|
|
22470
22590
|
if (!b.deferred) {
|
|
22471
22591
|
ctx.runtimeNeeded.add(attrHelper);
|
|
22472
22592
|
registerAttrLoweringOrigin(ctx, b.nameOrigin, attrHelper, b.name);
|
|
@@ -23876,12 +23996,10 @@ function commitSourceRows(sources, valueOf) {
|
|
|
23876
23996
|
// is byte-identical to the old unconditional mount write).
|
|
23877
23997
|
function emitDeferredMount(bind, elVar, bag) {
|
|
23878
23998
|
// Whole-object `style` diffs on `_sty`; scalar styles and other values on `_prev`.
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
23882
|
-
|
|
23883
|
-
);
|
|
23884
|
-
}
|
|
23999
|
+
bag.constField(
|
|
24000
|
+
bind.kind === 'style' ? `_sty$${bind.id}` : `_prev$${bind.id}`,
|
|
24001
|
+
bind.kind === 'styleProperty' ? 'style-unset' : 'undefined',
|
|
24002
|
+
);
|
|
23885
24003
|
const key = `_el$${bind.id}`;
|
|
23886
24004
|
if (!bag.host(key, elVar)) return null;
|
|
23887
24005
|
return inheritOriginLoc(
|
|
@@ -24089,9 +24207,15 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
24089
24207
|
}
|
|
24090
24208
|
case 'class': {
|
|
24091
24209
|
// On SVG/MathML hosts the `className` property is read-only — fall back
|
|
24092
|
-
// to setAttribute.
|
|
24093
|
-
|
|
24094
|
-
|
|
24210
|
+
// to setAttribute. A fresh literal is truthy even when it composes to
|
|
24211
|
+
// an empty string, so caching its composition preserves class="".
|
|
24212
|
+
const value = bind.fresh ? b.call('_$normalizeClass', bind.expr) : bind.expr;
|
|
24213
|
+
const body = [
|
|
24214
|
+
b.const('_v', value),
|
|
24215
|
+
b.stmt(b.call(callee(), el(), V())),
|
|
24216
|
+
...mountHost(),
|
|
24217
|
+
b.stmt(b.assignment('=', local(`_prev$${bind.id}`), V())),
|
|
24218
|
+
];
|
|
24095
24219
|
return st(b.block(body));
|
|
24096
24220
|
}
|
|
24097
24221
|
case 'style': {
|
|
@@ -24386,13 +24510,14 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
|
|
|
24386
24510
|
return st(b.stmt(b.call(callee(), F('_el'), bind.expr)));
|
|
24387
24511
|
}
|
|
24388
24512
|
case 'class': {
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24513
|
+
// Recompose fresh literal values on every render, including when their
|
|
24514
|
+
// members have getters, but skip the DOM write for an unchanged class.
|
|
24515
|
+
const value =
|
|
24516
|
+
bind.fresh && inlineBindingGuards ? b.call('_$normalizeClass', bind.expr) : bind.expr;
|
|
24392
24517
|
if (inlineBindingGuards) {
|
|
24393
24518
|
return st(
|
|
24394
24519
|
b.block([
|
|
24395
|
-
b.const('_v',
|
|
24520
|
+
b.const('_v', value),
|
|
24396
24521
|
b.if(
|
|
24397
24522
|
b.binary('!==', F('_prev'), V()),
|
|
24398
24523
|
b.block([
|
|
@@ -24405,7 +24530,7 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
|
|
|
24405
24530
|
);
|
|
24406
24531
|
}
|
|
24407
24532
|
return st(
|
|
24408
|
-
b.stmt(b.assignment('=', F('_prev'), b.call(callee(),
|
|
24533
|
+
b.stmt(b.assignment('=', F('_prev'), b.call(callee(), value, F('_prev'), F('_el')))),
|
|
24409
24534
|
);
|
|
24410
24535
|
}
|
|
24411
24536
|
case 'style': {
|
|
@@ -24847,12 +24972,11 @@ function emitNodeHtml(
|
|
|
24847
24972
|
cssHash = null,
|
|
24848
24973
|
) {
|
|
24849
24974
|
if (node.type === 'Text') {
|
|
24850
|
-
if (
|
|
24975
|
+
if (isKnownTextChildExpression(node.expression, ctx.knownStringChildLocals)) {
|
|
24851
24976
|
bindings.push({
|
|
24852
24977
|
id: bindings.length,
|
|
24853
24978
|
kind: 'text',
|
|
24854
24979
|
expr: resolveStyleExpr(node.expression, cssHash),
|
|
24855
|
-
knownString: true,
|
|
24856
24980
|
path: path.slice(0, -1),
|
|
24857
24981
|
childIndex: path[path.length - 1],
|
|
24858
24982
|
});
|
|
@@ -26050,12 +26174,11 @@ function emitElementHtml(
|
|
|
26050
26174
|
]
|
|
26051
26175
|
: null;
|
|
26052
26176
|
appendTemplatePart(html, escaped, 'text', origins);
|
|
26053
|
-
} else if (
|
|
26177
|
+
} else if (isKnownTextChildExpression(txtChild.expression, ctx.knownStringChildLocals)) {
|
|
26054
26178
|
bindings.push({
|
|
26055
26179
|
id: bindings.length,
|
|
26056
26180
|
kind: 'textOnlyChild',
|
|
26057
26181
|
expr: resolveStyleExpr(txtChild.expression, cssHash),
|
|
26058
|
-
knownString: true,
|
|
26059
26182
|
path,
|
|
26060
26183
|
});
|
|
26061
26184
|
// The element stays empty in the template — runtime appends a Text node.
|
|
@@ -26224,12 +26347,11 @@ function emitElementHtml(
|
|
|
26224
26347
|
appendTemplatePart(html, escaped, 'text', origins);
|
|
26225
26348
|
prevBakedText = true;
|
|
26226
26349
|
if (!prevBaked) childIdx++;
|
|
26227
|
-
} else if (
|
|
26350
|
+
} else if (isKnownTextChildExpression(child.expression, ctx.knownStringChildLocals)) {
|
|
26228
26351
|
bindings.push({
|
|
26229
26352
|
id: bindings.length,
|
|
26230
26353
|
kind: 'text',
|
|
26231
26354
|
expr: resolveStyleExpr(child.expression, cssHash),
|
|
26232
|
-
knownString: true,
|
|
26233
26355
|
path,
|
|
26234
26356
|
childIndex: childIdx,
|
|
26235
26357
|
// A text-producing neighbour → the server emits a `<!-- -->`
|
|
@@ -26396,7 +26518,7 @@ function emitElementHtml(
|
|
|
26396
26518
|
// - `{createPortal(BODY, TARGET, PROPS?)}` → portal() call
|
|
26397
26519
|
// - `{cond ? <JSX/> : <JSX/>}` → lowered to ifBlock (so the branches
|
|
26398
26520
|
// mount real DOM, not stringified text)
|
|
26399
|
-
// - a
|
|
26521
|
+
// - a proven primitive text expression → text-hole binding
|
|
26400
26522
|
// - anything else → renderable childSlot hole
|
|
26401
26523
|
const expr = child.expression;
|
|
26402
26524
|
if (isCreatePortalCall(expr)) {
|
|
@@ -26424,12 +26546,11 @@ function emitElementHtml(
|
|
|
26424
26546
|
ifCalls.push(ic);
|
|
26425
26547
|
appendTemplatePart(html, '<!>', 'anchor');
|
|
26426
26548
|
childIdx++;
|
|
26427
|
-
} else if (
|
|
26549
|
+
} else if (isKnownTextChildExpression(expr, ctx.knownStringChildLocals)) {
|
|
26428
26550
|
bindings.push({
|
|
26429
26551
|
id: bindings.length,
|
|
26430
26552
|
kind: 'text',
|
|
26431
26553
|
expr: tsrxExprNode(resolveStyleExpr(expr, cssHash), ctx, componentName, inlinedSubs),
|
|
26432
|
-
knownString: true,
|
|
26433
26554
|
path,
|
|
26434
26555
|
childIndex: childIdx,
|
|
26435
26556
|
});
|
|
@@ -26555,7 +26676,7 @@ function helperCaptures(ctx, stmts, params) {
|
|
|
26555
26676
|
|
|
26556
26677
|
// A construct's helpers (then+else, all switch cases, try+pending+catch,
|
|
26557
26678
|
// item+empty) share ONE env tuple — `block.extra` is per construct block and
|
|
26558
|
-
// every helper
|
|
26679
|
+
// every helper reads from the same layout — so the emitted array is the
|
|
26559
26680
|
// sorted UNION of each body's captures. Null propagates (no component
|
|
26560
26681
|
// context → all of the construct's helpers stay inline).
|
|
26561
26682
|
function unionEnv(ctx, bodies) {
|
|
@@ -26578,7 +26699,7 @@ function unionEnv(ctx, bodies) {
|
|
|
26578
26699
|
// `__extra` ABI slot (renderBlock forwards `block.extra` as the third body
|
|
26579
26700
|
// arg; the compiled call site passes the current values every parent render):
|
|
26580
26701
|
//
|
|
26581
|
-
// function __then$0(__props, __s, __extra) { const
|
|
26702
|
+
// function __then$0(__props, __s, __extra) { const label = __extra[0]; … }
|
|
26582
26703
|
//
|
|
26583
26704
|
// `envNames: null` keeps the legacy placement — the helper is emitted INSIDE
|
|
26584
26705
|
// the component function so its closures capture the parent's locals
|
|
@@ -26603,25 +26724,27 @@ function hoistBodyHelper(
|
|
|
26603
26724
|
const ownEnv = new Set(ownEnvNames || []);
|
|
26604
26725
|
let bodyStmts = stmts;
|
|
26605
26726
|
if (envNames && envNames.length > 0) {
|
|
26606
|
-
//
|
|
26607
|
-
//
|
|
26608
|
-
//
|
|
26609
|
-
//
|
|
26610
|
-
//
|
|
26727
|
+
// Read only this helper's captures at their positions in the construct's
|
|
26728
|
+
// shared UNION tuple. Union-only names must not become local bindings: an
|
|
26729
|
+
// arm may have a same-named local shadowing another arm's capture. Direct
|
|
26730
|
+
// indexed reads avoid iterator work before these helpers optimize.
|
|
26731
|
+
// Each binding maps to the construct body it feeds.
|
|
26611
26732
|
const envOrigin =
|
|
26612
26733
|
(Array.isArray(stmts) && stmts.find((s) => s != null && s.loc != null)) ||
|
|
26613
26734
|
(params || []).find((p) => p != null && p.loc != null) ||
|
|
26614
26735
|
null;
|
|
26615
|
-
|
|
26616
|
-
|
|
26617
|
-
|
|
26618
|
-
|
|
26619
|
-
|
|
26736
|
+
const captures = [];
|
|
26737
|
+
for (let i = 0; i < envNames.length; i++) {
|
|
26738
|
+
const name = envNames[i];
|
|
26739
|
+
if (!ownEnv.has(name)) continue;
|
|
26740
|
+
captures.push(
|
|
26741
|
+
inheritOriginLoc(
|
|
26742
|
+
b.const(b.id(name), b.member(b.id('__extra'), b.literal(i), true)),
|
|
26743
|
+
envOrigin,
|
|
26620
26744
|
),
|
|
26621
|
-
|
|
26622
|
-
|
|
26623
|
-
|
|
26624
|
-
];
|
|
26745
|
+
);
|
|
26746
|
+
}
|
|
26747
|
+
bodyStmts = [...captures, ...stmts];
|
|
26625
26748
|
}
|
|
26626
26749
|
// The synthetic helper shell maps to the construct body it hoists.
|
|
26627
26750
|
const fakeOrigin =
|
|
@@ -26650,7 +26773,7 @@ function hoistBodyHelper(
|
|
|
26650
26773
|
// Module-scope placement. Nested constructs compiled INSIDE this body
|
|
26651
26774
|
// compute THEIR captures against the names visible at their call sites —
|
|
26652
26775
|
// which live in THIS compiled body: the component's locals extended with
|
|
26653
|
-
// this helper's params, env
|
|
26776
|
+
// this helper's params, env bindings, and body-level locals. (A nested
|
|
26654
26777
|
// helper's env values are emitted as plain identifiers at its call site.)
|
|
26655
26778
|
const prevLocals = ctx.currentComponentLocals;
|
|
26656
26779
|
const prevInvariantLocals = ctx.currentInvariantLocals;
|
|
@@ -28249,7 +28372,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
28249
28372
|
// event-row captures in the same order improves the compressed fragment.
|
|
28250
28373
|
envNames = [envNames[1], envNames[0], ...envNames.slice(2)];
|
|
28251
28374
|
}
|
|
28252
|
-
// The helper
|
|
28375
|
+
// The helper reads only component-local captures (`envNames`) from the
|
|
28253
28376
|
// tuple prefix. Compiler memoization may additionally need live imported
|
|
28254
28377
|
// bindings as dependency witnesses; append them without disturbing that ABI.
|
|
28255
28378
|
const runtimeDepNames =
|