@fictjs/compiler 0.8.0 → 0.10.0

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/index.js CHANGED
@@ -14226,6 +14226,9 @@ var RUNTIME_ALIASES = {
14226
14226
  hydrateComponent: "hydrateComponent",
14227
14227
  registerResume: "__fictRegisterResume"
14228
14228
  };
14229
+ var RUNTIME_HELPER_MODULES = {
14230
+ keyedList: "@fictjs/runtime/internal/list"
14231
+ };
14229
14232
  var DelegatedEvents = /* @__PURE__ */ new Set([...DelegatedEventNames]);
14230
14233
  var SAFE_FUNCTIONS = /* @__PURE__ */ new Set([
14231
14234
  // Console methods
@@ -19924,15 +19927,38 @@ function buildEffectCall(ctx, t4, effectFn, options) {
19924
19927
  }
19925
19928
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.useEffect), args);
19926
19929
  }
19927
- function buildMemoCall(ctx, t4, memoFn, slot) {
19930
+ function buildMemoCall(ctx, t4, memoFn, options) {
19931
+ const slot = options?.slot;
19932
+ const memoOptionsProperties = [];
19933
+ if (options?.name) {
19934
+ memoOptionsProperties.push(
19935
+ t4.objectProperty(t4.identifier("name"), t4.stringLiteral(options.name))
19936
+ );
19937
+ }
19938
+ if (options?.source) {
19939
+ memoOptionsProperties.push(
19940
+ t4.objectProperty(t4.identifier("devToolsSource"), t4.stringLiteral(options.source))
19941
+ );
19942
+ }
19943
+ if (options?.internal) {
19944
+ memoOptionsProperties.push(t4.objectProperty(t4.identifier("internal"), t4.booleanLiteral(true)));
19945
+ }
19946
+ const memoOptions = memoOptionsProperties.length > 0 ? t4.objectExpression(memoOptionsProperties) : null;
19928
19947
  if (ctx.inModule) {
19929
19948
  ctx.helpersUsed.add("memo");
19930
- return t4.callExpression(t4.identifier(RUNTIME_ALIASES.memo), [memoFn]);
19949
+ const args2 = [memoFn];
19950
+ if (memoOptions) args2.push(memoOptions);
19951
+ return t4.callExpression(t4.identifier(RUNTIME_ALIASES.memo), args2);
19931
19952
  }
19932
19953
  ctx.helpersUsed.add("useMemo");
19933
19954
  ctx.needsCtx = true;
19934
19955
  const args = [t4.identifier("__fictCtx"), memoFn];
19935
- if (slot !== void 0 && slot >= 0) {
19956
+ if (memoOptions) {
19957
+ args.push(memoOptions);
19958
+ if (slot !== void 0 && slot >= 0) {
19959
+ args.push(t4.numericLiteral(slot));
19960
+ }
19961
+ } else if (slot !== void 0 && slot >= 0) {
19936
19962
  args.push(t4.numericLiteral(slot));
19937
19963
  }
19938
19964
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.useMemo), args);
@@ -21193,7 +21219,10 @@ function wrapInMemo(region, t4, declaredVars, ctx, bodyStatementsOverride, outpu
21193
21219
  const returnObj = t4.objectExpression(uniqueOutputNames.map((name) => buildOutputProperty(name)));
21194
21220
  const memoBody = t4.blockStatement([...bodyStatements, t4.returnStatement(returnObj)]);
21195
21221
  const slot = ctx.inModule ? void 0 : reserveHookSlot(ctx);
21196
- const memoCall = buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], memoBody), slot);
21222
+ const memoCall = buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], memoBody), {
21223
+ slot,
21224
+ internal: true
21225
+ });
21197
21226
  const regionVarName = `__region_${region.id}`;
