octane 0.1.38 → 0.1.40
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/runtime.cjs +13 -6
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/compile-universal.js +3 -1
- package/dist/compiler/compile.js +27 -3
- package/dist/compiler/hook-deps.js +210 -40
- package/dist/compiler/parser.browser.js +1 -0
- package/dist/compiler/parser.node.js +1 -0
- package/dist/compiler/strong-mode.js +764 -170
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +13 -6
- package/dist/version.js +1 -1
- package/package.json +11 -4
package/dist/cjs/runtime.cjs
CHANGED
|
@@ -11553,7 +11553,8 @@ function childSlot(parentScope, slotKey, domParent, value, anchor, ownEnd, ownsH
|
|
|
11553
11553
|
cachedDeps: null,
|
|
11554
11554
|
emptyBlock: null,
|
|
11555
11555
|
env: void 0,
|
|
11556
|
-
adopt: null
|
|
11556
|
+
adopt: null,
|
|
11557
|
+
plainDeopt: false
|
|
11557
11558
|
};
|
|
11558
11559
|
if (upgradeArmed) {
|
|
11559
11560
|
state.forSlot.adopt = buildDeoptAdoptQueue(upgradeChildren, state.start, state.end);
|
|
@@ -11624,8 +11625,10 @@ function childSlot(parentScope, slotKey, domParent, value, anchor, ownEnd, ownsH
|
|
|
11624
11625
|
descriptorBody(item, scope);
|
|
11625
11626
|
};
|
|
11626
11627
|
}
|
|
11628
|
+
const plainDeopt = compiledMapBody === void 0 && mappedFallback !== true;
|
|
11629
|
+
state.forSlot.plainDeopt = plainDeopt;
|
|
11627
11630
|
const fastFlags = compiledMapFlags || 0;
|
|
11628
|
-
const ssrMarkerless = compiledMapBody === void 0 ? markerlessMappedFallback ||
|
|
11631
|
+
const ssrMarkerless = compiledMapBody === void 0 ? markerlessMappedFallback || plainDeopt : (fastFlags & 16) !== 0;
|
|
11629
11632
|
let pure = (fastFlags & 1) !== 0;
|
|
11630
11633
|
let lite = false;
|
|
11631
11634
|
if (compiledMapBody !== void 0) {
|
|
@@ -14722,7 +14725,11 @@ function forBlock(parentScope, slotKey, domParent, items, getKey, itemBody, flag
|
|
|
14722
14725
|
cachedDeps: null,
|
|
14723
14726
|
emptyBlock: null,
|
|
14724
14727
|
env: void 0,
|
|
14725
|
-
adopt: null
|
|
14728
|
+
adopt: null,
|
|
14729
|
+
// Compiled `@for` slots never read this — they never pass the de-opt
|
|
14730
|
+
// sentinel — but both ForSlot literals declare it so every slot shares
|
|
14731
|
+
// one hidden class and the stamp in childSlot transitions nothing.
|
|
14732
|
+
plainDeopt: false
|
|
14726
14733
|
};
|
|
14727
14734
|
parentScope.slots[slotKey] = state;
|
|
14728
14735
|
registerSlot(parentScope, state);
|
|
@@ -15532,8 +15539,8 @@ function batchClearItems(state, oldItems) {
|
|
|
15532
15539
|
function mountItem(parentBlock, parentNode, anchor, item, index, body, forSlot, singleRoot, ssrMarkerless, adoptNode = null) {
|
|
15533
15540
|
const hydration = activeHydration();
|
|
15534
15541
|
if (hydration !== null) {
|
|
15535
|
-
if (ssrMarkerless && !hydration.isOpen(hydration.node) && (singleRoot !== 2 ||
|
|
15536
|
-
if (hydration.node !== null && hydration.node !== forSlot.end && (singleRoot !== 2 ||
|
|
15542
|
+
if (ssrMarkerless && !hydration.isOpen(hydration.node) && (singleRoot !== 2 || forSlot.plainDeopt !== true || isHostDescriptor(item) && !descNeedsBlocks(item))) {
|
|
15543
|
+
if (hydration.node !== null && hydration.node !== forSlot.end && (singleRoot !== 2 || forSlot.plainDeopt !== true || hydration.node.nodeType === 1 && hydration.node.parentNode === parentNode)) {
|
|
15537
15544
|
const root = hydration.node;
|
|
15538
15545
|
const block3 = createBlock(
|
|
15539
15546
|
"control-flow",
|
|
@@ -15547,7 +15554,7 @@ function mountItem(parentBlock, parentNode, anchor, item, index, body, forSlot,
|
|
|
15547
15554
|
);
|
|
15548
15555
|
block3.forSlot = forSlot;
|
|
15549
15556
|
block3.itemIndex = index;
|
|
15550
|
-
if (singleRoot === 2 &&
|
|
15557
|
+
if (singleRoot === 2 && forSlot.plainDeopt === true) block3.deoptNode = root;
|
|
15551
15558
|
renderBlock(block3);
|
|
15552
15559
|
hydration.node = block3.endMarker?.nextSibling ?? root.nextSibling;
|
|
15553
15560
|
return block3;
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
version: () => version
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const version = "0.1.
|
|
24
|
+
const version = "0.1.40";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
version
|
|
@@ -272,6 +272,7 @@ const UNIVERSAL_RUNTIME_IMPORTS = new Set([
|
|
|
272
272
|
// ordinary renderers while allowing native first-screen programs to erase
|
|
273
273
|
// background-owned callbacks from their render-only specialization.
|
|
274
274
|
const MAIN_THREAD_RENDER_ONLY_CAPABILITY = 'main-thread-render-only';
|
|
275
|
+
const COMPONENT_SCOPE_FOR_CAPABILITY = 'component-scope-for';
|
|
275
276
|
const THREAD_FUNCTION_CAPABILITY = 'thread-functions';
|
|
276
277
|
const THREAD_DIRECTIVES = new Map([
|
|
277
278
|
['main thread', 'main-thread'],
|
|
@@ -2453,7 +2454,8 @@ function templateProgramForHost(node, state) {
|
|
|
2453
2454
|
function templateProgramForComponent(node, state) {
|
|
2454
2455
|
if (
|
|
2455
2456
|
node.empty != null ||
|
|
2456
|
-
!rendererHasCapability(state, 'template-program-mount')
|
|
2457
|
+
(!rendererHasCapability(state, 'template-program-mount') &&
|
|
2458
|
+
!rendererHasCapability(state, COMPONENT_SCOPE_FOR_CAPABILITY)) ||
|
|
2457
2459
|
!isOwnerFreeForExpression(node.right) ||
|
|
2458
2460
|
!isOwnerFreeForExpression(node.key)
|
|
2459
2461
|
) {
|
package/dist/compiler/compile.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* runtime.
|
|
4
4
|
*
|
|
5
5
|
* Architecture:
|
|
6
|
-
* 1. Parse TSRX
|
|
7
|
-
*
|
|
6
|
+
* 1. Parse TSRX through the environment-selected @tsrx/core-compatible
|
|
7
|
+
* parser (native oxc-tsrx in Node, pure JavaScript elsewhere), then run
|
|
8
|
+
* @tsrx/core's target-neutral semantic analysis on the authored module.
|
|
8
9
|
* 2. For each top-level node:
|
|
9
10
|
* - Component (`@{ … }` body or a return-JSX function) → compile to a
|
|
10
11
|
* function taking the props-first ABI `(…userParams, __s, __extra)`.
|
|
@@ -30,7 +31,6 @@
|
|
|
30
31
|
import {
|
|
31
32
|
analyzeCss,
|
|
32
33
|
analyzeTsrx,
|
|
33
|
-
parseModule,
|
|
34
34
|
prepareStylesheetForRender,
|
|
35
35
|
renderStylesheets,
|
|
36
36
|
builders as b,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
clone_ast_node as cloneAstNode,
|
|
39
39
|
strongHash,
|
|
40
40
|
} from '@tsrx/core';
|
|
41
|
+
import { parseModule } from '#octane/compiler-parser';
|
|
41
42
|
import { print as esrapPrint } from 'esrap';
|
|
42
43
|
import esrapTsx from 'esrap/languages/tsx';
|
|
43
44
|
import { buildFatSegments } from './fat-segments.js';
|
|
@@ -8640,6 +8641,29 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
8640
8641
|
return true;
|
|
8641
8642
|
}
|
|
8642
8643
|
}
|
|
8644
|
+
// METHOD-style custom hooks — `route.useLoaderData()`, `api.useSearch()`,
|
|
8645
|
+
// `SomeContext.useSelector(fn)`. The free-identifier sweep above only sees
|
|
8646
|
+
// bare names, and the unknown-call rule above only inspects Identifier
|
|
8647
|
+
// callees, so without this a component whose ONLY hook is member-form
|
|
8648
|
+
// looks hookless: it takes the lite path, gets no block of its own, and
|
|
8649
|
+
// its hook state lands on the parent — an update from that hook then
|
|
8650
|
+
// re-renders the parent and every sibling.
|
|
8651
|
+
//
|
|
8652
|
+
// Same `use[A-Z]` convention, applied to the PROPERTY name, that the
|
|
8653
|
+
// slot-injection pass already uses for these calls. `useContext` is
|
|
8654
|
+
// included rather than excluded: it is not lite-safe either (the sweep
|
|
8655
|
+
// above rejects the bare name for the same reason).
|
|
8656
|
+
if (
|
|
8657
|
+
t === 'CallExpression' &&
|
|
8658
|
+
n.callee &&
|
|
8659
|
+
n.callee.type === 'MemberExpression' &&
|
|
8660
|
+
!n.callee.computed &&
|
|
8661
|
+
n.callee.property &&
|
|
8662
|
+
n.callee.property.type === 'Identifier' &&
|
|
8663
|
+
/^use[A-Z]/.test(n.callee.property.name)
|
|
8664
|
+
) {
|
|
8665
|
+
return true;
|
|
8666
|
+
}
|
|
8643
8667
|
for (const k in n) {
|
|
8644
8668
|
if (AST_WALK_SKIP_KEYS.has(k)) continue;
|
|
8645
8669
|
if (walk(n[k])) return true;
|
|
@@ -118,12 +118,14 @@ function unwrapExport(node) {
|
|
|
118
118
|
return node;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function predeclareDirect(statements, scope, hookRuntimeModules) {
|
|
121
|
+
function predeclareDirect(statements, scope, hookRuntimeModules, bindingsOnly = false) {
|
|
122
122
|
for (const original of statements || []) {
|
|
123
123
|
if (original.type === 'ImportDeclaration') {
|
|
124
|
+
if (bindingsOnly && original.importKind === 'type') continue;
|
|
124
125
|
const isHookRuntime = hookRuntimeModules.has(original.source?.value);
|
|
125
126
|
const isOctane = original.source?.value === 'octane';
|
|
126
127
|
for (const specifier of original.specifiers || []) {
|
|
128
|
+
if (bindingsOnly && specifier.importKind === 'type') continue;
|
|
127
129
|
const imported = specifier.imported?.name;
|
|
128
130
|
declareName(scope, specifier.local.name, {
|
|
129
131
|
imported: true,
|
|
@@ -156,19 +158,29 @@ function predeclareDirect(statements, scope, hookRuntimeModules) {
|
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
function collectHoistedVars(node, functionScope, root = true) {
|
|
161
|
+
function collectHoistedVars(node, functionScope, root = true, bindingsOnly = false) {
|
|
160
162
|
if (!node || typeof node !== 'object') return;
|
|
161
163
|
if (Array.isArray(node)) {
|
|
162
|
-
for (const child of node) collectHoistedVars(child, functionScope, false);
|
|
164
|
+
for (const child of node) collectHoistedVars(child, functionScope, false, bindingsOnly);
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
165
167
|
if (!root && isFunction(node)) return;
|
|
168
|
+
if (
|
|
169
|
+
bindingsOnly &&
|
|
170
|
+
(isFunction(node) ||
|
|
171
|
+
(!root && node.type === 'StaticBlock') ||
|
|
172
|
+
node.type === 'ClassDeclaration' ||
|
|
173
|
+
node.type === 'ClassExpression' ||
|
|
174
|
+
node.type === 'TSModuleDeclaration')
|
|
175
|
+
) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
166
178
|
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
|
|
167
179
|
for (const decl of node.declarations || []) declarePattern(decl.id, functionScope);
|
|
168
180
|
}
|
|
169
181
|
for (const key in node) {
|
|
170
182
|
if (AST_META_KEYS.has(key)) continue;
|
|
171
|
-
collectHoistedVars(node[key], functionScope, false);
|
|
183
|
+
collectHoistedVars(node[key], functionScope, false, bindingsOnly);
|
|
172
184
|
}
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -277,7 +289,7 @@ function markReassignedPattern(pattern, scope) {
|
|
|
277
289
|
}
|
|
278
290
|
}
|
|
279
291
|
|
|
280
|
-
function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
292
|
+
function buildScopes(ast, onlyImported, hookRuntimeModules, bindingsOnly = false) {
|
|
281
293
|
nextBindingId = 0;
|
|
282
294
|
const moduleScope = createScope(null, 'module');
|
|
283
295
|
const nodeScopes = new WeakMap();
|
|
@@ -289,8 +301,35 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
289
301
|
const functionRecords = new WeakMap();
|
|
290
302
|
const trustedHookNames = new WeakMap();
|
|
291
303
|
const callAnnotations = new Map();
|
|
292
|
-
|
|
293
|
-
|
|
304
|
+
const declarationBindings = bindingsOnly ? new Map() : null;
|
|
305
|
+
const firstDefinitions = bindingsOnly ? new Map() : null;
|
|
306
|
+
predeclareDirect(ast.body, moduleScope, hookRuntimeModules, bindingsOnly);
|
|
307
|
+
collectHoistedVars(ast, moduleScope, true, bindingsOnly);
|
|
308
|
+
|
|
309
|
+
function rememberBindings(bindings, definesValue = true) {
|
|
310
|
+
if (declarationBindings === null || firstDefinitions === null) return;
|
|
311
|
+
for (const { pattern, binding } of bindings) {
|
|
312
|
+
declarationBindings.set(pattern, binding);
|
|
313
|
+
if (!definesValue) continue;
|
|
314
|
+
const previous = firstDefinitions.get(binding);
|
|
315
|
+
if (previous !== undefined && previous !== pattern) binding.reassigned = true;
|
|
316
|
+
else firstDefinitions.set(binding, pattern);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function rememberPattern(pattern, scope, definesValue = true) {
|
|
321
|
+
if (!bindingsOnly) return;
|
|
322
|
+
if (pattern?.type === 'TSParameterProperty') pattern = pattern.parameter;
|
|
323
|
+
const bindings = [];
|
|
324
|
+
collectPatternBindings(pattern, scope, bindings);
|
|
325
|
+
rememberBindings(bindings, definesValue);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function invalidateVisibleBindings(scope) {
|
|
329
|
+
for (let current = scope; current !== null; current = current.parent) {
|
|
330
|
+
for (const binding of current.bindings.values()) binding.reassigned = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
294
333
|
|
|
295
334
|
function walk(node, scope) {
|
|
296
335
|
if (!node || typeof node !== 'object') return;
|
|
@@ -298,34 +337,103 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
298
337
|
for (const child of node) walk(child, scope);
|
|
299
338
|
return;
|
|
300
339
|
}
|
|
301
|
-
nodeScopes.set(node, scope);
|
|
340
|
+
if (!bindingsOnly) nodeScopes.set(node, scope);
|
|
341
|
+
if (
|
|
342
|
+
bindingsOnly &&
|
|
343
|
+
(node.type === 'TSInstantiationExpression' || node.type === 'TSExportAssignment')
|
|
344
|
+
) {
|
|
345
|
+
walk(node.expression, scope);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
302
348
|
|
|
303
349
|
if (isFunction(node)) {
|
|
304
350
|
const fnScope = createScope(scope, 'function');
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
351
|
+
const record = bindingsOnly
|
|
352
|
+
? null
|
|
353
|
+
: {
|
|
354
|
+
node,
|
|
355
|
+
scope: fnScope,
|
|
356
|
+
binding:
|
|
357
|
+
node.type === 'FunctionDeclaration' && node.id
|
|
358
|
+
? resolveBinding(scope, node.id.name)
|
|
359
|
+
: null,
|
|
360
|
+
parameters: null,
|
|
361
|
+
stableDefinition: node.type === 'FunctionDeclaration',
|
|
362
|
+
};
|
|
363
|
+
if (record !== null) {
|
|
364
|
+
functionScopes.set(node, fnScope);
|
|
365
|
+
functions.push(record);
|
|
366
|
+
functionRecords.set(node, record);
|
|
367
|
+
}
|
|
368
|
+
if (bindingsOnly && node.type === 'FunctionDeclaration' && node.id) {
|
|
369
|
+
rememberPattern(node.id, scope);
|
|
370
|
+
}
|
|
318
371
|
if (node.type === 'FunctionExpression' && node.id) {
|
|
319
372
|
declareName(fnScope, node.id.name);
|
|
373
|
+
if (bindingsOnly) rememberPattern(node.id, fnScope);
|
|
320
374
|
}
|
|
321
375
|
if (node.type !== 'ArrowFunctionExpression') declareName(fnScope, 'arguments');
|
|
322
|
-
for (const param of node.params || [])
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
376
|
+
for (const param of node.params || []) {
|
|
377
|
+
declarePattern(
|
|
378
|
+
bindingsOnly && param.type === 'TSParameterProperty' ? param.parameter : param,
|
|
379
|
+
fnScope,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (record !== null) {
|
|
383
|
+
record.parameters = (node.params || []).map((param) =>
|
|
384
|
+
directParameterBinding(param, fnScope),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (bindingsOnly) {
|
|
388
|
+
for (const param of node.params || []) rememberPattern(param, fnScope);
|
|
389
|
+
// Parameter defaults cannot see declarations in the function body.
|
|
390
|
+
// Afterwards, share the direct body with the parameters so legal var
|
|
391
|
+
// and function redeclarations count as competing value definitions.
|
|
392
|
+
for (const param of node.params || []) walkPatternDefaults(param, fnScope);
|
|
393
|
+
collectHoistedVars(node.body, fnScope, true, true);
|
|
394
|
+
if (node.body?.type === 'BlockStatement' || node.body?.type === 'JSXCodeBlock') {
|
|
395
|
+
predeclareDirect(node.body.body, fnScope, hookRuntimeModules, true);
|
|
396
|
+
for (const statement of node.body.body || []) walk(statement, fnScope);
|
|
397
|
+
if (node.body.type === 'JSXCodeBlock') walk(node.body.render, fnScope);
|
|
398
|
+
} else {
|
|
399
|
+
walk(node.body, fnScope);
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
collectHoistedVars(node.body, fnScope);
|
|
403
|
+
for (const param of node.params || []) walkPatternDefaults(param, fnScope);
|
|
404
|
+
walk(node.body, fnScope);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (bindingsOnly && node.type === 'ImportDeclaration') {
|
|
410
|
+
if (node.importKind !== 'type') {
|
|
411
|
+
for (const specifier of node.specifiers || []) {
|
|
412
|
+
if (specifier.importKind !== 'type') rememberPattern(specifier.local, scope);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
bindingsOnly &&
|
|
420
|
+
node.declare !== true &&
|
|
421
|
+
((node.type === 'TSModuleDeclaration' && node.kind !== 'global') ||
|
|
422
|
+
node.type === 'TSEnumDeclaration')
|
|
423
|
+
) {
|
|
424
|
+
// The dependency walk deliberately skips TypeScript-only syntax. Runtime
|
|
425
|
+
// namespaces and enum initializers can still write their enclosing scope.
|
|
426
|
+
invalidateVisibleBindings(scope);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (bindingsOnly && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) {
|
|
431
|
+
if (node.type === 'ClassDeclaration' && node.id) rememberPattern(node.id, scope);
|
|
432
|
+
const classScope = createScope(scope, 'block');
|
|
433
|
+
if (node.id) declarePattern(node.id, classScope);
|
|
434
|
+
for (const decorator of node.decorators || []) walk(decorator, scope);
|
|
435
|
+
walk(node.superClass, classScope);
|
|
436
|
+
walk(node.body, classScope);
|
|
329
437
|
return;
|
|
330
438
|
}
|
|
331
439
|
|
|
@@ -334,8 +442,14 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
334
442
|
node.type === 'StaticBlock' ||
|
|
335
443
|
node.type === 'JSXCodeBlock'
|
|
336
444
|
) {
|
|
337
|
-
const blockScope = createScope(
|
|
338
|
-
|
|
445
|
+
const blockScope = createScope(
|
|
446
|
+
scope,
|
|
447
|
+
bindingsOnly && node.type === 'StaticBlock' ? 'function' : 'block',
|
|
448
|
+
);
|
|
449
|
+
if (bindingsOnly && node.type === 'StaticBlock') {
|
|
450
|
+
collectHoistedVars(node, blockScope, true, true);
|
|
451
|
+
}
|
|
452
|
+
predeclareDirect(node.body, blockScope, hookRuntimeModules, bindingsOnly);
|
|
339
453
|
for (const statement of node.body || []) walk(statement, blockScope);
|
|
340
454
|
// TSRX's final render node lives beside the setup-statement list.
|
|
341
455
|
if (node.type === 'JSXCodeBlock') walk(node.render, blockScope);
|
|
@@ -345,12 +459,17 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
345
459
|
if (node.type === 'CatchClause') {
|
|
346
460
|
const catchScope = createScope(scope, 'block');
|
|
347
461
|
declarePattern(node.param, catchScope);
|
|
462
|
+
if (bindingsOnly) rememberPattern(node.param, catchScope);
|
|
463
|
+
if (bindingsOnly && node.resetParam) {
|
|
464
|
+
declarePattern(node.resetParam, catchScope);
|
|
465
|
+
rememberPattern(node.resetParam, catchScope);
|
|
466
|
+
}
|
|
348
467
|
walkPatternDefaults(node.param, catchScope);
|
|
349
468
|
walk(node.body, catchScope);
|
|
350
469
|
return;
|
|
351
470
|
}
|
|
352
471
|
|
|
353
|
-
if (node.type === 'SwitchStatement') {
|
|
472
|
+
if (node.type === 'SwitchStatement' || (bindingsOnly && node.type === 'JSXSwitchExpression')) {
|
|
354
473
|
// A switch body is one lexical scope shared by every unbraced case.
|
|
355
474
|
// Predeclare the direct case statements together so let/const/function
|
|
356
475
|
// captures resolve even when their declaration appears in a case list
|
|
@@ -360,10 +479,10 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
360
479
|
for (const switchCase of node.cases || []) {
|
|
361
480
|
statements.push(...(switchCase.consequent || []));
|
|
362
481
|
}
|
|
363
|
-
predeclareDirect(statements, switchScope, hookRuntimeModules);
|
|
482
|
+
predeclareDirect(statements, switchScope, hookRuntimeModules, bindingsOnly);
|
|
364
483
|
walk(node.discriminant, scope);
|
|
365
484
|
for (const switchCase of node.cases || []) {
|
|
366
|
-
nodeScopes.set(switchCase, switchScope);
|
|
485
|
+
if (!bindingsOnly) nodeScopes.set(switchCase, switchScope);
|
|
367
486
|
walk(switchCase.test, switchScope);
|
|
368
487
|
for (const statement of switchCase.consequent || []) walk(statement, switchScope);
|
|
369
488
|
}
|
|
@@ -373,32 +492,45 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
373
492
|
if (
|
|
374
493
|
node.type === 'ForStatement' ||
|
|
375
494
|
node.type === 'ForInStatement' ||
|
|
376
|
-
node.type === 'ForOfStatement'
|
|
495
|
+
node.type === 'ForOfStatement' ||
|
|
496
|
+
(bindingsOnly && node.type === 'JSXForExpression')
|
|
377
497
|
) {
|
|
378
498
|
const loopScope = createScope(scope, 'block');
|
|
379
|
-
const
|
|
499
|
+
const loopType = node.type === 'JSXForExpression' ? node.statementType : node.type;
|
|
500
|
+
const declaration = loopType === 'ForStatement' ? node.init : node.left;
|
|
380
501
|
if (declaration?.type === 'VariableDeclaration' && declaration.kind !== 'var') {
|
|
381
502
|
for (const decl of declaration.declarations || []) declarePattern(decl.id, loopScope);
|
|
382
503
|
}
|
|
383
|
-
if (
|
|
504
|
+
if (loopType === 'ForStatement') {
|
|
384
505
|
walk(node.init, loopScope);
|
|
385
506
|
walk(node.test, loopScope);
|
|
386
507
|
walk(node.update, loopScope);
|
|
387
508
|
} else {
|
|
388
509
|
walk(node.left, loopScope);
|
|
389
510
|
if (node.left?.type !== 'VariableDeclaration') markReassignedPattern(node.left, loopScope);
|
|
390
|
-
|
|
511
|
+
else if (bindingsOnly && node.left.kind === 'var') {
|
|
512
|
+
for (const decl of node.left.declarations || [])
|
|
513
|
+
markReassignedPattern(decl.id, loopScope);
|
|
514
|
+
}
|
|
515
|
+
walk(node.right, node.type === 'JSXForExpression' ? scope : loopScope);
|
|
516
|
+
}
|
|
517
|
+
if (bindingsOnly && node.type === 'JSXForExpression') {
|
|
518
|
+
declarePattern(node.index, loopScope);
|
|
519
|
+
rememberPattern(node.index, loopScope);
|
|
520
|
+
walk(node.key, loopScope);
|
|
391
521
|
}
|
|
392
522
|
walk(node.body, loopScope);
|
|
523
|
+
if (bindingsOnly && node.type === 'JSXForExpression') walk(node.empty, scope);
|
|
393
524
|
return;
|
|
394
525
|
}
|
|
395
526
|
|
|
396
527
|
if (node.type === 'VariableDeclaration') {
|
|
397
528
|
for (const decl of node.declarations || []) {
|
|
398
|
-
nodeScopes.set(decl, scope);
|
|
529
|
+
if (!bindingsOnly) nodeScopes.set(decl, scope);
|
|
399
530
|
const bindings = [];
|
|
400
531
|
collectPatternBindings(decl.id, scope, bindings);
|
|
401
|
-
|
|
532
|
+
if (bindingsOnly) rememberBindings(bindings, node.kind !== 'var' || decl.init != null);
|
|
533
|
+
else declarators.push({ decl, bindings, kind: node.kind });
|
|
402
534
|
walkPatternDefaults(decl.id, scope);
|
|
403
535
|
walk(decl.init, scope);
|
|
404
536
|
}
|
|
@@ -411,7 +543,21 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
411
543
|
markReassignedPattern(node.argument, scope);
|
|
412
544
|
}
|
|
413
545
|
|
|
414
|
-
if (node.type === 'CallExpression') {
|
|
546
|
+
if (bindingsOnly && node.type === 'CallExpression') {
|
|
547
|
+
let callee = unwrapValue(node.callee);
|
|
548
|
+
while (callee?.type === 'TSInstantiationExpression') {
|
|
549
|
+
callee = unwrapValue(callee.expression);
|
|
550
|
+
}
|
|
551
|
+
if (
|
|
552
|
+
node.optional !== true &&
|
|
553
|
+
callee?.type === 'Identifier' &&
|
|
554
|
+
callee.name === 'eval' &&
|
|
555
|
+
resolveBinding(scope, callee.name) === null
|
|
556
|
+
) {
|
|
557
|
+
// Direct eval can reassign any lexically reachable writable binding.
|
|
558
|
+
invalidateVisibleBindings(scope);
|
|
559
|
+
}
|
|
560
|
+
} else if (node.type === 'CallExpression') {
|
|
415
561
|
// Preserve lexical import identity for the later slotting pass. A module-
|
|
416
562
|
// level name map is insufficient: a component can shadow either a named
|
|
417
563
|
// alias or an Octane namespace inside any nested scope. The annotation is
|
|
@@ -471,6 +617,9 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
471
617
|
function walkPatternDefaults(pattern, scope) {
|
|
472
618
|
if (!pattern) return;
|
|
473
619
|
switch (pattern.type) {
|
|
620
|
+
case 'TSParameterProperty':
|
|
621
|
+
if (bindingsOnly) walkPatternDefaults(pattern.parameter, scope);
|
|
622
|
+
return;
|
|
474
623
|
case 'AssignmentPattern':
|
|
475
624
|
walkPatternDefaults(pattern.left, scope);
|
|
476
625
|
walk(pattern.right, scope);
|
|
@@ -513,7 +662,9 @@ function buildScopes(ast, onlyImported, hookRuntimeModules) {
|
|
|
513
662
|
functions,
|
|
514
663
|
trustedHookNames,
|
|
515
664
|
callAnnotations,
|
|
665
|
+
declarationBindings,
|
|
516
666
|
};
|
|
667
|
+
if (bindingsOnly) return analysis;
|
|
517
668
|
// The surgical plain-TS pass slots base hooks only; without a custom-hook
|
|
518
669
|
// withSlot boundary, two local wrapper calls would share their inner slots.
|
|
519
670
|
// Restrict custom-call inference to the full TSRX/TSX compiler, which emits
|
|
@@ -1252,6 +1403,25 @@ function analyzeInternal(ast, options) {
|
|
|
1252
1403
|
return { analysis, inferred };
|
|
1253
1404
|
}
|
|
1254
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* Return authored declaration identifiers whose bindings have a write or a
|
|
1408
|
+
* competing value definition. This is a read-only, scope-aware proof for callers
|
|
1409
|
+
* that recreate lexical scopes per invocation. Absence from the set does not
|
|
1410
|
+
* make a mutable declaration kind immutable by itself.
|
|
1411
|
+
*
|
|
1412
|
+
* @param {any} ast
|
|
1413
|
+
* @returns {WeakSet<import('estree').Identifier>}
|
|
1414
|
+
*/
|
|
1415
|
+
export function collectReassignedBindings(ast) {
|
|
1416
|
+
const { declarationBindings } = buildScopes(ast, true, new Set(), true);
|
|
1417
|
+
/** @type {WeakSet<import('estree').Identifier>} */
|
|
1418
|
+
const reassigned = new WeakSet();
|
|
1419
|
+
for (const [node, binding] of declarationBindings ?? []) {
|
|
1420
|
+
if (binding.reassigned) reassigned.add(node);
|
|
1421
|
+
}
|
|
1422
|
+
return reassigned;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1255
1425
|
/**
|
|
1256
1426
|
* Return inferred dependency expressions for every supported hook call whose
|
|
1257
1427
|
* dependency argument is omitted. Explicit arrays, `null`, and any other
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseModule } from '@tsrx/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseModule } from 'oxc-tsrx/tsrx-core-compat';
|