21198
21227
  statements.push(
21199
21228
  t4.variableDeclaration("const", [t4.variableDeclarator(t4.identifier(regionVarName), memoCall)])
@@ -21452,7 +21481,10 @@ function generateLazyConditionalMemo(region, orderedOutputs, bodyStatements, con
21452
21481
  ctx,
21453
21482
  t4,
21454
21483
  t4.arrowFunctionExpression([], t4.blockStatement(memoBody)),
21455
- ctx.inModule ? void 0 : reserveHookSlot(ctx)
21484
+ {
21485
+ slot: ctx.inModule ? void 0 : reserveHookSlot(ctx),
21486
+ internal: true
21487
+ }
21456
21488
  );
21457
21489
  statements.push(
21458
21490
  t4.variableDeclaration("const", [t4.variableDeclarator(t4.identifier(regionVarName), memoCall)])
@@ -21565,7 +21597,12 @@ Context: ${location}`
21565
21597
  };
21566
21598
  const buildDerivedMemoCall = (expr) => {
21567
21599
  const slot = !ctx.inModule && inRegionMemo ? reserveHookSlot(ctx) : void 0;
21568
- return buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], expr), slot);
21600
+ const source = ctx.options?.dev !== false && instr.loc ? `${ctx.options?.filename ?? ""}:${instr.loc.start.line}:${instr.loc.start.column}` : void 0;
21601
+ return buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], expr), {
21602
+ slot,
21603
+ name: baseName2,
21604
+ source
21605
+ });
21569
21606
  };
21570
21607
  if (isShadowDeclaration && declKind) {
21571
21608
  ctx.trackedVars.delete(baseName2);
@@ -22927,7 +22964,7 @@ function expressionUsesIdentifier(expr, name, t4) {
22927
22964
  visit(expr);
22928
22965
  return found;
22929
22966
  }
22930
- function extractDelegatedEventData(expr, t4) {
22967
+ function extractDelegatedEventData(expr, t4, options) {
22931
22968
  const isSimpleHandler = t4.isIdentifier(expr) || t4.isMemberExpression(expr);
22932
22969
  if (isSimpleHandler) {
22933
22970
  return { handler: expr };
@@ -22940,6 +22977,9 @@ function extractDelegatedEventData(expr, t4) {
22940
22977
  if (!bodyExpr || !t4.isCallExpression(bodyExpr)) return null;
22941
22978
  if (paramNames.some((name) => expressionUsesIdentifier(bodyExpr, name, t4))) return null;
22942
22979
  if (!t4.isIdentifier(bodyExpr.callee)) return null;
22980
+ if (options?.isKnownHandlerIdentifier && !options.isKnownHandlerIdentifier(bodyExpr.callee.name)) {
22981
+ return null;
22982
+ }
22943
22983
  if (bodyExpr.arguments.length === 0) return null;
22944
22984
  if (bodyExpr.arguments.length > 1) return null;
22945
22985
  const dataArg = bodyExpr.arguments[0];
@@ -23002,6 +23042,10 @@ function extractDelegatedEventDataFromHIR(expr, ctx) {
23002
23042
  return null;
23003
23043
  }
23004
23044
  const handlerName = callee.name;
23045
+ const normalizedHandlerName = deSSAVarName(handlerName);
23046
+ if (!ctx.functionVars?.has(normalizedHandlerName)) {
23047
+ return null;
23048
+ }
23005
23049
  if (ctx.signalVars?.has(handlerName) || ctx.memoVars?.has(handlerName) || ctx.aliasVars?.has(handlerName) || ctx.storeVars?.has(handlerName) || ctx.trackedVars.has(handlerName)) {
23006
23050
  return null;
23007
23051
  }
@@ -23012,7 +23056,10 @@ function extractDelegatedEventDataFromHIR(expr, ctx) {
23012
23056
  if (isTrackedAccessor) {
23013
23057
  return null;
23014
23058
  }
23015
- const paramNames = new Set(expr.params.map((p) => p.name));
23059
+ const paramNames = new Set(expr.params.map((p) => deSSAVarName(p.name)));
23060
+ if (paramNames.has(deSSAVarName(callee.name))) {
23061
+ return null;
23062
+ }
23016
23063
  const dataExpr = bodyExpr.arguments[0];
23017
23064
  if (!dataExpr) {
23018
23065
  return null;
@@ -24051,17 +24098,19 @@ function collectDeclaredNames(body, t4) {
24051
24098
  function attachHelperImports(ctx, body, t4) {
24052
24099
  if (ctx.helpersUsed.size === 0) return body;
24053
24100
  const declared = collectDeclaredNames(body, t4);
24054
- const specifiers = [];
24101
+ const specifiersByModule = /* @__PURE__ */ new Map();
24055
24102
  for (const name of ctx.helpersUsed) {
24056
24103
  const alias = RUNTIME_ALIASES[name];
24057
24104
  const helper = RUNTIME_HELPERS[name];
24058
24105
  if (alias && helper) {
24059
24106
  if (declared.has(alias)) continue;
24060
- specifiers.push(t4.importSpecifier(t4.identifier(alias), t4.identifier(helper)));
24107
+ const modulePath = RUNTIME_HELPER_MODULES[name] ?? RUNTIME_MODULE;
24108
+ const moduleSpecifiers = specifiersByModule.get(modulePath) ?? [];
24109
+ moduleSpecifiers.push(t4.importSpecifier(t4.identifier(alias), t4.identifier(helper)));
24110
+ specifiersByModule.set(modulePath, moduleSpecifiers);
24061
24111
  }
24062
24112
  }
24063
- if (specifiers.length === 0) return body;
24064
- const importDecl = t4.importDeclaration(specifiers, t4.stringLiteral(RUNTIME_MODULE));
24113
+ if (specifiersByModule.size === 0) return body;
24065
24114
  const helpers = [];
24066
24115
  if (ctx.needsForOfHelper) {
24067
24116
  const itemId = t4.identifier("item");
@@ -24099,7 +24148,15 @@ function attachHelperImports(ctx, body, t4) {
24099
24148
  )
24100
24149
  );
24101
24150
  }
24102
- return [importDecl, ...helpers, ...body];
24151
+ const modulePaths = Array.from(specifiersByModule.keys()).sort((a, b) => {
24152
+ if (a === RUNTIME_MODULE) return -1;
24153
+ if (b === RUNTIME_MODULE) return 1;
24154
+ return a.localeCompare(b);
24155
+ });
24156
+ const importDecls = modulePaths.map(
24157
+ (modulePath) => t4.importDeclaration(specifiersByModule.get(modulePath) ?? [], t4.stringLiteral(modulePath))
24158
+ );
24159
+ return [...importDecls, ...helpers, ...body];
24103
24160
  }
24104
24161
 
24105
24162
  // src/ir/codegen-jsx-keys.ts
@@ -24169,8 +24226,8 @@ function getDependencyPathFromNode(node, t4) {
24169
24226
  }
24170
24227
  return null;
24171
24228
  }
24172
- function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parentKey, skipCurrentNode = false) {
24173
- const isCallTarget = parentKey === "callee" && (parentKind === "CallExpression" || parentKind === "OptionalCallExpression");
24229
+ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parentKey, skipCurrentNode = false, allowCallCalleeReplacement = false) {
24230
+ const isCallTarget = !allowCallCalleeReplacement && parentKey === "callee" && (parentKind === "CallExpression" || parentKind === "OptionalCallExpression");
24174
24231
  if (parentKind === "VariableDeclarator" && parentKey === "id") {
24175
24232
  return;
24176
24233
  }
@@ -24242,9 +24299,25 @@ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parent
24242
24299
  }
24243
24300
  }
24244
24301
  if (t4.isBlockStatement(node.body)) {
24245
- replaceIdentifiersWithOverrides(node.body, scopedOverrides, t4, node.type, "body");
24302
+ replaceIdentifiersWithOverrides(
24303
+ node.body,
24304
+ scopedOverrides,
24305
+ t4,
24306
+ node.type,
24307
+ "body",
24308
+ false,
24309
+ allowCallCalleeReplacement
24310
+ );
24246
24311
  } else {
24247
- replaceIdentifiersWithOverrides(node.body, scopedOverrides, t4, node.type, "body");
24312
+ replaceIdentifiersWithOverrides(
24313
+ node.body,
24314
+ scopedOverrides,
24315
+ t4,
24316
+ node.type,
24317
+ "body",
24318
+ false,
24319
+ allowCallCalleeReplacement
24320
+ );
24248
24321
  }
24249
24322
  return;
24250
24323
  }
@@ -24272,12 +24345,21 @@ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parent
24272
24345
  t4,
24273
24346
  node.type,
24274
24347
  key,
24275
- false
24348
+ false,
24349
+ allowCallCalleeReplacement
24276
24350
  );
24277
24351
  }
24278
24352
  }
24279
24353
  } else if (value && typeof value === "object" && "type" in value) {
24280
- replaceIdentifiersWithOverrides(value, overrides, t4, node.type, key);
24354
+ replaceIdentifiersWithOverrides(
24355
+ value,
24356
+ overrides,
24357
+ t4,
24358
+ node.type,
24359
+ key,
24360
+ false,
24361
+ allowCallCalleeReplacement
24362
+ );
24281
24363
  }
24282
24364
  }
24283
24365
  }
@@ -24550,6 +24632,124 @@ function applySelectorHoist(callbackExpr, itemParamName, keyParamName, statement
24550
24632
  }
24551
24633
 
24552
24634
  // src/ir/codegen-list-child.ts
24635
+ function getCallbackBlocks(callback) {
24636
+ if (callback.kind === "FunctionExpression") {
24637
+ return callback.body;
24638
+ }
24639
+ if (callback.kind === "ArrowFunction" && Array.isArray(callback.body)) {
24640
+ return callback.body;
24641
+ }
24642
+ return [];
24643
+ }
24644
+ function collectMapCallbackAliasDeclarations(callback) {
24645
+ const blocks = getCallbackBlocks(callback);
24646
+ if (blocks.length === 0) {
24647
+ return /* @__PURE__ */ new Map();
24648
+ }
24649
+ const paramNames = callback.kind === "ArrowFunction" || callback.kind === "FunctionExpression" ? new Set(callback.params.map((param) => param.name)) : /* @__PURE__ */ new Set();
24650
+ const declarationState = /* @__PURE__ */ new Map();
24651
+ for (const block of blocks) {
24652
+ for (const instr of block.instructions) {
24653
+ if (instr.kind !== "Assign" || instr.target.kind !== "Identifier") {
24654
+ continue;
24655
+ }
24656
+ const name = instr.target.name;
24657
+ if (paramNames.has(name)) continue;
24658
+ const isDeclaration = !!instr.declarationKind;
24659
+ const previous = declarationState.get(name);
24660
+ if (previous) {
24661
+ declarationState.set(name, {
24662
+ declarationCount: previous.declarationCount + (isDeclaration ? 1 : 0),
24663
+ hasNonDeclarationWrite: previous.hasNonDeclarationWrite || !isDeclaration,
24664
+ declarationValue: previous.declarationValue,
24665
+ lastAssignedValue: instr.value
24666
+ });
24667
+ } else {
24668
+ declarationState.set(name, {
24669
+ declarationCount: isDeclaration ? 1 : 0,
24670
+ hasNonDeclarationWrite: !isDeclaration,
24671
+ declarationValue: isDeclaration ? instr.value : null,
24672
+ lastAssignedValue: instr.value
24673
+ });
24674
+ }
24675
+ }
24676
+ }
24677
+ const aliasMap = /* @__PURE__ */ new Map();
24678
+ const effectiveBlocks = blocks.filter(
24679
+ (block) => block.instructions.length > 0 || block.terminator.kind !== "Unreachable"
24680
+ );
24681
+ const isSingleLinearBlock = effectiveBlocks.length === 1 && effectiveBlocks[0].terminator.kind === "Return";
24682
+ for (const [name, state] of declarationState) {
24683
+ if (isSingleLinearBlock) {
24684
+ if (state.declarationCount <= 1) {
24685
+ aliasMap.set(name, state.lastAssignedValue);
24686
+ }
24687
+ continue;
24688
+ }
24689
+ if (state.declarationCount === 1 && !state.hasNonDeclarationWrite && state.declarationValue) {
24690
+ aliasMap.set(name, state.declarationValue);
24691
+ }
24692
+ }
24693
+ return aliasMap;
24694
+ }
24695
+ function resolveMapCallbackKeyExpression(keyExpr, callback) {
24696
+ if (keyExpr.kind !== "Identifier") {
24697
+ return keyExpr;
24698
+ }
24699
+ const aliasMap = collectMapCallbackAliasDeclarations(callback);
24700
+ if (aliasMap.size === 0) {
24701
+ return keyExpr;
24702
+ }
24703
+ let resolved = keyExpr;
24704
+ const seen = /* @__PURE__ */ new Set();
24705
+ while (resolved.kind === "Identifier") {
24706
+ const next = aliasMap.get(resolved.name);
24707
+ if (!next || seen.has(resolved.name)) break;
24708
+ seen.add(resolved.name);
24709
+ resolved = next;
24710
+ }
24711
+ return resolved;
24712
+ }
24713
+ function collectMapCallbackLocalNames(callback) {
24714
+ const blocks = getCallbackBlocks(callback);
24715
+ if (blocks.length === 0) {
24716
+ return /* @__PURE__ */ new Set();
24717
+ }
24718
+ const paramNames = callback.kind === "ArrowFunction" || callback.kind === "FunctionExpression" ? new Set(callback.params.map((param) => deSSAVarName(param.name))) : /* @__PURE__ */ new Set();
24719
+ const locals = /* @__PURE__ */ new Set();
24720
+ for (const block of blocks) {
24721
+ for (const instr of block.instructions) {
24722
+ if (instr.kind === "Assign" && instr.target.kind === "Identifier") {
24723
+ const name = deSSAVarName(instr.target.name);
24724
+ if (!paramNames.has(name)) locals.add(name);
24725
+ }
24726
+ if (instr.kind === "Phi" && instr.target.kind === "Identifier") {
24727
+ const name = deSSAVarName(instr.target.name);
24728
+ if (!paramNames.has(name)) locals.add(name);
24729
+ }
24730
+ }
24731
+ }
24732
+ return locals;
24733
+ }
24734
+ function hasUnresolvedCallbackLocalKeyDependencies(keyExpr, callback, keyAliasDeclarations) {
24735
+ const callbackLocals = collectMapCallbackLocalNames(callback);
24736
+ if (callbackLocals.size === 0) {
24737
+ return false;
24738
+ }
24739
+ const resolvableAliases = new Set(
24740
+ Array.from(keyAliasDeclarations.keys()).map((name) => deSSAVarName(name))
24741
+ );
24742
+ const deps = /* @__PURE__ */ new Set();
24743
+ collectExpressionDependencies(keyExpr, deps);
24744
+ for (const dep of deps) {
24745
+ const base = dep.split(".")[0] ?? dep;
24746
+ if (!base) continue;
24747
+ if (callbackLocals.has(base) && !resolvableAliases.has(base)) {
24748
+ return true;
24749
+ }
24750
+ }
24751
+ return false;
24752
+ }
24553
24753
  function buildListCallExpression(expr, statements, ctx, ops) {
24554
24754
  const { t: t4 } = ctx;
24555
24755
  if (expr.kind !== "CallExpression" && expr.kind !== "OptionalCallExpression") {
@@ -24568,7 +24768,8 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24568
24768
  if (!mapCallback) {
24569
24769
  throw new Error("map callback is required");
24570
24770
  }
24571
- const keyExpr = extractKeyFromMapCallback(mapCallback);
24771
+ const extractedKeyExpr = extractKeyFromMapCallback(mapCallback);
24772
+ const keyExpr = extractedKeyExpr ? resolveMapCallbackKeyExpression(extractedKeyExpr, mapCallback) : void 0;
24572
24773
  const isKeyed = !!keyExpr;
24573
24774
  const hasRestParam = (mapCallback.kind === "ArrowFunction" || mapCallback.kind === "FunctionExpression") && Array.isArray(mapCallback.rawParams) && mapCallback.rawParams.some((param) => t4.isRestElement(param));
24574
24775
  const canConstifyKey = isKeyed && keyExpr && !hasRestParam;
@@ -24619,7 +24820,7 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24619
24820
  if (Object.keys(overrides).length > 0) {
24620
24821
  if (t4.isBlockStatement(callbackExpr.body)) {
24621
24822
  for (const stmt of callbackExpr.body.body) {
24622
- if (!t4.isVariableDeclaration(stmt)) continue;
24823
+ if (!t4.isVariableDeclaration(stmt) || stmt.kind !== "const") continue;
24623
24824
  for (const decl of stmt.declarations) {
24624
24825
  if (!t4.isIdentifier(decl.id) || !decl.init) continue;
24625
24826
  const replacement = t4.cloneNode(decl.init, true);
@@ -24649,7 +24850,32 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24649
24850
  }
24650
24851
  let listCall;
24651
24852
  if (isKeyed && keyExpr) {
24853
+ const keyAliasDeclarations = collectMapCallbackAliasDeclarations(mapCallback);
24854
+ const hasUnresolvedLocalKeyDeps = hasUnresolvedCallbackLocalKeyDependencies(
24855
+ keyExpr,
24856
+ mapCallback,
24857
+ keyAliasDeclarations
24858
+ );
24652
24859
  let keyExprAst = ops.lowerExpression(keyExpr, ctx);
24860
+ if (keyAliasDeclarations.size > 0) {
24861
+ const keyOverrides = {};
24862
+ for (const [name, value] of keyAliasDeclarations) {
24863
+ const replacement = ops.lowerExpression(value, ctx);
24864
+ replaceIdentifiersWithOverrides(replacement, keyOverrides, t4);
24865
+ keyOverrides[name] = () => t4.cloneNode(replacement, true);
24866
+ }
24867
+ if (Object.keys(keyOverrides).length > 0) {
24868
+ replaceIdentifiersWithOverrides(
24869
+ keyExprAst,
24870
+ keyOverrides,
24871
+ t4,
24872
+ void 0,
24873
+ void 0,
24874
+ false,
24875
+ true
24876
+ );
24877
+ }
24878
+ }
24653
24879
  if (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) {
24654
24880
  const itemParam = callbackExpr.params[0];
24655
24881
  const indexParam = callbackExpr.params[1];
@@ -24663,6 +24889,9 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24663
24889
  }
24664
24890
  const itemParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? callbackExpr.params[0] : null;
24665
24891
  const indexParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? callbackExpr.params[1] : null;
24892
+ if (hasUnresolvedLocalKeyDeps) {
24893
+ keyExprAst = t4.identifier(t4.isIdentifier(indexParamName) ? indexParamName.name : "__index");
24894
+ }
24666
24895
  const keyFn = t4.arrowFunctionExpression(
24667
24896
  [
24668
24897
  t4.isIdentifier(itemParamName) ? itemParamName : t4.identifier("__item"),
@@ -25104,33 +25333,42 @@ function dependencyCoveredByDeclarations(dep, region) {
25104
25333
 
25105
25334
  // src/ir/codegen-resumable-utils.ts
25106
25335
  import { pathToFileURL } from "url";
25107
- function renameIdentifiersInExpr(expr, renames) {
25108
- const cloned = JSON.parse(JSON.stringify(expr));
25109
- function visit(node) {
25110
- if (!node || typeof node !== "object") return;
25111
- const n = node;
25112
- if (n.type === "Identifier" && typeof n.name === "string") {
25113
- const newName = renames.get(n.name);
25114
- if (newName) {
25115
- n.name = newName;
25116
- }
25117
- }
25118
- for (const key of Object.keys(n)) {
25119
- if (key === "loc" || key === "start" || key === "end" || key === "extra" || key === "comments" || key === "leadingComments" || key === "trailingComments") {
25120
- continue;
25121
- }
25122
- const value = n[key];
25123
- if (Array.isArray(value)) {
25124
- for (const item of value) {
25125
- visit(item);
25126
- }
25127
- } else if (value && typeof value === "object") {
25128
- visit(value);
25336
+ import traverseModule2 from "@babel/traverse";
25337
+ function renameIdentifiersInExpr(expr, renames, t4) {
25338
+ const traverse = traverseModule2.default ?? traverseModule2;
25339
+ const cloned = t4.cloneNode(expr, true);
25340
+ const file = t4.file(t4.program([t4.expressionStatement(cloned)]));
25341
+ traverse(file, {
25342
+ Identifier(path2) {
25343
+ const oldName = path2.node.name;
25344
+ const nextName = renames.get(oldName);
25345
+ if (!nextName) return;
25346
+ if (path2.parentPath.isObjectProperty() && path2.parentPath.node.shorthand && path2.parentPath.node.value === path2.node && t4.isIdentifier(path2.parentPath.node.key)) {
25347
+ path2.parentPath.node.shorthand = false;
25348
+ path2.parentPath.node.value = t4.identifier(nextName);
25349
+ return;
25129
25350
  }
25351
+ if (!path2.isReferencedIdentifier()) return;
25352
+ const binding = path2.scope.getBinding(oldName);
25353
+ if (binding && binding.scope !== path2.scope.getProgramParent()) return;
25354
+ path2.node.name = nextName;
25130
25355
  }
25131
- }
25132
- visit(cloned);
25133
- return cloned;
25356
+ });
25357
+ const first = file.program.body[0];
25358
+ return t4.isExpressionStatement(first) ? first.expression : cloned;
25359
+ }
25360
+ function collectFreeIdentifiersInExpr(expr, t4) {
25361
+ const traverse = traverseModule2.default ?? traverseModule2;
25362
+ const file = t4.file(t4.program([t4.expressionStatement(t4.cloneNode(expr, true))]));
25363
+ const names = /* @__PURE__ */ new Set();
25364
+ traverse(file, {
25365
+ ReferencedIdentifier(path2) {
25366
+ const name = path2.node.name;
25367
+ if (path2.scope.getBinding(name)) return;
25368
+ names.add(name);
25369
+ }
25370
+ });
25371
+ return names;
25134
25372
  }
25135
25373
  function genModuleUrlExpr(ctx) {
25136
25374
  const { t: t4 } = ctx;
@@ -25267,10 +25505,10 @@ function registerResumableComponent(componentName, ctx) {
25267
25505
  }
25268
25506
 
25269
25507
  // src/ir/codegen-resumable-events.ts
25270
- function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, containingRegion, ops) {
25508
+ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, containingRegion, ops, options) {
25271
25509
  const { t: t4 } = ctx;
25272
25510
  if (!ctx.resumableEnabled) {
25273
- return;
25511
+ return false;
25274
25512
  }
25275
25513
  const prevWrapTracked = ctx.wrapTrackedExpressions;
25276
25514
  ctx.wrapTrackedExpressions = false;
@@ -25284,53 +25522,88 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25284
25522
  const scopeParam = t4.identifier("scopeId");
25285
25523
  const ensureHandlerParam = (fn) => {
25286
25524
  if (t4.isArrowFunctionExpression(fn)) {
25287
- if (fn.params.length > 0) return fn;
25288
- return t4.arrowFunctionExpression([eventParam], fn.body, fn.async);
25525
+ return fn;
25289
25526
  }
25290
25527
  if (t4.isFunctionExpression(fn)) {
25291
- if (fn.params.length > 0) return fn;
25292
- return t4.functionExpression(fn.id, [eventParam], fn.body, fn.generator, fn.async);
25528
+ return fn;
25293
25529
  }
25294
25530
  if (t4.isIdentifier(fn) || t4.isMemberExpression(fn)) {
25295
25531
  return fn;
25296
25532
  }
25297
- if (t4.isCallExpression(fn) && fn.arguments.length === 0 && (t4.isIdentifier(fn.callee) || t4.isMemberExpression(fn.callee))) {
25298
- return fn.callee;
25299
- }
25300
25533
  return t4.functionExpression(
25301
25534
  null,
25302
- [eventParam],
25303
- t4.blockStatement([
25304
- t4.returnStatement(
25305
- t4.callExpression(
25306
- t4.memberExpression(fn, t4.identifier("call")),
25307
- [t4.thisExpression(), eventParam]
25308
- )
25309
- )
25310
- ])
25535
+ [],
25536
+ t4.blockStatement([t4.returnStatement(fn)])
25311
25537
  );
25312
25538
  };
25313
25539
  const handlerExpr = ensureHandlerParam(valueExpr);
25314
25540
  const handlerId = t4.identifier(`__fict_e${ctx.resumableHandlerCounter ?? 0}`);
25315
25541
  ctx.resumableHandlerCounter = (ctx.resumableHandlerCounter ?? 0) + 1;
25316
- const captured = /* @__PURE__ */ new Set();
25317
- collectExpressionIdentifiersDeep(expr, captured);
25542
+ const captured = collectFreeIdentifiersInExpr(handlerExpr, t4);
25318
25543
  const lexicalNames = Array.from(captured).filter((name) => ctx.signalVars?.has(name));
25319
25544
  const propsName = ctx.propsParamName && captured.has(ctx.propsParamName) ? ctx.propsParamName : null;
25545
+ const unsupportedLocals = Array.from(captured).filter((name) => {
25546
+ if (ctx.inListRender && ctx.listKeyParamName && name === ctx.listKeyParamName) return true;
25547
+ if (!ctx.localDeclaredNames?.has(name)) return false;
25548
+ if (ctx.signalVars?.has(name)) return false;
25549
+ if (ctx.functionVars?.has(name)) return false;
25550
+ if (propsName && name === propsName) return false;
25551
+ return true;
25552
+ });
25553
+ const loweredFunctionDeps = /* @__PURE__ */ new Map();
25554
+ const unsafeFunctionCaptures = [];
25555
+ for (const name of captured) {
25556
+ if (!ctx.functionVars?.has(name) || ctx.signalVars?.has(name)) continue;
25557
+ if (ctx.hoistedFunctionDepNames?.has(name)) continue;
25558
+ const hirDef = ctx.componentFunctionDefs?.get(name);
25559
+ if (!hirDef) {
25560
+ if (ctx.localDeclaredNames?.has(name)) {
25561
+ unsafeFunctionCaptures.push(`${name} -> <unhoistable>`);
25562
+ }
25563
+ continue;
25564
+ }
25565
+ const loweredFn = ops.lowerDomExpression(hirDef, ctx, null, {
25566
+ skipHookAccessors: true,
25567
+ skipRegionRootOverride: true
25568
+ });
25569
+ const fnCaptured = collectFreeIdentifiersInExpr(loweredFn, t4);
25570
+ const localFnCaptures = Array.from(fnCaptured).filter((dep) => ctx.localDeclaredNames?.has(dep)).sort();
25571
+ if (localFnCaptures.length > 0) {
25572
+ unsafeFunctionCaptures.push(`${name} -> ${localFnCaptures.join(", ")}`);
25573
+ continue;
25574
+ }
25575
+ loweredFunctionDeps.set(name, loweredFn);
25576
+ }
25577
+ if (unsupportedLocals.length > 0 || unsafeFunctionCaptures.length > 0) {
25578
+ const detailParts = [];
25579
+ if (unsupportedLocals.length > 0) {
25580
+ detailParts.push(`direct: ${unsupportedLocals.sort().join(", ")}`);
25581
+ }
25582
+ if (unsafeFunctionCaptures.length > 0) {
25583
+ detailParts.push(`function deps: ${unsafeFunctionCaptures.sort().join("; ")}`);
25584
+ }
25585
+ const detail = `Resumable handlers cannot capture non-serializable local variables (${detailParts.join(" | ")}).`;
25586
+ if (options?.explicit) {
25587
+ const loc = expr.loc?.start;
25588
+ const fileName = ctx.options?.filename ?? "<unknown>";
25589
+ const location = loc ? `${fileName}:${loc.line}:${loc.column + 1}` : fileName;
25590
+ throw new Error(
25591
+ `${detail} Use signals/props/function references or remove '$' suffix.
25592
+ at ${location}`
25593
+ );
25594
+ }
25595
+ return false;
25596
+ }
25320
25597
  const functionDepRenames = /* @__PURE__ */ new Map();
25321
25598
  for (const name of captured) {
25322
25599
  if (ctx.functionVars?.has(name) && !ctx.signalVars?.has(name)) {
25323
- const hirDef = ctx.componentFunctionDefs?.get(name);
25324
- if (!hirDef) continue;
25325
25600
  let hoistedName = ctx.hoistedFunctionDepNames?.get(name);
25326
25601
  if (!hoistedName) {
25602
+ const loweredFn = loweredFunctionDeps.get(name);
25603
+ if (!loweredFn) continue;
25327
25604
  hoistedName = `__fict_fn_${name}_${ctx.hoistedFunctionDepCounter ?? 0}`;
25328
25605
  ctx.hoistedFunctionDepCounter = (ctx.hoistedFunctionDepCounter ?? 0) + 1;
25329
25606
  ctx.hoistedFunctionDepNames?.set(name, hoistedName);
25330
- const loweredFn = ops.lowerDomExpression(hirDef, ctx, null, {
25331
- skipHookAccessors: true,
25332
- skipRegionRootOverride: true
25333
- });
25334
25607
  const hoistedDecl = t4.variableDeclaration("const", [
25335
25608
  t4.variableDeclarator(t4.identifier(hoistedName), loweredFn)
25336
25609
  ]);
@@ -25342,7 +25615,7 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25342
25615
  }
25343
25616
  let finalHandlerExpr = handlerExpr;
25344
25617
  if (functionDepRenames.size > 0) {
25345
- finalHandlerExpr = renameIdentifiersInExpr(handlerExpr, functionDepRenames);
25618
+ finalHandlerExpr = renameIdentifiersInExpr(handlerExpr, functionDepRenames, t4);
25346
25619
  }
25347
25620
  const bodyStatements = [];
25348
25621
  if (lexicalNames.length > 0) {
@@ -25375,12 +25648,97 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25375
25648
  );
25376
25649
  }
25377
25650
  const handlerVar = t4.identifier("__handler");
25651
+ const resultVar = t4.identifier("__result");
25378
25652
  bodyStatements.push(
25379
25653
  t4.variableDeclaration("const", [t4.variableDeclarator(handlerVar, finalHandlerExpr)])
25380
25654
  );
25381
25655
  bodyStatements.push(
25382
- t4.returnStatement(
25383
- t4.callExpression(t4.memberExpression(handlerVar, t4.identifier("call")), [elParam, eventParam])
25656
+ t4.ifStatement(
25657
+ t4.binaryExpression(
25658
+ "===",
25659
+ t4.unaryExpression("typeof", handlerVar),
25660
+ t4.stringLiteral("function")
25661
+ ),
25662
+ t4.blockStatement([
25663
+ t4.variableDeclaration("const", [
25664
+ t4.variableDeclarator(
25665
+ resultVar,
25666
+ t4.callExpression(t4.memberExpression(handlerVar, t4.identifier("call")), [
25667
+ elParam,
25668
+ eventParam
25669
+ ])
25670
+ )
25671
+ ]),
25672
+ t4.ifStatement(
25673
+ t4.logicalExpression(
25674
+ "&&",
25675
+ t4.binaryExpression(
25676
+ "===",
25677
+ t4.unaryExpression("typeof", resultVar),
25678
+ t4.stringLiteral("function")
25679
+ ),
25680
+ t4.binaryExpression("!==", resultVar, handlerVar)
25681
+ ),
25682
+ t4.blockStatement([
25683
+ t4.returnStatement(
25684
+ t4.callExpression(t4.memberExpression(resultVar, t4.identifier("call")), [
25685
+ elParam,
25686
+ eventParam
25687
+ ])
25688
+ )
25689
+ ])
25690
+ ),
25691
+ t4.ifStatement(
25692
+ t4.logicalExpression(
25693
+ "&&",
25694
+ resultVar,
25695
+ t4.binaryExpression(
25696
+ "===",
25697
+ t4.unaryExpression(
25698
+ "typeof",
25699
+ t4.memberExpression(resultVar, t4.identifier("handleEvent"))
25700
+ ),
25701
+ t4.stringLiteral("function")
25702
+ )
25703
+ ),
25704
+ t4.blockStatement([
25705
+ t4.returnStatement(
25706
+ t4.callExpression(
25707
+ t4.memberExpression(
25708
+ t4.memberExpression(resultVar, t4.identifier("handleEvent")),
25709
+ t4.identifier("call")
25710
+ ),
25711
+ [resultVar, eventParam]
25712
+ )
25713
+ )
25714
+ ])
25715
+ ),
25716
+ t4.returnStatement(resultVar)
25717
+ ])
25718
+ )
25719
+ );
25720
+ bodyStatements.push(
25721
+ t4.ifStatement(
25722
+ t4.logicalExpression(
25723
+ "&&",
25724
+ handlerVar,
25725
+ t4.binaryExpression(
25726
+ "===",
25727
+ t4.unaryExpression("typeof", t4.memberExpression(handlerVar, t4.identifier("handleEvent"))),
25728
+ t4.stringLiteral("function")
25729
+ )
25730
+ ),
25731
+ t4.blockStatement([
25732
+ t4.returnStatement(
25733
+ t4.callExpression(
25734
+ t4.memberExpression(
25735
+ t4.memberExpression(handlerVar, t4.identifier("handleEvent")),
25736
+ t4.identifier("call")
25737
+ ),
25738
+ [handlerVar, eventParam]
25739
+ )
25740
+ )
25741
+ ])
25384
25742
  )
25385
25743
  );
25386
25744
  const exportedHandler = t4.exportNamedDeclaration(
@@ -25409,11 +25767,13 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25409
25767
  ])
25410
25768
  )
25411
25769
  );
25770
+ return true;
25412
25771
  }
25413
25772
 
25414
25773
  // src/ir/codegen-runtime-imports.ts
25415
25774
  var RUNTIME_IMPORT_MODULES = /* @__PURE__ */ new Set([
25416
25775
  RUNTIME_MODULE,
25776
+ ...Object.values(RUNTIME_HELPER_MODULES),
25417
25777
  "@fictjs/runtime",
25418
25778
  "@fictjs/runtime/advanced",
25419
25779
  "fict",
@@ -25792,7 +26152,8 @@ function extractHIRStaticHtml(jsx, ctx, ops, parentPath = [], namespace = null)
25792
26152
  name: eventName.toLowerCase(),
25793
26153
  expr: attr.value ?? void 0,
25794
26154
  eventOptions: { capture, passive, once },
25795
- resumable: shouldBeResumable
26155
+ resumable: shouldBeResumable,
26156
+ resumableExplicit: isResumableEvent
25796
26157
  });
25797
26158
  continue;
25798
26159
  }
@@ -26365,7 +26726,7 @@ function buildOutputParams(fn, t4) {
26365
26726
  }
26366
26727
  return fn.params.map((p) => t4.identifier(deSSAVarName(p.name)));
26367
26728
  }
26368
- function lowerTrackedExpression(expr, ctx) {
26729
+ function lowerTrackedExpression(expr, ctx, valueUsed = true) {
26369
26730
  const regionOverride = ctx.inReturn && ctx.currentFnIsHook ? null : ctx.currentRegion ?? (ctx.trackedVars.size ? {
26370
26731
  id: -1,
26371
26732
  dependencies: new Set(ctx.trackedVars),
@@ -26373,7 +26734,7 @@ function lowerTrackedExpression(expr, ctx) {
26373
26734
  hasControlFlow: false,
26374
26735
  hasReactiveWrites: false
26375
26736
  } : null);
26376
- const lowered = lowerExpression2(expr, ctx);
26737
+ const lowered = lowerExpression2(expr, ctx, valueUsed);
26377
26738
  if (ctx.t.isAssignmentExpression(lowered)) {
26378
26739
  const right = applyRegionMetadataToExpression2(lowered.right, ctx, regionOverride ?? void 0);
26379
26740
  return ctx.t.assignmentExpression(lowered.operator, lowered.left, right);
@@ -26495,7 +26856,7 @@ function lowerInstruction(instr, ctx) {
26495
26856
  );
26496
26857
  }
26497
26858
  if (instr.kind === "Expression") {
26498
- return applyLoc(t4.expressionStatement(lowerTrackedExpression(instr.value, ctx)));
26859
+ return applyLoc(t4.expressionStatement(lowerTrackedExpression(instr.value, ctx, false)));
26499
26860
  }
26500
26861
  if (instr.kind === "Phi") {
26501
26862
  return null;
@@ -26659,7 +27020,7 @@ function collectLocalDeclaredNames(params, blocks, t4) {
26659
27020
  }
26660
27021
  return declared;
26661
27022
  }
26662
- function lowerExpression2(expr, ctx, isAssigned = false) {
27023
+ function lowerExpression2(expr, ctx, valueUsed = true) {
26663
27024
  const depth = (ctx.expressionDepth ?? 0) + 1;
26664
27025
  const maxDepth = ctx.maxExpressionDepth ?? 500;
26665
27026
  if (depth > maxDepth) {
@@ -26670,12 +27031,12 @@ function lowerExpression2(expr, ctx, isAssigned = false) {
26670
27031
  }
26671
27032
  ctx.expressionDepth = depth;
26672
27033
  try {
26673
- return setNodeLoc(lowerExpressionImpl(expr, ctx, isAssigned), expr.loc);
27034
+ return setNodeLoc(lowerExpressionImpl(expr, ctx, valueUsed), expr.loc);
26674
27035
  } finally {
26675
27036
  ctx.expressionDepth = depth - 1;
26676
27037
  }
26677
27038
  }
26678
- function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27039
+ function lowerExpressionImpl(expr, ctx, valueUsed = true) {
26679
27040
  const { t: t4 } = ctx;
26680
27041
  const mapParams = (params) => params.map((p) => t4.identifier(deSSAVarName(p.name)));
26681
27042
  const lowerArgsAsExpressions = (args) => args.map(
@@ -26716,6 +27077,152 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
26716
27077
  ctx.localDeclaredNames = prevLocalDeclared;
26717
27078
  return result;
26718
27079
  };
27080
+ const lowerTrackedWriteCall = (callee, nextValue) => {
27081
+ if (!valueUsed) {
27082
+ return t4.callExpression(t4.cloneNode(callee, true), [nextValue]);
27083
+ }
27084
+ const nextId = genTemp3(ctx, "next");
27085
+ const nextRef = t4.identifier(nextId.name);
27086
+ return t4.callExpression(
27087
+ t4.arrowFunctionExpression(
27088
+ [t4.cloneNode(nextId, true)],
27089
+ t4.sequenceExpression([
27090
+ t4.callExpression(t4.cloneNode(callee, true), [nextRef]),
27091
+ t4.identifier(nextId.name)
27092
+ ])
27093
+ ),
27094
+ [nextValue]
27095
+ );
27096
+ };
27097
+ const buildTrackedAssignmentNext = (operator, current, right) => {
27098
+ switch (operator) {
27099
+ case "=":
27100
+ return right;
27101
+ case "+=":
27102
+ return t4.binaryExpression("+", current, right);
27103
+ case "-=":
27104
+ return t4.binaryExpression("-", current, right);
27105
+ case "*=":
27106
+ return t4.binaryExpression("*", current, right);
27107
+ case "/=":
27108
+ return t4.binaryExpression("/", current, right);
27109
+ case "%=":
27110
+ return t4.binaryExpression("%", current, right);
27111
+ case "**=":
27112
+ return t4.binaryExpression("**", current, right);
27113
+ case "<<=":
27114
+ return t4.binaryExpression("<<", current, right);
27115
+ case ">>=":
27116
+ return t4.binaryExpression(">>", current, right);
27117
+ case ">>>=":
27118
+ return t4.binaryExpression(">>>", current, right);
27119
+ case "|=":
27120
+ return t4.binaryExpression("|", current, right);
27121
+ case "^=":
27122
+ return t4.binaryExpression("^", current, right);
27123
+ case "&=":
27124
+ return t4.binaryExpression("&", current, right);
27125
+ case "&&=":
27126
+ return t4.logicalExpression("&&", current, right);
27127
+ case "||=":
27128
+ return t4.logicalExpression("||", current, right);
27129
+ case "??=":
27130
+ return t4.logicalExpression("??", current, right);
27131
+ default:
27132
+ return right;
27133
+ }
27134
+ };
27135
+ const buildStaticSignalKeyTest = (keyRef, keys) => {
27136
+ if (keys.length === 0) return null;
27137
+ let test = null;
27138
+ for (const key of keys) {
27139
+ const literal = typeof key === "number" ? t4.numericLiteral(key) : t4.stringLiteral(String(key));
27140
+ const eq = t4.binaryExpression("===", t4.cloneNode(keyRef, true), literal);
27141
+ test = test ? t4.logicalExpression("||", test, eq) : eq;
27142
+ }
27143
+ return test;
27144
+ };
27145
+ const lowerComputedHookSignalAssignment = (objectName, keyExpr, signalKeys, operator, rightExpr) => {
27146
+ const keyTestKeys = signalKeys.filter(
27147
+ (key) => typeof key === "number" && Number.isFinite(key) || typeof key === "string"
27148
+ );
27149
+ if (keyTestKeys.length === 0) return null;
27150
+ const keyId = genTemp3(ctx, "key");
27151
+ const keyRef = t4.identifier(keyId.name);
27152
+ const memberForAccessor = t4.memberExpression(
27153
+ t4.identifier(objectName),
27154
+ t4.identifier(keyId.name),
27155
+ true
27156
+ );
27157
+ const current = t4.callExpression(t4.cloneNode(memberForAccessor, true), []);
27158
+ const right = lowerExpression2(rightExpr, ctx);
27159
+ const signalWrite = lowerTrackedWriteCall(
27160
+ memberForAccessor,
27161
+ buildTrackedAssignmentNext(operator, current, t4.cloneNode(right, true))
27162
+ );
27163
+ const fallback = t4.assignmentExpression(
27164
+ operator,
27165
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27166
+ right
27167
+ );
27168
+ const keyTest = buildStaticSignalKeyTest(keyRef, keyTestKeys);
27169
+ if (!keyTest) return null;
27170
+ return t4.callExpression(
27171
+ t4.arrowFunctionExpression(
27172
+ [t4.cloneNode(keyId, true)],
27173
+ t4.conditionalExpression(keyTest, signalWrite, fallback)
27174
+ ),
27175
+ [lowerExpression2(keyExpr, ctx)]
27176
+ );
27177
+ };
27178
+ const lowerComputedHookSignalUpdate = (objectName, keyExpr, signalKeys, operator, prefix) => {
27179
+ const keyTestKeys = signalKeys.filter(
27180
+ (key) => typeof key === "number" && Number.isFinite(key) || typeof key === "string"
27181
+ );
27182
+ if (keyTestKeys.length === 0) return null;
27183
+ const keyId = genTemp3(ctx, "key");
27184
+ const keyRef = t4.identifier(keyId.name);
27185
+ const signalUpdate = lowerTrackedUpdateCall(
27186
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27187
+ operator,
27188
+ prefix
27189
+ );
27190
+ const fallback = t4.updateExpression(
27191
+ operator,
27192
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27193
+ prefix
27194
+ );
27195
+ const keyTest = buildStaticSignalKeyTest(keyRef, keyTestKeys);
27196
+ if (!keyTest) return null;
27197
+ return t4.callExpression(
27198
+ t4.arrowFunctionExpression(
27199
+ [t4.cloneNode(keyId, true)],
27200
+ t4.conditionalExpression(keyTest, signalUpdate, fallback)
27201
+ ),
27202
+ [lowerExpression2(keyExpr, ctx)]
27203
+ );
27204
+ };
27205
+ const lowerTrackedUpdateCall = (callee, operator, prefix) => {
27206
+ const op = operator === "++" ? "+" : "-";
27207
+ const delta = t4.numericLiteral(1);
27208
+ const current = t4.callExpression(t4.cloneNode(callee, true), []);
27209
+ if (!valueUsed) {
27210
+ return t4.callExpression(t4.cloneNode(callee, true), [t4.binaryExpression(op, current, delta)]);
27211
+ }
27212
+ const prevId = genTemp3(ctx, "prev");
27213
+ const prevForSet = t4.identifier(prevId.name);
27214
+ const prevForResult = t4.identifier(prevId.name);
27215
+ return t4.callExpression(
27216
+ t4.arrowFunctionExpression(
27217
+ [t4.cloneNode(prevId, true)],
27218
+ t4.sequenceExpression([
27219
+ t4.callExpression(t4.cloneNode(callee, true), [t4.binaryExpression(op, prevForSet, delta)]),
27220
+ prefix ? t4.binaryExpression(op, prevForResult, t4.numericLiteral(1)) : prevForResult
27221
+ ])
27222
+ ),
27223
+ [current]
27224
+ );
27225
+ };
26719
27226
  const lowerBlocksToStatements = (blocks) => {
26720
27227
  const stmts = [];
26721
27228
  for (const block of blocks) {
@@ -26885,6 +27392,24 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
26885
27392
  expr.arguments,
26886
27393
  (arg) => arg.kind === "ArrowFunction" || arg.kind === "FunctionExpression" ? withNonReactiveScope(ctx, () => lowerExpression2(arg, ctx)) : lowerExpression2(arg, ctx)
26887
27394
  );
27395
+ const includeDevtools = ctx.options?.dev !== false;
27396
+ if (includeDevtools && expr.loc) {
27397
+ const source = `${ctx.options?.filename ?? ""}:${expr.loc.start.line}:${expr.loc.start.column}`;
27398
+ const sourceProp = t4.objectProperty(
27399
+ t4.identifier("devToolsSource"),
27400
+ t4.stringLiteral(source)
27401
+ );
27402
+ if (args.length === 1) {
27403
+ args.push(t4.objectExpression([sourceProp]));
27404
+ } else if (args.length > 1 && t4.isObjectExpression(args[1])) {
27405
+ const hasSourceProp = args[1].properties.some(
27406
+ (prop) => t4.isObjectProperty(prop) && t4.isIdentifier(prop.key) && prop.key.name === "devToolsSource"
27407
+ );
27408
+ if (!hasSourceProp) {
27409
+ args[1].properties.push(sourceProp);
27410
+ }
27411
+ }
27412
+ }
26888
27413
  if (ctx.inModule) {
26889
27414
  ctx.helpersUsed.add("effect");
26890
27415
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.effect), args);
@@ -27192,59 +27717,42 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27192
27717
  expr.left.computed,
27193
27718
  expr.left.optional
27194
27719
  );
27195
- const current = t4.callExpression(member, []);
27720
+ const current = t4.callExpression(t4.cloneNode(member, true), []);
27196
27721
  const right = lowerExpression2(expr.right, ctx);
27197
- let next;
27198
- switch (expr.operator) {
27199
- case "=":
27200
- next = right;
27201
- break;
27202
- case "+=":
27203
- next = t4.binaryExpression("+", current, right);
27204
- break;
27205
- case "-=":
27206
- next = t4.binaryExpression("-", current, right);
27207
- break;
27208
- case "*=":
27209
- next = t4.binaryExpression("*", current, right);
27210
- break;
27211
- case "/=":
27212
- next = t4.binaryExpression("/", current, right);
27213
- break;
27214
- default:
27215
- next = right;
27722
+ const next = buildTrackedAssignmentNext(expr.operator, current, right);
27723
+ return lowerTrackedWriteCall(member, next);
27724
+ }
27725
+ if (expr.left.computed) {
27726
+ const signalKeys = [];
27727
+ if (info?.objectProps) {
27728
+ for (const [key, accessorKind] of info.objectProps.entries()) {
27729
+ if (accessorKind === "signal") signalKeys.push(key);
27730
+ }
27216
27731
  }
27217
- return t4.callExpression(member, [next]);
27732
+ if (info?.arrayProps) {
27733
+ for (const [key, accessorKind] of info.arrayProps.entries()) {
27734
+ if (accessorKind === "signal") signalKeys.push(key);
27735
+ }
27736
+ }
27737
+ const lowered = lowerComputedHookSignalAssignment(
27738
+ deSSAVarName(expr.left.object.name),
27739
+ expr.left.property,
27740
+ signalKeys,
27741
+ expr.operator,
27742
+ expr.right
27743
+ );
27744
+ if (lowered) return lowered;
27218
27745
  }
27219
27746
  }
27220
27747
  }
27221
27748
  if (expr.left.kind === "Identifier") {
27222
27749
  const baseName2 = deSSAVarName(expr.left.name);
27223
27750
  if (ctx.trackedVars.has(baseName2)) {
27224
- const id = t4.identifier(baseName2);
27751
+ const callee = t4.identifier(baseName2);
27225
27752
  const current = t4.callExpression(t4.identifier(baseName2), []);
27226
27753
  const right = lowerExpression2(expr.right, ctx);
27227
- let next;
27228
- switch (expr.operator) {
27229
- case "=":
27230
- next = right;
27231
- break;
27232
- case "+=":
27233
- next = t4.binaryExpression("+", current, right);
27234
- break;
27235
- case "-=":
27236
- next = t4.binaryExpression("-", current, right);
27237
- break;
27238
- case "*=":
27239
- next = t4.binaryExpression("*", current, right);
27240
- break;
27241
- case "/=":
27242
- next = t4.binaryExpression("/", current, right);
27243
- break;
27244
- default:
27245
- next = right;
27246
- }
27247
- return t4.callExpression(id, [next]);
27754
+ const next = buildTrackedAssignmentNext(expr.operator, current, right);
27755
+ return lowerTrackedWriteCall(callee, next);
27248
27756
  }
27249
27757
  }
27250
27758
  return t4.assignmentExpression(
@@ -27276,21 +27784,35 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27276
27784
  expr.argument.computed,
27277
27785
  expr.argument.optional
27278
27786
  );
27279
- const current = t4.callExpression(member, []);
27280
- const delta = t4.numericLiteral(1);
27281
- const next = expr.operator === "++" ? t4.binaryExpression("+", current, delta) : t4.binaryExpression("-", current, delta);
27282
- return t4.callExpression(member, [next]);
27787
+ return lowerTrackedUpdateCall(member, expr.operator, expr.prefix);
27788
+ }
27789
+ if (expr.argument.computed) {
27790
+ const signalKeys = [];
27791
+ if (info?.objectProps) {
27792
+ for (const [key, accessorKind] of info.objectProps.entries()) {
27793
+ if (accessorKind === "signal") signalKeys.push(key);
27794
+ }
27795
+ }
27796
+ if (info?.arrayProps) {
27797
+ for (const [key, accessorKind] of info.arrayProps.entries()) {
27798
+ if (accessorKind === "signal") signalKeys.push(key);
27799
+ }
27800
+ }
27801
+ const lowered = lowerComputedHookSignalUpdate(
27802
+ deSSAVarName(expr.argument.object.name),
27803
+ expr.argument.property,
27804
+ signalKeys,
27805
+ expr.operator,
27806
+ expr.prefix
27807
+ );
27808
+ if (lowered) return lowered;
27283
27809
  }
27284
27810
  }
27285
27811
  }
27286
27812
  if (expr.argument.kind === "Identifier") {
27287
27813
  const baseName2 = deSSAVarName(expr.argument.name);
27288
27814
  if (ctx.trackedVars.has(baseName2)) {
27289
- const id = t4.identifier(baseName2);
27290
- const current = t4.callExpression(t4.identifier(baseName2), []);
27291
- const delta = t4.numericLiteral(1);
27292
- const next = expr.operator === "++" ? t4.binaryExpression("+", current, delta) : t4.binaryExpression("-", current, delta);
27293
- return t4.callExpression(id, [next]);
27815
+ return lowerTrackedUpdateCall(t4.identifier(baseName2), expr.operator, expr.prefix);
27294
27816
  }
27295
27817
  }
27296
27818
  return t4.updateExpression(
@@ -27312,7 +27834,11 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27312
27834
  case "NewExpression":
27313
27835
  return t4.newExpression(lowerExpression2(expr.callee, ctx), lowerCallArguments(expr.arguments));
27314
27836
  case "SequenceExpression":
27315
- return t4.sequenceExpression(expr.expressions.map((e) => lowerExpression2(e, ctx)));
27837
+ return t4.sequenceExpression(
27838
+ expr.expressions.map(
27839
+ (e, index) => lowerExpression2(e, ctx, index === expr.expressions.length - 1 ? valueUsed : false)
27840
+ )
27841
+ );
27316
27842
  case "YieldExpression":
27317
27843
  return t4.yieldExpression(
27318
27844
  expr.argument ? lowerExpression2(expr.argument, ctx) : null,
@@ -27770,16 +28296,17 @@ function lowerIntrinsicElement(jsx, ctx) {
27770
28296
  const hasEventOptions = binding.eventOptions && (binding.eventOptions.capture || binding.eventOptions.passive || binding.eventOptions.once);
27771
28297
  const isDelegated = DelegatedEvents.has(eventName) && !hasEventOptions;
27772
28298
  if (binding.resumable && !hasEventOptions) {
27773
- emitResumableEventBinding(
28299
+ const emitted = emitResumableEventBinding(
27774
28300
  targetId,
27775
28301
  eventName,
27776
28302
  binding.expr,
27777
28303
  statements,
27778
28304
  ctx,
27779
28305
  containingRegion,
27780
- createResumableEventBindingOps()
28306
+ createResumableEventBindingOps(),
28307
+ { explicit: binding.resumableExplicit === true }
27781
28308
  );
27782
- continue;
28309
+ if (emitted) continue;
27783
28310
  }
27784
28311
  const hirDataBinding = isDelegated && binding.expr ? extractDelegatedEventDataFromHIR(binding.expr, ctx) : null;
27785
28312
  if (hirDataBinding) {
@@ -27821,34 +28348,24 @@ function lowerIntrinsicElement(jsx, ctx) {
27821
28348
  const isFn = t4.isArrowFunctionExpression(valueExpr) || t4.isFunctionExpression(valueExpr);
27822
28349
  const ensureHandlerParam = (fn) => {
27823
28350
  if (t4.isArrowFunctionExpression(fn)) {
27824
- if (fn.params.length > 0) return fn;
27825
- return t4.arrowFunctionExpression([eventParam], fn.body, fn.async);
28351
+ return fn;
27826
28352
  }
27827
28353
  if (t4.isFunctionExpression(fn)) {
27828
- if (fn.params.length > 0) return fn;
27829
- return t4.functionExpression(fn.id, [eventParam], fn.body, fn.generator, fn.async);
28354
+ return fn;
27830
28355
  }
27831
28356
  if (t4.isIdentifier(fn) || t4.isMemberExpression(fn)) {
27832
28357
  return fn;
27833
28358
  }
27834
- if (t4.isCallExpression(fn) && fn.arguments.length === 0 && (t4.isIdentifier(fn.callee) || t4.isMemberExpression(fn.callee))) {
27835
- return fn.callee;
27836
- }
27837
28359
  return t4.functionExpression(
27838
28360
  null,
27839
- [eventParam],
27840
- t4.blockStatement([
27841
- t4.returnStatement(
27842
- t4.callExpression(
27843
- t4.memberExpression(fn, t4.identifier("call")),
27844
- [t4.thisExpression(), eventParam]
27845
- )
27846
- )
27847
- ])
28361
+ [],
28362
+ t4.blockStatement([t4.returnStatement(fn)])
27848
28363
  );
27849
28364
  };
27850
28365
  const handlerExpr = !isFn && shouldWrapHandler ? t4.arrowFunctionExpression([], valueExpr) : ensureHandlerParam(valueExpr);
27851
- let dataBinding = isDelegated && !shouldWrapHandler ? extractDelegatedEventData(valueExpr, t4) : null;
28366
+ let dataBinding = isDelegated && !shouldWrapHandler ? extractDelegatedEventData(valueExpr, t4, {
28367
+ isKnownHandlerIdentifier: (name) => ctx.functionVars?.has(deSSAVarName(name)) ?? false
28368
+ }) : null;
27852
28369
  if (dataBinding && t4.isIdentifier(dataBinding.handler)) {
27853
28370
  const handlerName = dataBinding.handler.name;
27854
28371
  if (ctx.signalVars?.has(handlerName) || ctx.memoVars?.has(handlerName) || ctx.aliasVars?.has(handlerName) || ctx.storeVars?.has(handlerName) || ctx.trackedVars.has(handlerName)) {
@@ -27860,8 +28377,6 @@ function lowerIntrinsicElement(jsx, ctx) {
27860
28377
  let handlerName = null;
27861
28378
  if (t4.isIdentifier(valueExpr)) {
27862
28379
  handlerName = valueExpr.name;
27863
- } else if (t4.isCallExpression(valueExpr) && valueExpr.arguments.length === 0 && t4.isIdentifier(valueExpr.callee)) {
27864
- handlerName = valueExpr.callee.name;
27865
28380
  }
27866
28381
  const handlerForCall = handlerName ? t4.identifier(handlerName) : t4.cloneNode(valueExpr, true);
27867
28382
  const finalHandler = !isFn && shouldWrapHandler ? t4.functionExpression(
@@ -27877,9 +28392,6 @@ function lowerIntrinsicElement(jsx, ctx) {
27877
28392
  ])
27878
28393
  ) : handlerExpr;
27879
28394
  const normalizeHandler = (expr) => {
27880
- if (t4.isCallExpression(expr) && (t4.isIdentifier(expr.callee) || t4.isMemberExpression(expr.callee))) {
27881
- return expr.callee;
27882
- }
27883
28395
  return expr;
27884
28396
  };
27885
28397
  const normalizedDataHandler = dataBinding !== null ? normalizeHandler(
@@ -28553,10 +29065,13 @@ function transformControlFlowReturns(statements, ctx) {
28553
29065
  }
28554
29066
  return false;
28555
29067
  };
28556
- function hasNodeMatch(nodes, predicate) {
29068
+ function hasNodeMatch(nodes, predicate, options) {
28557
29069
  let found = false;
28558
- const visit = (node) => {
29070
+ const visit = (node, isRoot = false) => {
28559
29071
  if (!node || found) return;
29072
+ if (!isRoot && options?.skipNestedFunctions && (t4.isFunctionExpression(node) || t4.isArrowFunctionExpression(node) || t4.isFunctionDeclaration(node) || t4.isObjectMethod(node) || t4.isClassMethod(node))) {
29073
+ return;
29074
+ }
28560
29075
  if (predicate(node)) {
28561
29076
  found = true;
28562
29077
  return;
@@ -28579,17 +29094,134 @@ function transformControlFlowReturns(statements, ctx) {
28579
29094
  }
28580
29095
  };
28581
29096
  for (const node of nodes) {
28582
- visit(node);
29097
+ visit(node, true);
28583
29098
  if (found) return true;
28584
29099
  }
28585
29100
  return found;
28586
29101
  }
28587
29102
  const containsReturnStatement = (nodes) => hasNodeMatch(nodes, (node) => t4.isReturnStatement(node));
28588
- const containsReactiveAccessorRead = (nodes) => hasNodeMatch(nodes, (node) => {
28589
- if (!t4.isCallExpression(node) && !t4.isOptionalCallExpression(node)) return false;
28590
- const callee = node.callee;
28591
- return t4.isIdentifier(callee) && reactiveAccessorNames.has(callee.name);
28592
- });
29103
+ const getMemberRootIdentifier = (expr) => {
29104
+ let current = expr.object;
29105
+ while (t4.isMemberExpression(current) || t4.isOptionalMemberExpression(current)) {
29106
+ current = current.object;
29107
+ }
29108
+ return t4.isIdentifier(current) ? current : null;
29109
+ };
29110
+ const containsReactiveAccessorRead = (nodes, options) => hasNodeMatch(
29111
+ nodes,
29112
+ (node) => {
29113
+ if (t4.isCallExpression(node) || t4.isOptionalCallExpression(node)) {
29114
+ const callee = node.callee;
29115
+ return t4.isIdentifier(callee) && reactiveAccessorNames.has(callee.name);
29116
+ }
29117
+ if (t4.isMemberExpression(node) || t4.isOptionalMemberExpression(node)) {
29118
+ const root = getMemberRootIdentifier(node);
29119
+ return !!(root && reactiveAccessorNames.has(root.name));
29120
+ }
29121
+ return false;
29122
+ },
29123
+ options
29124
+ );
29125
+ const containsReactiveControlFlowRead = (nodes) => hasNodeMatch(
29126
+ nodes,
29127
+ (node) => {
29128
+ if (t4.isIfStatement(node)) {
29129
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29130
+ }
29131
+ if (t4.isSwitchStatement(node)) {
29132
+ return containsReactiveAccessorRead([node.discriminant], { skipNestedFunctions: true });
29133
+ }
29134
+ if (t4.isConditionalExpression(node)) {
29135
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29136
+ }
29137
+ if (t4.isWhileStatement(node) || t4.isDoWhileStatement(node)) {
29138
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29139
+ }
29140
+ if (t4.isForStatement(node)) {
29141
+ const parts = [];
29142
+ if (node.init) parts.push(node.init);
29143
+ if (node.test) parts.push(node.test);
29144
+ if (node.update) parts.push(node.update);
29145
+ return parts.length > 0 && containsReactiveAccessorRead(parts, { skipNestedFunctions: true });
29146
+ }
29147
+ if (t4.isForOfStatement(node) || t4.isForInStatement(node)) {
29148
+ return containsReactiveAccessorRead([node.right], { skipNestedFunctions: true });
29149
+ }
29150
+ return false;
29151
+ },
29152
+ { skipNestedFunctions: true }
29153
+ );
29154
+ const hasRiskyBranchControlFlow = (stmts) => {
29155
+ if (stmts.length === 0) return false;
29156
+ return containsReactiveControlFlowRead(stmts);
29157
+ };
29158
+ const isJSXLikeNode = (node) => !!node && (t4.isJSXElement(node) || t4.isJSXFragment(node));
29159
+ const isStoreSourceExpression = (expr) => {
29160
+ if (t4.isIdentifier(expr)) {
29161
+ return !!ctx.storeVars?.has(expr.name);
29162
+ }
29163
+ if (t4.isMemberExpression(expr) || t4.isOptionalMemberExpression(expr)) {
29164
+ const root = getMemberRootIdentifier(expr);
29165
+ return !!(root && ctx.storeVars?.has(root.name));
29166
+ }
29167
+ return false;
29168
+ };
29169
+ const hasRiskyStoreDestructureRead = (stmt) => {
29170
+ if (t4.isVariableDeclaration(stmt)) {
29171
+ for (const decl of stmt.declarations) {
29172
+ if (!decl.init) continue;
29173
+ const hasPattern = t4.isObjectPattern(decl.id) || t4.isArrayPattern(decl.id);
29174
+ if (!hasPattern) continue;
29175
+ if (isStoreSourceExpression(decl.init)) {
29176
+ return true;
29177
+ }
29178
+ }
29179
+ return false;
29180
+ }
29181
+ if (t4.isExpressionStatement(stmt) && t4.isAssignmentExpression(stmt.expression)) {
29182
+ const assignment = stmt.expression;
29183
+ const isPatternLhs = t4.isObjectPattern(assignment.left) || t4.isArrayPattern(assignment.left);
29184
+ if (!isPatternLhs) return false;
29185
+ return isStoreSourceExpression(assignment.right);
29186
+ }
29187
+ return false;
29188
+ };
29189
+ const hasRiskyBranchPreludeReads = (stmts) => {
29190
+ for (const stmt of stmts) {
29191
+ if (hasRiskyStoreDestructureRead(stmt)) {
29192
+ return true;
29193
+ }
29194
+ if (t4.isReturnStatement(stmt)) {
29195
+ const arg = stmt.argument;
29196
+ if (!arg || isJSXLikeNode(arg)) continue;
29197
+ if (containsReactiveAccessorRead([arg], { skipNestedFunctions: true })) {
29198
+ return true;
29199
+ }
29200
+ continue;
29201
+ }
29202
+ if (containsReactiveAccessorRead([stmt], { skipNestedFunctions: true })) {
29203
+ return true;
29204
+ }
29205
+ }
29206
+ return false;
29207
+ };
29208
+ const hasRiskyImmediateInvocationReads = (stmts) => hasNodeMatch(
29209
+ stmts,
29210
+ (node) => {
29211
+ if (!t4.isCallExpression(node) && !t4.isOptionalCallExpression(node)) return false;
29212
+ const callee = node.callee;
29213
+ if (!t4.isFunctionExpression(callee) && !t4.isArrowFunctionExpression(callee)) {
29214
+ return false;
29215
+ }
29216
+ const bodyNodes = t4.isBlockStatement(callee.body) ? callee.body.body : [callee.body];
29217
+ return containsReactiveAccessorRead(bodyNodes, { skipNestedFunctions: true });
29218
+ },
29219
+ { skipNestedFunctions: true }
29220
+ );
29221
+ const needsTrackedBranchReads = (stmts) => {
29222
+ if (stmts.length === 0) return false;
29223
+ return hasRiskyBranchControlFlow(stmts) || hasRiskyBranchPreludeReads(stmts) || hasRiskyImmediateInvocationReads(stmts);
29224
+ };
28593
29225
  const emitControlFlowFallbackWarning = (node, kind) => {
28594
29226
  const onWarn = ctx.options?.onWarn;
28595
29227
  if (!onWarn) return;
@@ -28658,7 +29290,7 @@ function transformControlFlowReturns(statements, ctx) {
28658
29290
  }
28659
29291
  return found;
28660
29292
  }
28661
- function buildConditionalBindingExpr(testExpr, trueFn, falseFn) {
29293
+ function buildConditionalBindingExpr(testExpr, trueFn, falseFn, options) {
28662
29294
  ctx.helpersUsed.add("conditional");
28663
29295
  ctx.helpersUsed.add("createElement");
28664
29296
  ctx.helpersUsed.add("onDestroy");
@@ -28669,6 +29301,16 @@ function transformControlFlowReturns(statements, ctx) {
28669
29301
  t4.identifier(RUNTIME_ALIASES.createElement),
28670
29302
  falseFn
28671
29303
  ];
29304
+ if (options?.trackBranchReads) {
29305
+ const undefinedExpr = t4.unaryExpression("void", t4.numericLiteral(0));
29306
+ args.push(
29307
+ undefinedExpr,
29308
+ t4.cloneNode(undefinedExpr),
29309
+ t4.objectExpression([
29310
+ t4.objectProperty(t4.identifier("trackBranchReads"), t4.booleanLiteral(true))
29311
+ ])
29312
+ );
29313
+ }
28672
29314
  const bindingCall = t4.callExpression(t4.identifier(RUNTIME_ALIASES.conditional), args);
28673
29315
  return t4.callExpression(
28674
29316
  t4.arrowFunctionExpression(
@@ -28702,7 +29344,13 @@ function transformControlFlowReturns(statements, ctx) {
28702
29344
  const trueFn = buildBranchFunction(consequentStmts);
28703
29345
  const falseFn = alternateStmts ? buildBranchFunction(alternateStmts) : null;
28704
29346
  if (!trueFn || !falseFn) return null;
28705
- return buildConditionalBindingExpr(ifStmt.test, trueFn, falseFn);
29347
+ const shouldTrackBranchReads = needsTrackedBranchReads(consequentStmts) || (alternateStmts ? needsTrackedBranchReads(alternateStmts) : false);
29348
+ return buildConditionalBindingExpr(
29349
+ ifStmt.test,
29350
+ trueFn,
29351
+ falseFn,
29352
+ shouldTrackBranchReads ? { trackBranchReads: true } : void 0
29353
+ );
28706
29354
  }
28707
29355
  function isSupportedSwitchDiscriminant(_expr) {
28708
29356
  return true;
@@ -28795,10 +29443,13 @@ function transformControlFlowReturns(statements, ctx) {
28795
29443
  ),
28796
29444
  []
28797
29445
  );
29446
+ let currentExprNeedsTrackedBranchReads = needsTrackedBranchReads(fallbackStatements);
28798
29447
  for (let i = branches.length - 1; i >= 0; i--) {
28799
29448
  const branch = branches[i];
28800
29449
  const trueFn = buildBranchFunction(branch.statements, { disallowRenderHooks: true });
28801
29450
  if (!trueFn) return null;
29451
+ const trueBranchNeedsTrackedBranchReads = needsTrackedBranchReads(branch.statements);
29452
+ const trackBranchReads = trueBranchNeedsTrackedBranchReads || currentExprNeedsTrackedBranchReads;
28802
29453
  const falseFn = t4.arrowFunctionExpression(
28803
29454
  [],
28804
29455
  t4.blockStatement([t4.returnStatement(currentExpr)])
@@ -28815,7 +29466,13 @@ function transformControlFlowReturns(statements, ctx) {
28815
29466
  (acc, expr) => t4.logicalExpression("||", acc, expr),
28816
29467
  comparisons[0]
28817
29468
  );
28818
- currentExpr = buildConditionalBindingExpr(testExpr, trueFn, falseFn);
29469
+ currentExpr = buildConditionalBindingExpr(
29470
+ testExpr,
29471
+ trueFn,
29472
+ falseFn,
29473
+ trackBranchReads ? { trackBranchReads: true } : void 0
29474
+ );
29475
+ currentExprNeedsTrackedBranchReads = trackBranchReads;
28819
29476
  }
28820
29477
  return t4.callExpression(
28821
29478
  t4.arrowFunctionExpression(
@@ -30185,11 +30842,27 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30185
30842
  const constArrays = /* @__PURE__ */ new Map();
30186
30843
  const cseMap = /* @__PURE__ */ new Map();
30187
30844
  const instructions = [];
30845
+ const deleteByBase = (map, name) => {
30846
+ const base = getSSABaseName(name);
30847
+ for (const key of Array.from(map.keys())) {
30848
+ if (getSSABaseName(key) === base) {
30849
+ map.delete(key);
30850
+ }
30851
+ }
30852
+ };
30188
30853
  const invalidateCSE = (name) => {
30854
+ const base = getSSABaseName(name);
30189
30855
  const toDelete = [];
30190
30856
  for (const [hash, entry] of cseMap.entries()) {
30191
- if (entry.name === name || entry.deps.has(name)) {
30857
+ if (getSSABaseName(entry.name) === base) {
30192
30858
  toDelete.push(hash);
30859
+ continue;
30860
+ }
30861
+ for (const dep of entry.deps) {
30862
+ if (getSSABaseName(dep) === base) {
30863
+ toDelete.push(hash);
30864
+ break;
30865
+ }
30193
30866
  }
30194
30867
  }
30195
30868
  toDelete.forEach((hash) => cseMap.delete(hash));
@@ -30199,22 +30872,22 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30199
30872
  const target = instr.target.name;
30200
30873
  const declKind = instr.declarationKind;
30201
30874
  invalidateCSE(target);
30202
- constants.delete(target);
30203
- constObjects.delete(target);
30204
- constArrays.delete(target);
30875
+ deleteByBase(constants, target);
30876
+ deleteByBase(constObjects, target);
30877
+ deleteByBase(constArrays, target);
30205
30878
  const sideWrites = collectWriteTargets(instr.value);
30206
30879
  for (const name of sideWrites) {
30207
30880
  if (name !== target) {
30208
- constants.delete(name);
30881
+ deleteByBase(constants, name);
30209
30882
  invalidateCSE(name);
30210
30883
  }
30211
- constObjects.delete(name);
30212
- constArrays.delete(name);
30884
+ deleteByBase(constObjects, name);
30885
+ deleteByBase(constArrays, name);
30213
30886
  }
30214
30887
  const memberCalls = collectMemberCallTargets(instr.value);
30215
30888
  for (const name of memberCalls) {
30216
- constObjects.delete(name);
30217
- constArrays.delete(name);
30889
+ deleteByBase(constObjects, name);
30890
+ deleteByBase(constArrays, name);
30218
30891
  }
30219
30892
  const dependsOnReactiveValue = expressionDependsOnReactive(instr.value, reactive);
30220
30893
  let value = dependsOnReactiveValue ? instr.value : foldExpressionWithConstants(instr.value, constants, options, constObjects, constArrays);
@@ -30254,15 +30927,15 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30254
30927
  if (instr.kind === "Expression") {
30255
30928
  const writes = collectWriteTargets(instr.value);
30256
30929
  for (const name of writes) {
30257
- constants.delete(name);
30930
+ deleteByBase(constants, name);
30258
30931
  invalidateCSE(name);
30259
- constObjects.delete(name);
30260
- constArrays.delete(name);
30932
+ deleteByBase(constObjects, name);
30933
+ deleteByBase(constArrays, name);
30261
30934
  }
30262
30935
  const memberCalls = collectMemberCallTargets(instr.value);
30263
30936
  for (const name of memberCalls) {
30264
- constObjects.delete(name);
30265
- constArrays.delete(name);
30937
+ deleteByBase(constObjects, name);
30938
+ deleteByBase(constArrays, name);
30266
30939
  }
30267
30940
  const dependsOnReactiveValue = expressionDependsOnReactive(instr.value, reactive);
30268
30941
  const value = dependsOnReactiveValue ? instr.value : foldExpressionWithConstants(instr.value, constants, options, constObjects, constArrays);
@@ -30912,6 +31585,8 @@ function collectWriteTargets(expr) {
30912
31585
  } else if (left.kind === "MemberExpression" || left.kind === "OptionalMemberExpression") {
30913
31586
  const base = getMemberBaseIdentifier(left);
30914
31587
  if (base) writes.add(base.name);
31588
+ visit(left.object);
31589
+ if (left.computed) visit(left.property);
30915
31590
  } else {
30916
31591
  visit(left);
30917
31592
  }
@@ -30928,8 +31603,10 @@ function collectWriteTargets(expr) {
30928
31603
  const base = getMemberBaseIdentifier(arg);
30929
31604
  if (base) {
30930
31605
  writes.add(base.name);
30931
- return;
30932
31606
  }
31607
+ visit(arg.object);
31608
+ if (arg.computed) visit(arg.property);
31609
+ return;
30933
31610
  }
30934
31611
  visit(arg);
30935
31612
  return;
@@ -31235,6 +31912,16 @@ function propagateConstants(fn, options) {
31235
31912
  }
31236
31913
  function computeConstantMap(fn) {
31237
31914
  const constants = /* @__PURE__ */ new Map();
31915
+ const invalidateWrittenName = (writtenName) => {
31916
+ const writtenBase = getSSABaseName(writtenName);
31917
+ let removed = false;
31918
+ for (const constantName of Array.from(constants.keys())) {
31919
+ if (constantName === writtenName || getSSABaseName(constantName) === writtenBase) {
31920
+ removed = constants.delete(constantName) || removed;
31921
+ }
31922
+ }
31923
+ return removed;
31924
+ };
31238
31925
  let changed = true;
31239
31926
  let iterations = 0;
31240
31927
  const maxIterations = 10;
@@ -31243,6 +31930,14 @@ function computeConstantMap(fn) {
31243
31930
  changed = false;
31244
31931
  for (const block of fn.blocks) {
31245
31932
  for (const instr of block.instructions) {
31933
+ if (instr.kind === "Assign" || instr.kind === "Expression") {
31934
+ const writes = collectWriteTargets(instr.value);
31935
+ for (const writtenName of writes) {
31936
+ if (invalidateWrittenName(writtenName)) {
31937
+ changed = true;
31938
+ }
31939
+ }
31940
+ }
31246
31941
  if (instr.kind === "Assign") {
31247
31942
  const value = evaluateConstant(instr.value, constants);
31248
31943
  if (value !== UNKNOWN_CONST) {
@@ -33164,8 +33859,52 @@ function shouldSuppressWarning(suppressions, code, line) {
33164
33859
  });
33165
33860
  }
33166
33861
  var DEFAULT_ERROR_WARNING_CODES = /* @__PURE__ */ new Set(["FICT-R004"]);
33862
+ var STRICT_REACTIVITY_WARNING_CODES = /* @__PURE__ */ new Set(["FICT-R003", "FICT-R006"]);
33863
+ var STRICT_GUARANTEE_WARNING_CODES = /* @__PURE__ */ new Set([
33864
+ "FICT-P001",
33865
+ "FICT-P002",
33866
+ "FICT-P003",
33867
+ "FICT-P004",
33868
+ "FICT-P005",
33869
+ "FICT-J003",
33870
+ "FICT-S002",
33871
+ "FICT-R001",
33872
+ "FICT-R002",
33873
+ "FICT-R003",
33874
+ "FICT-R006"
33875
+ ]);
33876
+ function readBooleanEnv(name) {
33877
+ const raw = process.env[name];
33878
+ if (!raw) return void 0;
33879
+ const normalized = raw.trim().toLowerCase();
33880
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
33881
+ return true;
33882
+ }
33883
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
33884
+ return false;
33885
+ }
33886
+ return void 0;
33887
+ }
33888
+ function validateStrictGuaranteeConfig(options, suppressions) {
33889
+ if (!options.strictGuarantee) return;
33890
+ if (suppressions.length > 0) {
33891
+ throw new Error(
33892
+ "strictGuarantee does not allow fict-ignore suppression comments. Remove suppressions to keep fail-closed guarantees."
33893
+ );
33894
+ }
33895
+ if (!options.warningLevels) return;
33896
+ for (const [code, level] of Object.entries(options.warningLevels)) {
33897
+ if (!STRICT_GUARANTEE_WARNING_CODES.has(code)) continue;
33898
+ if (level === "error") continue;
33899
+ throw new Error(
33900
+ `strictGuarantee does not allow downgrading ${code} to "${level}". Remove this warningLevels override.`
33901
+ );
33902
+ }
33903
+ }
33167
33904
  function hasErrorEscalation(options) {
33168
33905
  if (DEFAULT_ERROR_WARNING_CODES.size > 0) return true;
33906
+ if (options.strictGuarantee) return true;
33907
+ if (options.strictReactivity) return true;
33169
33908
  if (options.warningsAsErrors === true) return true;
33170
33909
  if (Array.isArray(options.warningsAsErrors) && options.warningsAsErrors.length > 0) return true;
33171
33910
  if (options.warningLevels) {
@@ -33174,8 +33913,10 @@ function hasErrorEscalation(options) {
33174
33913
  return false;
33175
33914
  }
33176
33915
  function resolveWarningLevel(code, options) {
33916
+ if (options.strictGuarantee && STRICT_GUARANTEE_WARNING_CODES.has(code)) return "error";
33177
33917
  const override = options.warningLevels?.[code];
33178
33918
  if (override) return override;
33919
+ if (options.strictReactivity && STRICT_REACTIVITY_WARNING_CODES.has(code)) return "error";
33179
33920
  if (options.warningsAsErrors === true) return "error";
33180
33921
  if (Array.isArray(options.warningsAsErrors) && options.warningsAsErrors.includes(code)) {
33181
33922
  return "error";
@@ -33189,6 +33930,7 @@ function formatWarningAsError(warning) {
33189
33930
  at ${location}`;
33190
33931
  }
33191
33932
  function createWarningDispatcher(onWarn, suppressions, options, dev) {
33933
+ validateStrictGuaranteeConfig(options, suppressions);
33192
33934
  const hasEscalation = hasErrorEscalation(options);
33193
33935
  if (!dev && !hasEscalation) return () => {
33194
33936
  };
@@ -33294,7 +34036,7 @@ function isDynamicPropertyAccess(node, t4) {
33294
34036
  if (!node.computed) return false;
33295
34037
  return !(t4.isStringLiteral(node.property) || t4.isNumericLiteral(node.property));
33296
34038
  }
33297
- function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, reactiveBindingIds, effectMacroNames, warn, fileName, t4) {
34039
+ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, reactiveBindingIds, stateMacroNames, memoMacroNames, effectMacroNames, warn, fileName, t4) {
33298
34040
  const hasTrackedBinding = (path2, name, tracked) => {
33299
34041
  const binding = path2.scope.getBinding(name);
33300
34042
  return !!(binding && tracked.has(binding.identifier));
@@ -33309,6 +34051,107 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33309
34051
  if (!root) return false;
33310
34052
  return hasTrackedBinding(path2, root.name, reactiveBindingIds);
33311
34053
  };
34054
+ const NON_ESCAPING_CALLBACK_METHODS = /* @__PURE__ */ new Set([
34055
+ "map",
34056
+ "forEach",
34057
+ "filter",
34058
+ "some",
34059
+ "every",
34060
+ "find",
34061
+ "findIndex",
34062
+ "findLast",
34063
+ "findLastIndex",
34064
+ "flatMap",
34065
+ "reduce",
34066
+ "reduceRight",
34067
+ "sort",
34068
+ "toSorted",
34069
+ "then",
34070
+ "catch",
34071
+ "finally"
34072
+ ]);
34073
+ const capturedClosureByBinding = /* @__PURE__ */ new Map();
34074
+ const shouldIgnoreIdentifierReference = (idPath) => {
34075
+ if (idPath.parentPath.isMemberExpression({ property: idPath.node }) && !idPath.parent.computed) {
34076
+ return true;
34077
+ }
34078
+ if (idPath.parentPath.isObjectProperty({ key: idPath.node }) && !idPath.parent.computed && !idPath.parent.shorthand) {
34079
+ return true;
34080
+ }
34081
+ return false;
34082
+ };
34083
+ const collectCapturedReactiveNames = (fnPath) => {
34084
+ const captured = /* @__PURE__ */ new Set();
34085
+ fnPath.traverse({
34086
+ Function(inner) {
34087
+ if (inner === fnPath) return;
34088
+ inner.skip();
34089
+ },
34090
+ Identifier(idPath) {
34091
+ if (shouldIgnoreIdentifierReference(idPath)) return;
34092
+ const name = idPath.node.name;
34093
+ const binding = idPath.scope.getBinding(name);
34094
+ if (!binding) return;
34095
+ if (!reactiveBindingIds.has(binding.identifier)) return;
34096
+ if (binding.scope === idPath.scope || binding.scope === fnPath.scope) return;
34097
+ captured.add(name);
34098
+ }
34099
+ });
34100
+ return captured;
34101
+ };
34102
+ const registerClosureCaptureBinding = (fnPath, captured) => {
34103
+ if (captured.size === 0) return;
34104
+ if (fnPath.isFunctionDeclaration() && fnPath.node.id) {
34105
+ const binding = fnPath.parentPath.scope.getBinding(fnPath.node.id.name);
34106
+ if (binding) {
34107
+ capturedClosureByBinding.set(binding.identifier, captured);
34108
+ }
34109
+ return;
34110
+ }
34111
+ if ((fnPath.isFunctionExpression() || fnPath.isArrowFunctionExpression()) && fnPath.parentPath.isVariableDeclarator()) {
34112
+ const id = fnPath.parentPath.node.id;
34113
+ if (!t4.isIdentifier(id)) return;
34114
+ const binding = fnPath.parentPath.scope.getBinding(id.name);
34115
+ if (binding) {
34116
+ capturedClosureByBinding.set(binding.identifier, captured);
34117
+ }
34118
+ return;
34119
+ }
34120
+ if (fnPath.parentPath.isAssignmentExpression({ right: fnPath.node })) {
34121
+ const left = fnPath.parentPath.node.left;
34122
+ if (!t4.isIdentifier(left)) return;
34123
+ const binding = fnPath.parentPath.scope.getBinding(left.name);
34124
+ if (binding) {
34125
+ capturedClosureByBinding.set(binding.identifier, captured);
34126
+ }
34127
+ }
34128
+ };
34129
+ const collectCapturedForArgument = (argPath) => {
34130
+ if (argPath.isArrowFunctionExpression() || argPath.isFunctionExpression()) {
34131
+ const captured2 = collectCapturedReactiveNames(argPath);
34132
+ return captured2.size > 0 ? captured2 : null;
34133
+ }
34134
+ if (!argPath.isIdentifier()) return null;
34135
+ const binding = argPath.scope.getBinding(argPath.node.name);
34136
+ if (!binding) return null;
34137
+ const captured = capturedClosureByBinding.get(binding.identifier);
34138
+ return captured && captured.size > 0 ? captured : null;
34139
+ };
34140
+ const isNonEscapingCallbackHost = (callee) => {
34141
+ const member = t4.isMemberExpression(callee) || t4.isOptionalMemberExpression(callee) ? callee : null;
34142
+ if (!member || member.computed || !t4.isIdentifier(member.property)) return false;
34143
+ return NON_ESCAPING_CALLBACK_METHODS.has(member.property.name);
34144
+ };
34145
+ const emitClosureCaptureWarning = (node, captured) => {
34146
+ const names = Array.from(captured).sort().join(", ");
34147
+ emitWarning(
34148
+ node,
34149
+ "FICT-R005",
34150
+ `Function captures reactive variable(s): ${names}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
34151
+ warn,
34152
+ fileName
34153
+ );
34154
+ };
33312
34155
  const argumentHasReactive = (argPath) => {
33313
34156
  if (argPath.isSpreadElement()) {
33314
34157
  const inner = argPath.get("argument");
@@ -33331,12 +34174,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33331
34174
  path2.skip();
33332
34175
  },
33333
34176
  Identifier(idPath) {
33334
- if (idPath.parentPath.isMemberExpression({ property: idPath.node }) && !idPath.parent.computed) {
33335
- return;
33336
- }
33337
- if (idPath.parentPath.isObjectProperty({ key: idPath.node }) && !idPath.parent.computed && !idPath.parent.shorthand) {
33338
- return;
33339
- }
34177
+ if (shouldIgnoreIdentifierReference(idPath)) return;
33340
34178
  const binding = idPath.scope.getBinding(idPath.node.name);
33341
34179
  if (binding && reactiveBindingIds.has(binding.identifier)) {
33342
34180
  found = true;
@@ -33417,36 +34255,14 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33417
34255
  }
33418
34256
  },
33419
34257
  Function(path2) {
33420
- const captured = /* @__PURE__ */ new Set();
33421
- path2.traverse(
33422
- {
33423
- Function(inner) {
33424
- if (inner === path2) return;
33425
- inner.skip();
33426
- },
33427
- Identifier(idPath) {
33428
- const name = idPath.node.name;
33429
- const binding = idPath.scope.getBinding(name);
33430
- if (!binding) return;
33431
- if (!reactiveBindingIds.has(binding.identifier)) return;
33432
- if (binding.scope === idPath.scope || binding.scope === path2.scope) return;
33433
- captured.add(name);
33434
- }
33435
- },
33436
- {}
33437
- );
33438
- if (captured.size > 0) {
33439
- emitWarning(
33440
- path2.node,
33441
- "FICT-R005",
33442
- `Function captures reactive variable(s): ${Array.from(captured).join(", ")}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
33443
- warn,
33444
- fileName
33445
- );
33446
- }
34258
+ const captured = collectCapturedReactiveNames(path2);
34259
+ registerClosureCaptureBinding(path2, captured);
33447
34260
  },
33448
34261
  CallExpression(path2) {
33449
- const isEffect = isEffectCall(path2.node, t4, effectMacroNames);
34262
+ const callNode = path2.node;
34263
+ if (isStateCall(callNode, t4, stateMacroNames)) return;
34264
+ if (isMemoCall(callNode, t4, memoMacroNames)) return;
34265
+ const isEffect = isEffectCall(callNode, t4, effectMacroNames);
33450
34266
  if (isEffect) {
33451
34267
  const argPath = path2.get("arguments.0");
33452
34268
  if (argPath?.isFunctionExpression() || argPath?.isArrowFunctionExpression()) {
@@ -33469,7 +34285,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33469
34285
  });
33470
34286
  if (!hasReactiveDependency) {
33471
34287
  emitWarning(
33472
- path2.node,
34288
+ callNode,
33473
34289
  "FICT-E001",
33474
34290
  "Effect has no reactive reads; it will run once. Consider removing $effect or adding dependencies.",
33475
34291
  warn,
@@ -33492,6 +34308,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33492
34308
  const isSafe = calleeName && SAFE_FUNCTIONS.has(calleeName);
33493
34309
  if (isSafe) return;
33494
34310
  const argPaths = path2.get("arguments");
34311
+ const nonEscapingCallbackHost = isNonEscapingCallbackHost(callee);
33495
34312
  for (const argPath of argPaths) {
33496
34313
  if (argPath.isIdentifier() && hasTrackedBinding(argPath, argPath.node.name, stateBindingIds)) {
33497
34314
  continue;
@@ -33507,6 +34324,13 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33507
34324
  break;
33508
34325
  }
33509
34326
  }
34327
+ if (nonEscapingCallbackHost) return;
34328
+ for (const argPath of argPaths) {
34329
+ const captured = collectCapturedForArgument(argPath);
34330
+ if (!captured) continue;
34331
+ emitClosureCaptureWarning(argPath.node, captured);
34332
+ break;
34333
+ }
33510
34334
  },
33511
34335
  OptionalMemberExpression(path2) {
33512
34336
  if (!path2.node.computed) return;
@@ -34371,6 +35195,8 @@ or extract the nested logic into a custom hook (useXxx).`
34371
35195
  stateBindingIds,
34372
35196
  stateRootBindingIds,
34373
35197
  reactiveBindingIds,
35198
+ stateMacroNames,
35199
+ memoMacroNames,
34374
35200
  effectMacroNames,
34375
35201
  warn,
34376
35202
  fileName,
@@ -34415,13 +35241,17 @@ var createFictPlugin = declare(
34415
35241
  (api, options = {}) => {
34416
35242
  api.assertVersion(7);
34417
35243
  const t4 = api.types;
35244
+ const strictGuaranteeFromEnv = readBooleanEnv("FICT_STRICT_GUARANTEE") === true;
34418
35245
  const normalizedOptions = {
34419
35246
  ...options,
34420
35247
  fineGrainedDom: options.fineGrainedDom ?? true,
35248
+ lazyConditional: options.lazyConditional ?? true,
35249
+ getterCache: options.getterCache ?? true,
34421
35250
  optimize: options.optimize ?? true,
34422
35251
  optimizeLevel: options.optimizeLevel ?? "safe",
34423
35252
  inlineDerivedMemos: options.inlineDerivedMemos ?? true,
34424
35253
  emitModuleMetadata: options.emitModuleMetadata ?? "auto",
35254
+ strictGuarantee: strictGuaranteeFromEnv || options.strictGuarantee !== false,
34425
35255
  dev: options.dev ?? (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test")
34426
35256
  };
34427
35257
  return {