@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.cjs CHANGED
@@ -14241,6 +14241,9 @@ var RUNTIME_ALIASES = {
14241
14241
  hydrateComponent: "hydrateComponent",
14242
14242
  registerResume: "__fictRegisterResume"
14243
14243
  };
14244
+ var RUNTIME_HELPER_MODULES = {
14245
+ keyedList: "@fictjs/runtime/internal/list"
14246
+ };
14244
14247
  var DelegatedEvents = /* @__PURE__ */ new Set([...DelegatedEventNames]);
14245
14248
  var SAFE_FUNCTIONS = /* @__PURE__ */ new Set([
14246
14249
  // Console methods
@@ -19939,15 +19942,38 @@ function buildEffectCall(ctx, t4, effectFn, options) {
19939
19942
  }
19940
19943
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.useEffect), args);
19941
19944
  }
19942
- function buildMemoCall(ctx, t4, memoFn, slot) {
19945
+ function buildMemoCall(ctx, t4, memoFn, options) {
19946
+ const slot = options?.slot;
19947
+ const memoOptionsProperties = [];
19948
+ if (options?.name) {
19949
+ memoOptionsProperties.push(
19950
+ t4.objectProperty(t4.identifier("name"), t4.stringLiteral(options.name))
19951
+ );
19952
+ }
19953
+ if (options?.source) {
19954
+ memoOptionsProperties.push(
19955
+ t4.objectProperty(t4.identifier("devToolsSource"), t4.stringLiteral(options.source))
19956
+ );
19957
+ }
19958
+ if (options?.internal) {
19959
+ memoOptionsProperties.push(t4.objectProperty(t4.identifier("internal"), t4.booleanLiteral(true)));
19960
+ }
19961
+ const memoOptions = memoOptionsProperties.length > 0 ? t4.objectExpression(memoOptionsProperties) : null;
19943
19962
  if (ctx.inModule) {
19944
19963
  ctx.helpersUsed.add("memo");
19945
- return t4.callExpression(t4.identifier(RUNTIME_ALIASES.memo), [memoFn]);
19964
+ const args2 = [memoFn];
19965
+ if (memoOptions) args2.push(memoOptions);
19966
+ return t4.callExpression(t4.identifier(RUNTIME_ALIASES.memo), args2);
19946
19967
  }
19947
19968
  ctx.helpersUsed.add("useMemo");
19948
19969
  ctx.needsCtx = true;
19949
19970
  const args = [t4.identifier("__fictCtx"), memoFn];
19950
- if (slot !== void 0 && slot >= 0) {
19971
+ if (memoOptions) {
19972
+ args.push(memoOptions);
19973
+ if (slot !== void 0 && slot >= 0) {
19974
+ args.push(t4.numericLiteral(slot));
19975
+ }
19976
+ } else if (slot !== void 0 && slot >= 0) {
19951
19977
  args.push(t4.numericLiteral(slot));
19952
19978
  }
19953
19979
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.useMemo), args);
@@ -21208,7 +21234,10 @@ function wrapInMemo(region, t4, declaredVars, ctx, bodyStatementsOverride, outpu
21208
21234
  const returnObj = t4.objectExpression(uniqueOutputNames.map((name) => buildOutputProperty(name)));
21209
21235
  const memoBody = t4.blockStatement([...bodyStatements, t4.returnStatement(returnObj)]);
21210
21236
  const slot = ctx.inModule ? void 0 : reserveHookSlot(ctx);
21211
- const memoCall = buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], memoBody), slot);
21237
+ const memoCall = buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], memoBody), {
21238
+ slot,
21239
+ internal: true
21240
+ });
21212
21241
  const regionVarName = `__region_${region.id}`;
21213
21242
  statements.push(
21214
21243
  t4.variableDeclaration("const", [t4.variableDeclarator(t4.identifier(regionVarName), memoCall)])
@@ -21467,7 +21496,10 @@ function generateLazyConditionalMemo(region, orderedOutputs, bodyStatements, con
21467
21496
  ctx,
21468
21497
  t4,
21469
21498
  t4.arrowFunctionExpression([], t4.blockStatement(memoBody)),
21470
- ctx.inModule ? void 0 : reserveHookSlot(ctx)
21499
+ {
21500
+ slot: ctx.inModule ? void 0 : reserveHookSlot(ctx),
21501
+ internal: true
21502
+ }
21471
21503
  );
21472
21504
  statements.push(
21473
21505
  t4.variableDeclaration("const", [t4.variableDeclarator(t4.identifier(regionVarName), memoCall)])
@@ -21580,7 +21612,12 @@ Context: ${location}`
21580
21612
  };
21581
21613
  const buildDerivedMemoCall = (expr) => {
21582
21614
  const slot = !ctx.inModule && inRegionMemo ? reserveHookSlot(ctx) : void 0;
21583
- return buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], expr), slot);
21615
+ const source = ctx.options?.dev !== false && instr.loc ? `${ctx.options?.filename ?? ""}:${instr.loc.start.line}:${instr.loc.start.column}` : void 0;
21616
+ return buildMemoCall(ctx, t4, t4.arrowFunctionExpression([], expr), {
21617
+ slot,
21618
+ name: baseName2,
21619
+ source
21620
+ });
21584
21621
  };
21585
21622
  if (isShadowDeclaration && declKind) {
21586
21623
  ctx.trackedVars.delete(baseName2);
@@ -22942,7 +22979,7 @@ function expressionUsesIdentifier(expr, name, t4) {
22942
22979
  visit(expr);
22943
22980
  return found;
22944
22981
  }
22945
- function extractDelegatedEventData(expr, t4) {
22982
+ function extractDelegatedEventData(expr, t4, options) {
22946
22983
  const isSimpleHandler = t4.isIdentifier(expr) || t4.isMemberExpression(expr);
22947
22984
  if (isSimpleHandler) {
22948
22985
  return { handler: expr };
@@ -22955,6 +22992,9 @@ function extractDelegatedEventData(expr, t4) {
22955
22992
  if (!bodyExpr || !t4.isCallExpression(bodyExpr)) return null;
22956
22993
  if (paramNames.some((name) => expressionUsesIdentifier(bodyExpr, name, t4))) return null;
22957
22994
  if (!t4.isIdentifier(bodyExpr.callee)) return null;
22995
+ if (options?.isKnownHandlerIdentifier && !options.isKnownHandlerIdentifier(bodyExpr.callee.name)) {
22996
+ return null;
22997
+ }
22958
22998
  if (bodyExpr.arguments.length === 0) return null;
22959
22999
  if (bodyExpr.arguments.length > 1) return null;
22960
23000
  const dataArg = bodyExpr.arguments[0];
@@ -23017,6 +23057,10 @@ function extractDelegatedEventDataFromHIR(expr, ctx) {
23017
23057
  return null;
23018
23058
  }
23019
23059
  const handlerName = callee.name;
23060
+ const normalizedHandlerName = deSSAVarName(handlerName);
23061
+ if (!ctx.functionVars?.has(normalizedHandlerName)) {
23062
+ return null;
23063
+ }
23020
23064
  if (ctx.signalVars?.has(handlerName) || ctx.memoVars?.has(handlerName) || ctx.aliasVars?.has(handlerName) || ctx.storeVars?.has(handlerName) || ctx.trackedVars.has(handlerName)) {
23021
23065
  return null;
23022
23066
  }
@@ -23027,7 +23071,10 @@ function extractDelegatedEventDataFromHIR(expr, ctx) {
23027
23071
  if (isTrackedAccessor) {
23028
23072
  return null;
23029
23073
  }
23030
- const paramNames = new Set(expr.params.map((p) => p.name));
23074
+ const paramNames = new Set(expr.params.map((p) => deSSAVarName(p.name)));
23075
+ if (paramNames.has(deSSAVarName(callee.name))) {
23076
+ return null;
23077
+ }
23031
23078
  const dataExpr = bodyExpr.arguments[0];
23032
23079
  if (!dataExpr) {
23033
23080
  return null;
@@ -24066,17 +24113,19 @@ function collectDeclaredNames(body, t4) {
24066
24113
  function attachHelperImports(ctx, body, t4) {
24067
24114
  if (ctx.helpersUsed.size === 0) return body;
24068
24115
  const declared = collectDeclaredNames(body, t4);
24069
- const specifiers = [];
24116
+ const specifiersByModule = /* @__PURE__ */ new Map();
24070
24117
  for (const name of ctx.helpersUsed) {
24071
24118
  const alias = RUNTIME_ALIASES[name];
24072
24119
  const helper = RUNTIME_HELPERS[name];
24073
24120
  if (alias && helper) {
24074
24121
  if (declared.has(alias)) continue;
24075
- specifiers.push(t4.importSpecifier(t4.identifier(alias), t4.identifier(helper)));
24122
+ const modulePath = RUNTIME_HELPER_MODULES[name] ?? RUNTIME_MODULE;
24123
+ const moduleSpecifiers = specifiersByModule.get(modulePath) ?? [];
24124
+ moduleSpecifiers.push(t4.importSpecifier(t4.identifier(alias), t4.identifier(helper)));
24125
+ specifiersByModule.set(modulePath, moduleSpecifiers);
24076
24126
  }
24077
24127
  }
24078
- if (specifiers.length === 0) return body;
24079
- const importDecl = t4.importDeclaration(specifiers, t4.stringLiteral(RUNTIME_MODULE));
24128
+ if (specifiersByModule.size === 0) return body;
24080
24129
  const helpers = [];
24081
24130
  if (ctx.needsForOfHelper) {
24082
24131
  const itemId = t4.identifier("item");
@@ -24114,7 +24163,15 @@ function attachHelperImports(ctx, body, t4) {
24114
24163
  )
24115
24164
  );
24116
24165
  }
24117
- return [importDecl, ...helpers, ...body];
24166
+ const modulePaths = Array.from(specifiersByModule.keys()).sort((a, b) => {
24167
+ if (a === RUNTIME_MODULE) return -1;
24168
+ if (b === RUNTIME_MODULE) return 1;
24169
+ return a.localeCompare(b);
24170
+ });
24171
+ const importDecls = modulePaths.map(
24172
+ (modulePath) => t4.importDeclaration(specifiersByModule.get(modulePath) ?? [], t4.stringLiteral(modulePath))
24173
+ );
24174
+ return [...importDecls, ...helpers, ...body];
24118
24175
  }
24119
24176
 
24120
24177
  // src/ir/codegen-jsx-keys.ts
@@ -24184,8 +24241,8 @@ function getDependencyPathFromNode(node, t4) {
24184
24241
  }
24185
24242
  return null;
24186
24243
  }
24187
- function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parentKey, skipCurrentNode = false) {
24188
- const isCallTarget = parentKey === "callee" && (parentKind === "CallExpression" || parentKind === "OptionalCallExpression");
24244
+ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parentKey, skipCurrentNode = false, allowCallCalleeReplacement = false) {
24245
+ const isCallTarget = !allowCallCalleeReplacement && parentKey === "callee" && (parentKind === "CallExpression" || parentKind === "OptionalCallExpression");
24189
24246
  if (parentKind === "VariableDeclarator" && parentKey === "id") {
24190
24247
  return;
24191
24248
  }
@@ -24257,9 +24314,25 @@ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parent
24257
24314
  }
24258
24315
  }
24259
24316
  if (t4.isBlockStatement(node.body)) {
24260
- replaceIdentifiersWithOverrides(node.body, scopedOverrides, t4, node.type, "body");
24317
+ replaceIdentifiersWithOverrides(
24318
+ node.body,
24319
+ scopedOverrides,
24320
+ t4,
24321
+ node.type,
24322
+ "body",
24323
+ false,
24324
+ allowCallCalleeReplacement
24325
+ );
24261
24326
  } else {
24262
- replaceIdentifiersWithOverrides(node.body, scopedOverrides, t4, node.type, "body");
24327
+ replaceIdentifiersWithOverrides(
24328
+ node.body,
24329
+ scopedOverrides,
24330
+ t4,
24331
+ node.type,
24332
+ "body",
24333
+ false,
24334
+ allowCallCalleeReplacement
24335
+ );
24263
24336
  }
24264
24337
  return;
24265
24338
  }
@@ -24287,12 +24360,21 @@ function replaceIdentifiersWithOverrides(node, overrides, t4, parentKind, parent
24287
24360
  t4,
24288
24361
  node.type,
24289
24362
  key,
24290
- false
24363
+ false,
24364
+ allowCallCalleeReplacement
24291
24365
  );
24292
24366
  }
24293
24367
  }
24294
24368
  } else if (value && typeof value === "object" && "type" in value) {
24295
- replaceIdentifiersWithOverrides(value, overrides, t4, node.type, key);
24369
+ replaceIdentifiersWithOverrides(
24370
+ value,
24371
+ overrides,
24372
+ t4,
24373
+ node.type,
24374
+ key,
24375
+ false,
24376
+ allowCallCalleeReplacement
24377
+ );
24296
24378
  }
24297
24379
  }
24298
24380
  }
@@ -24565,6 +24647,124 @@ function applySelectorHoist(callbackExpr, itemParamName, keyParamName, statement
24565
24647
  }
24566
24648
 
24567
24649
  // src/ir/codegen-list-child.ts
24650
+ function getCallbackBlocks(callback) {
24651
+ if (callback.kind === "FunctionExpression") {
24652
+ return callback.body;
24653
+ }
24654
+ if (callback.kind === "ArrowFunction" && Array.isArray(callback.body)) {
24655
+ return callback.body;
24656
+ }
24657
+ return [];
24658
+ }
24659
+ function collectMapCallbackAliasDeclarations(callback) {
24660
+ const blocks = getCallbackBlocks(callback);
24661
+ if (blocks.length === 0) {
24662
+ return /* @__PURE__ */ new Map();
24663
+ }
24664
+ const paramNames = callback.kind === "ArrowFunction" || callback.kind === "FunctionExpression" ? new Set(callback.params.map((param) => param.name)) : /* @__PURE__ */ new Set();
24665
+ const declarationState = /* @__PURE__ */ new Map();
24666
+ for (const block of blocks) {
24667
+ for (const instr of block.instructions) {
24668
+ if (instr.kind !== "Assign" || instr.target.kind !== "Identifier") {
24669
+ continue;
24670
+ }
24671
+ const name = instr.target.name;
24672
+ if (paramNames.has(name)) continue;
24673
+ const isDeclaration = !!instr.declarationKind;
24674
+ const previous = declarationState.get(name);
24675
+ if (previous) {
24676
+ declarationState.set(name, {
24677
+ declarationCount: previous.declarationCount + (isDeclaration ? 1 : 0),
24678
+ hasNonDeclarationWrite: previous.hasNonDeclarationWrite || !isDeclaration,
24679
+ declarationValue: previous.declarationValue,
24680
+ lastAssignedValue: instr.value
24681
+ });
24682
+ } else {
24683
+ declarationState.set(name, {
24684
+ declarationCount: isDeclaration ? 1 : 0,
24685
+ hasNonDeclarationWrite: !isDeclaration,
24686
+ declarationValue: isDeclaration ? instr.value : null,
24687
+ lastAssignedValue: instr.value
24688
+ });
24689
+ }
24690
+ }
24691
+ }
24692
+ const aliasMap = /* @__PURE__ */ new Map();
24693
+ const effectiveBlocks = blocks.filter(
24694
+ (block) => block.instructions.length > 0 || block.terminator.kind !== "Unreachable"
24695
+ );
24696
+ const isSingleLinearBlock = effectiveBlocks.length === 1 && effectiveBlocks[0].terminator.kind === "Return";
24697
+ for (const [name, state] of declarationState) {
24698
+ if (isSingleLinearBlock) {
24699
+ if (state.declarationCount <= 1) {
24700
+ aliasMap.set(name, state.lastAssignedValue);
24701
+ }
24702
+ continue;
24703
+ }
24704
+ if (state.declarationCount === 1 && !state.hasNonDeclarationWrite && state.declarationValue) {
24705
+ aliasMap.set(name, state.declarationValue);
24706
+ }
24707
+ }
24708
+ return aliasMap;
24709
+ }
24710
+ function resolveMapCallbackKeyExpression(keyExpr, callback) {
24711
+ if (keyExpr.kind !== "Identifier") {
24712
+ return keyExpr;
24713
+ }
24714
+ const aliasMap = collectMapCallbackAliasDeclarations(callback);
24715
+ if (aliasMap.size === 0) {
24716
+ return keyExpr;
24717
+ }
24718
+ let resolved = keyExpr;
24719
+ const seen = /* @__PURE__ */ new Set();
24720
+ while (resolved.kind === "Identifier") {
24721
+ const next = aliasMap.get(resolved.name);
24722
+ if (!next || seen.has(resolved.name)) break;
24723
+ seen.add(resolved.name);
24724
+ resolved = next;
24725
+ }
24726
+ return resolved;
24727
+ }
24728
+ function collectMapCallbackLocalNames(callback) {
24729
+ const blocks = getCallbackBlocks(callback);
24730
+ if (blocks.length === 0) {
24731
+ return /* @__PURE__ */ new Set();
24732
+ }
24733
+ const paramNames = callback.kind === "ArrowFunction" || callback.kind === "FunctionExpression" ? new Set(callback.params.map((param) => deSSAVarName(param.name))) : /* @__PURE__ */ new Set();
24734
+ const locals = /* @__PURE__ */ new Set();
24735
+ for (const block of blocks) {
24736
+ for (const instr of block.instructions) {
24737
+ if (instr.kind === "Assign" && instr.target.kind === "Identifier") {
24738
+ const name = deSSAVarName(instr.target.name);
24739
+ if (!paramNames.has(name)) locals.add(name);
24740
+ }
24741
+ if (instr.kind === "Phi" && instr.target.kind === "Identifier") {
24742
+ const name = deSSAVarName(instr.target.name);
24743
+ if (!paramNames.has(name)) locals.add(name);
24744
+ }
24745
+ }
24746
+ }
24747
+ return locals;
24748
+ }
24749
+ function hasUnresolvedCallbackLocalKeyDependencies(keyExpr, callback, keyAliasDeclarations) {
24750
+ const callbackLocals = collectMapCallbackLocalNames(callback);
24751
+ if (callbackLocals.size === 0) {
24752
+ return false;
24753
+ }
24754
+ const resolvableAliases = new Set(
24755
+ Array.from(keyAliasDeclarations.keys()).map((name) => deSSAVarName(name))
24756
+ );
24757
+ const deps = /* @__PURE__ */ new Set();
24758
+ collectExpressionDependencies(keyExpr, deps);
24759
+ for (const dep of deps) {
24760
+ const base = dep.split(".")[0] ?? dep;
24761
+ if (!base) continue;
24762
+ if (callbackLocals.has(base) && !resolvableAliases.has(base)) {
24763
+ return true;
24764
+ }
24765
+ }
24766
+ return false;
24767
+ }
24568
24768
  function buildListCallExpression(expr, statements, ctx, ops) {
24569
24769
  const { t: t4 } = ctx;
24570
24770
  if (expr.kind !== "CallExpression" && expr.kind !== "OptionalCallExpression") {
@@ -24583,7 +24783,8 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24583
24783
  if (!mapCallback) {
24584
24784
  throw new Error("map callback is required");
24585
24785
  }
24586
- const keyExpr = extractKeyFromMapCallback(mapCallback);
24786
+ const extractedKeyExpr = extractKeyFromMapCallback(mapCallback);
24787
+ const keyExpr = extractedKeyExpr ? resolveMapCallbackKeyExpression(extractedKeyExpr, mapCallback) : void 0;
24587
24788
  const isKeyed = !!keyExpr;
24588
24789
  const hasRestParam = (mapCallback.kind === "ArrowFunction" || mapCallback.kind === "FunctionExpression") && Array.isArray(mapCallback.rawParams) && mapCallback.rawParams.some((param) => t4.isRestElement(param));
24589
24790
  const canConstifyKey = isKeyed && keyExpr && !hasRestParam;
@@ -24634,7 +24835,7 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24634
24835
  if (Object.keys(overrides).length > 0) {
24635
24836
  if (t4.isBlockStatement(callbackExpr.body)) {
24636
24837
  for (const stmt of callbackExpr.body.body) {
24637
- if (!t4.isVariableDeclaration(stmt)) continue;
24838
+ if (!t4.isVariableDeclaration(stmt) || stmt.kind !== "const") continue;
24638
24839
  for (const decl of stmt.declarations) {
24639
24840
  if (!t4.isIdentifier(decl.id) || !decl.init) continue;
24640
24841
  const replacement = t4.cloneNode(decl.init, true);
@@ -24664,7 +24865,32 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24664
24865
  }
24665
24866
  let listCall;
24666
24867
  if (isKeyed && keyExpr) {
24868
+ const keyAliasDeclarations = collectMapCallbackAliasDeclarations(mapCallback);
24869
+ const hasUnresolvedLocalKeyDeps = hasUnresolvedCallbackLocalKeyDependencies(
24870
+ keyExpr,
24871
+ mapCallback,
24872
+ keyAliasDeclarations
24873
+ );
24667
24874
  let keyExprAst = ops.lowerExpression(keyExpr, ctx);
24875
+ if (keyAliasDeclarations.size > 0) {
24876
+ const keyOverrides = {};
24877
+ for (const [name, value] of keyAliasDeclarations) {
24878
+ const replacement = ops.lowerExpression(value, ctx);
24879
+ replaceIdentifiersWithOverrides(replacement, keyOverrides, t4);
24880
+ keyOverrides[name] = () => t4.cloneNode(replacement, true);
24881
+ }
24882
+ if (Object.keys(keyOverrides).length > 0) {
24883
+ replaceIdentifiersWithOverrides(
24884
+ keyExprAst,
24885
+ keyOverrides,
24886
+ t4,
24887
+ void 0,
24888
+ void 0,
24889
+ false,
24890
+ true
24891
+ );
24892
+ }
24893
+ }
24668
24894
  if (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) {
24669
24895
  const itemParam = callbackExpr.params[0];
24670
24896
  const indexParam = callbackExpr.params[1];
@@ -24678,6 +24904,9 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24678
24904
  }
24679
24905
  const itemParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? callbackExpr.params[0] : null;
24680
24906
  const indexParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? callbackExpr.params[1] : null;
24907
+ if (hasUnresolvedLocalKeyDeps) {
24908
+ keyExprAst = t4.identifier(t4.isIdentifier(indexParamName) ? indexParamName.name : "__index");
24909
+ }
24681
24910
  const keyFn = t4.arrowFunctionExpression(
24682
24911
  [
24683
24912
  t4.isIdentifier(itemParamName) ? itemParamName : t4.identifier("__item"),
@@ -25119,33 +25348,42 @@ function dependencyCoveredByDeclarations(dep, region) {
25119
25348
 
25120
25349
  // src/ir/codegen-resumable-utils.ts
25121
25350
  var import_node_url2 = require("url");
25122
- function renameIdentifiersInExpr(expr, renames) {
25123
- const cloned = JSON.parse(JSON.stringify(expr));
25124
- function visit(node) {
25125
- if (!node || typeof node !== "object") return;
25126
- const n = node;
25127
- if (n.type === "Identifier" && typeof n.name === "string") {
25128
- const newName = renames.get(n.name);
25129
- if (newName) {
25130
- n.name = newName;
25131
- }
25132
- }
25133
- for (const key of Object.keys(n)) {
25134
- if (key === "loc" || key === "start" || key === "end" || key === "extra" || key === "comments" || key === "leadingComments" || key === "trailingComments") {
25135
- continue;
25136
- }
25137
- const value = n[key];
25138
- if (Array.isArray(value)) {
25139
- for (const item of value) {
25140
- visit(item);
25141
- }
25142
- } else if (value && typeof value === "object") {
25143
- visit(value);
25351
+ var import_traverse2 = __toESM(require("@babel/traverse"), 1);
25352
+ function renameIdentifiersInExpr(expr, renames, t4) {
25353
+ const traverse = import_traverse2.default.default ?? import_traverse2.default;
25354
+ const cloned = t4.cloneNode(expr, true);
25355
+ const file = t4.file(t4.program([t4.expressionStatement(cloned)]));
25356
+ traverse(file, {
25357
+ Identifier(path2) {
25358
+ const oldName = path2.node.name;
25359
+ const nextName = renames.get(oldName);
25360
+ if (!nextName) return;
25361
+ if (path2.parentPath.isObjectProperty() && path2.parentPath.node.shorthand && path2.parentPath.node.value === path2.node && t4.isIdentifier(path2.parentPath.node.key)) {
25362
+ path2.parentPath.node.shorthand = false;
25363
+ path2.parentPath.node.value = t4.identifier(nextName);
25364
+ return;
25144
25365
  }
25366
+ if (!path2.isReferencedIdentifier()) return;
25367
+ const binding = path2.scope.getBinding(oldName);
25368
+ if (binding && binding.scope !== path2.scope.getProgramParent()) return;
25369
+ path2.node.name = nextName;
25145
25370
  }
25146
- }
25147
- visit(cloned);
25148
- return cloned;
25371
+ });
25372
+ const first = file.program.body[0];
25373
+ return t4.isExpressionStatement(first) ? first.expression : cloned;
25374
+ }
25375
+ function collectFreeIdentifiersInExpr(expr, t4) {
25376
+ const traverse = import_traverse2.default.default ?? import_traverse2.default;
25377
+ const file = t4.file(t4.program([t4.expressionStatement(t4.cloneNode(expr, true))]));
25378
+ const names = /* @__PURE__ */ new Set();
25379
+ traverse(file, {
25380
+ ReferencedIdentifier(path2) {
25381
+ const name = path2.node.name;
25382
+ if (path2.scope.getBinding(name)) return;
25383
+ names.add(name);
25384
+ }
25385
+ });
25386
+ return names;
25149
25387
  }
25150
25388
  function genModuleUrlExpr(ctx) {
25151
25389
  const { t: t4 } = ctx;
@@ -25282,10 +25520,10 @@ function registerResumableComponent(componentName, ctx) {
25282
25520
  }
25283
25521
 
25284
25522
  // src/ir/codegen-resumable-events.ts
25285
- function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, containingRegion, ops) {
25523
+ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, containingRegion, ops, options) {
25286
25524
  const { t: t4 } = ctx;
25287
25525
  if (!ctx.resumableEnabled) {
25288
- return;
25526
+ return false;
25289
25527
  }
25290
25528
  const prevWrapTracked = ctx.wrapTrackedExpressions;
25291
25529
  ctx.wrapTrackedExpressions = false;
@@ -25299,53 +25537,88 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25299
25537
  const scopeParam = t4.identifier("scopeId");
25300
25538
  const ensureHandlerParam = (fn) => {
25301
25539
  if (t4.isArrowFunctionExpression(fn)) {
25302
- if (fn.params.length > 0) return fn;
25303
- return t4.arrowFunctionExpression([eventParam], fn.body, fn.async);
25540
+ return fn;
25304
25541
  }
25305
25542
  if (t4.isFunctionExpression(fn)) {
25306
- if (fn.params.length > 0) return fn;
25307
- return t4.functionExpression(fn.id, [eventParam], fn.body, fn.generator, fn.async);
25543
+ return fn;
25308
25544
  }
25309
25545
  if (t4.isIdentifier(fn) || t4.isMemberExpression(fn)) {
25310
25546
  return fn;
25311
25547
  }
25312
- if (t4.isCallExpression(fn) && fn.arguments.length === 0 && (t4.isIdentifier(fn.callee) || t4.isMemberExpression(fn.callee))) {
25313
- return fn.callee;
25314
- }
25315
25548
  return t4.functionExpression(
25316
25549
  null,
25317
- [eventParam],
25318
- t4.blockStatement([
25319
- t4.returnStatement(
25320
- t4.callExpression(
25321
- t4.memberExpression(fn, t4.identifier("call")),
25322
- [t4.thisExpression(), eventParam]
25323
- )
25324
- )
25325
- ])
25550
+ [],
25551
+ t4.blockStatement([t4.returnStatement(fn)])
25326
25552
  );
25327
25553
  };
25328
25554
  const handlerExpr = ensureHandlerParam(valueExpr);
25329
25555
  const handlerId = t4.identifier(`__fict_e${ctx.resumableHandlerCounter ?? 0}`);
25330
25556
  ctx.resumableHandlerCounter = (ctx.resumableHandlerCounter ?? 0) + 1;
25331
- const captured = /* @__PURE__ */ new Set();
25332
- collectExpressionIdentifiersDeep(expr, captured);
25557
+ const captured = collectFreeIdentifiersInExpr(handlerExpr, t4);
25333
25558
  const lexicalNames = Array.from(captured).filter((name) => ctx.signalVars?.has(name));
25334
25559
  const propsName = ctx.propsParamName && captured.has(ctx.propsParamName) ? ctx.propsParamName : null;
25560
+ const unsupportedLocals = Array.from(captured).filter((name) => {
25561
+ if (ctx.inListRender && ctx.listKeyParamName && name === ctx.listKeyParamName) return true;
25562
+ if (!ctx.localDeclaredNames?.has(name)) return false;
25563
+ if (ctx.signalVars?.has(name)) return false;
25564
+ if (ctx.functionVars?.has(name)) return false;
25565
+ if (propsName && name === propsName) return false;
25566
+ return true;
25567
+ });
25568
+ const loweredFunctionDeps = /* @__PURE__ */ new Map();
25569
+ const unsafeFunctionCaptures = [];
25570
+ for (const name of captured) {
25571
+ if (!ctx.functionVars?.has(name) || ctx.signalVars?.has(name)) continue;
25572
+ if (ctx.hoistedFunctionDepNames?.has(name)) continue;
25573
+ const hirDef = ctx.componentFunctionDefs?.get(name);
25574
+ if (!hirDef) {
25575
+ if (ctx.localDeclaredNames?.has(name)) {
25576
+ unsafeFunctionCaptures.push(`${name} -> <unhoistable>`);
25577
+ }
25578
+ continue;
25579
+ }
25580
+ const loweredFn = ops.lowerDomExpression(hirDef, ctx, null, {
25581
+ skipHookAccessors: true,
25582
+ skipRegionRootOverride: true
25583
+ });
25584
+ const fnCaptured = collectFreeIdentifiersInExpr(loweredFn, t4);
25585
+ const localFnCaptures = Array.from(fnCaptured).filter((dep) => ctx.localDeclaredNames?.has(dep)).sort();
25586
+ if (localFnCaptures.length > 0) {
25587
+ unsafeFunctionCaptures.push(`${name} -> ${localFnCaptures.join(", ")}`);
25588
+ continue;
25589
+ }
25590
+ loweredFunctionDeps.set(name, loweredFn);
25591
+ }
25592
+ if (unsupportedLocals.length > 0 || unsafeFunctionCaptures.length > 0) {
25593
+ const detailParts = [];
25594
+ if (unsupportedLocals.length > 0) {
25595
+ detailParts.push(`direct: ${unsupportedLocals.sort().join(", ")}`);
25596
+ }
25597
+ if (unsafeFunctionCaptures.length > 0) {
25598
+ detailParts.push(`function deps: ${unsafeFunctionCaptures.sort().join("; ")}`);
25599
+ }
25600
+ const detail = `Resumable handlers cannot capture non-serializable local variables (${detailParts.join(" | ")}).`;
25601
+ if (options?.explicit) {
25602
+ const loc = expr.loc?.start;
25603
+ const fileName = ctx.options?.filename ?? "<unknown>";
25604
+ const location = loc ? `${fileName}:${loc.line}:${loc.column + 1}` : fileName;
25605
+ throw new Error(
25606
+ `${detail} Use signals/props/function references or remove '$' suffix.
25607
+ at ${location}`
25608
+ );
25609
+ }
25610
+ return false;
25611
+ }
25335
25612
  const functionDepRenames = /* @__PURE__ */ new Map();
25336
25613
  for (const name of captured) {
25337
25614
  if (ctx.functionVars?.has(name) && !ctx.signalVars?.has(name)) {
25338
- const hirDef = ctx.componentFunctionDefs?.get(name);
25339
- if (!hirDef) continue;
25340
25615
  let hoistedName = ctx.hoistedFunctionDepNames?.get(name);
25341
25616
  if (!hoistedName) {
25617
+ const loweredFn = loweredFunctionDeps.get(name);
25618
+ if (!loweredFn) continue;
25342
25619
  hoistedName = `__fict_fn_${name}_${ctx.hoistedFunctionDepCounter ?? 0}`;
25343
25620
  ctx.hoistedFunctionDepCounter = (ctx.hoistedFunctionDepCounter ?? 0) + 1;
25344
25621
  ctx.hoistedFunctionDepNames?.set(name, hoistedName);
25345
- const loweredFn = ops.lowerDomExpression(hirDef, ctx, null, {
25346
- skipHookAccessors: true,
25347
- skipRegionRootOverride: true
25348
- });
25349
25622
  const hoistedDecl = t4.variableDeclaration("const", [
25350
25623
  t4.variableDeclarator(t4.identifier(hoistedName), loweredFn)
25351
25624
  ]);
@@ -25357,7 +25630,7 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25357
25630
  }
25358
25631
  let finalHandlerExpr = handlerExpr;
25359
25632
  if (functionDepRenames.size > 0) {
25360
- finalHandlerExpr = renameIdentifiersInExpr(handlerExpr, functionDepRenames);
25633
+ finalHandlerExpr = renameIdentifiersInExpr(handlerExpr, functionDepRenames, t4);
25361
25634
  }
25362
25635
  const bodyStatements = [];
25363
25636
  if (lexicalNames.length > 0) {
@@ -25390,12 +25663,97 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25390
25663
  );
25391
25664
  }
25392
25665
  const handlerVar = t4.identifier("__handler");
25666
+ const resultVar = t4.identifier("__result");
25393
25667
  bodyStatements.push(
25394
25668
  t4.variableDeclaration("const", [t4.variableDeclarator(handlerVar, finalHandlerExpr)])
25395
25669
  );
25396
25670
  bodyStatements.push(
25397
- t4.returnStatement(
25398
- t4.callExpression(t4.memberExpression(handlerVar, t4.identifier("call")), [elParam, eventParam])
25671
+ t4.ifStatement(
25672
+ t4.binaryExpression(
25673
+ "===",
25674
+ t4.unaryExpression("typeof", handlerVar),
25675
+ t4.stringLiteral("function")
25676
+ ),
25677
+ t4.blockStatement([
25678
+ t4.variableDeclaration("const", [
25679
+ t4.variableDeclarator(
25680
+ resultVar,
25681
+ t4.callExpression(t4.memberExpression(handlerVar, t4.identifier("call")), [
25682
+ elParam,
25683
+ eventParam
25684
+ ])
25685
+ )
25686
+ ]),
25687
+ t4.ifStatement(
25688
+ t4.logicalExpression(
25689
+ "&&",
25690
+ t4.binaryExpression(
25691
+ "===",
25692
+ t4.unaryExpression("typeof", resultVar),
25693
+ t4.stringLiteral("function")
25694
+ ),
25695
+ t4.binaryExpression("!==", resultVar, handlerVar)
25696
+ ),
25697
+ t4.blockStatement([
25698
+ t4.returnStatement(
25699
+ t4.callExpression(t4.memberExpression(resultVar, t4.identifier("call")), [
25700
+ elParam,
25701
+ eventParam
25702
+ ])
25703
+ )
25704
+ ])
25705
+ ),
25706
+ t4.ifStatement(
25707
+ t4.logicalExpression(
25708
+ "&&",
25709
+ resultVar,
25710
+ t4.binaryExpression(
25711
+ "===",
25712
+ t4.unaryExpression(
25713
+ "typeof",
25714
+ t4.memberExpression(resultVar, t4.identifier("handleEvent"))
25715
+ ),
25716
+ t4.stringLiteral("function")
25717
+ )
25718
+ ),
25719
+ t4.blockStatement([
25720
+ t4.returnStatement(
25721
+ t4.callExpression(
25722
+ t4.memberExpression(
25723
+ t4.memberExpression(resultVar, t4.identifier("handleEvent")),
25724
+ t4.identifier("call")
25725
+ ),
25726
+ [resultVar, eventParam]
25727
+ )
25728
+ )
25729
+ ])
25730
+ ),
25731
+ t4.returnStatement(resultVar)
25732
+ ])
25733
+ )
25734
+ );
25735
+ bodyStatements.push(
25736
+ t4.ifStatement(
25737
+ t4.logicalExpression(
25738
+ "&&",
25739
+ handlerVar,
25740
+ t4.binaryExpression(
25741
+ "===",
25742
+ t4.unaryExpression("typeof", t4.memberExpression(handlerVar, t4.identifier("handleEvent"))),
25743
+ t4.stringLiteral("function")
25744
+ )
25745
+ ),
25746
+ t4.blockStatement([
25747
+ t4.returnStatement(
25748
+ t4.callExpression(
25749
+ t4.memberExpression(
25750
+ t4.memberExpression(handlerVar, t4.identifier("handleEvent")),
25751
+ t4.identifier("call")
25752
+ ),
25753
+ [handlerVar, eventParam]
25754
+ )
25755
+ )
25756
+ ])
25399
25757
  )
25400
25758
  );
25401
25759
  const exportedHandler = t4.exportNamedDeclaration(
@@ -25424,11 +25782,13 @@ function emitResumableEventBinding(targetId, eventName, expr, statements, ctx, c
25424
25782
  ])
25425
25783
  )
25426
25784
  );
25785
+ return true;
25427
25786
  }
25428
25787
 
25429
25788
  // src/ir/codegen-runtime-imports.ts
25430
25789
  var RUNTIME_IMPORT_MODULES = /* @__PURE__ */ new Set([
25431
25790
  RUNTIME_MODULE,
25791
+ ...Object.values(RUNTIME_HELPER_MODULES),
25432
25792
  "@fictjs/runtime",
25433
25793
  "@fictjs/runtime/advanced",
25434
25794
  "fict",
@@ -25807,7 +26167,8 @@ function extractHIRStaticHtml(jsx, ctx, ops, parentPath = [], namespace = null)
25807
26167
  name: eventName.toLowerCase(),
25808
26168
  expr: attr.value ?? void 0,
25809
26169
  eventOptions: { capture, passive, once },
25810
- resumable: shouldBeResumable
26170
+ resumable: shouldBeResumable,
26171
+ resumableExplicit: isResumableEvent
25811
26172
  });
25812
26173
  continue;
25813
26174
  }
@@ -26380,7 +26741,7 @@ function buildOutputParams(fn, t4) {
26380
26741
  }
26381
26742
  return fn.params.map((p) => t4.identifier(deSSAVarName(p.name)));
26382
26743
  }
26383
- function lowerTrackedExpression(expr, ctx) {
26744
+ function lowerTrackedExpression(expr, ctx, valueUsed = true) {
26384
26745
  const regionOverride = ctx.inReturn && ctx.currentFnIsHook ? null : ctx.currentRegion ?? (ctx.trackedVars.size ? {
26385
26746
  id: -1,
26386
26747
  dependencies: new Set(ctx.trackedVars),
@@ -26388,7 +26749,7 @@ function lowerTrackedExpression(expr, ctx) {
26388
26749
  hasControlFlow: false,
26389
26750
  hasReactiveWrites: false
26390
26751
  } : null);
26391
- const lowered = lowerExpression2(expr, ctx);
26752
+ const lowered = lowerExpression2(expr, ctx, valueUsed);
26392
26753
  if (ctx.t.isAssignmentExpression(lowered)) {
26393
26754
  const right = applyRegionMetadataToExpression2(lowered.right, ctx, regionOverride ?? void 0);
26394
26755
  return ctx.t.assignmentExpression(lowered.operator, lowered.left, right);
@@ -26510,7 +26871,7 @@ function lowerInstruction(instr, ctx) {
26510
26871
  );
26511
26872
  }
26512
26873
  if (instr.kind === "Expression") {
26513
- return applyLoc(t4.expressionStatement(lowerTrackedExpression(instr.value, ctx)));
26874
+ return applyLoc(t4.expressionStatement(lowerTrackedExpression(instr.value, ctx, false)));
26514
26875
  }
26515
26876
  if (instr.kind === "Phi") {
26516
26877
  return null;
@@ -26674,7 +27035,7 @@ function collectLocalDeclaredNames(params, blocks, t4) {
26674
27035
  }
26675
27036
  return declared;
26676
27037
  }
26677
- function lowerExpression2(expr, ctx, isAssigned = false) {
27038
+ function lowerExpression2(expr, ctx, valueUsed = true) {
26678
27039
  const depth = (ctx.expressionDepth ?? 0) + 1;
26679
27040
  const maxDepth = ctx.maxExpressionDepth ?? 500;
26680
27041
  if (depth > maxDepth) {
@@ -26685,12 +27046,12 @@ function lowerExpression2(expr, ctx, isAssigned = false) {
26685
27046
  }
26686
27047
  ctx.expressionDepth = depth;
26687
27048
  try {
26688
- return setNodeLoc(lowerExpressionImpl(expr, ctx, isAssigned), expr.loc);
27049
+ return setNodeLoc(lowerExpressionImpl(expr, ctx, valueUsed), expr.loc);
26689
27050
  } finally {
26690
27051
  ctx.expressionDepth = depth - 1;
26691
27052
  }
26692
27053
  }
26693
- function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27054
+ function lowerExpressionImpl(expr, ctx, valueUsed = true) {
26694
27055
  const { t: t4 } = ctx;
26695
27056
  const mapParams = (params) => params.map((p) => t4.identifier(deSSAVarName(p.name)));
26696
27057
  const lowerArgsAsExpressions = (args) => args.map(
@@ -26731,6 +27092,152 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
26731
27092
  ctx.localDeclaredNames = prevLocalDeclared;
26732
27093
  return result;
26733
27094
  };
27095
+ const lowerTrackedWriteCall = (callee, nextValue) => {
27096
+ if (!valueUsed) {
27097
+ return t4.callExpression(t4.cloneNode(callee, true), [nextValue]);
27098
+ }
27099
+ const nextId = genTemp3(ctx, "next");
27100
+ const nextRef = t4.identifier(nextId.name);
27101
+ return t4.callExpression(
27102
+ t4.arrowFunctionExpression(
27103
+ [t4.cloneNode(nextId, true)],
27104
+ t4.sequenceExpression([
27105
+ t4.callExpression(t4.cloneNode(callee, true), [nextRef]),
27106
+ t4.identifier(nextId.name)
27107
+ ])
27108
+ ),
27109
+ [nextValue]
27110
+ );
27111
+ };
27112
+ const buildTrackedAssignmentNext = (operator, current, right) => {
27113
+ switch (operator) {
27114
+ case "=":
27115
+ return right;
27116
+ case "+=":
27117
+ return t4.binaryExpression("+", current, right);
27118
+ case "-=":
27119
+ return t4.binaryExpression("-", current, right);
27120
+ case "*=":
27121
+ return t4.binaryExpression("*", current, right);
27122
+ case "/=":
27123
+ return t4.binaryExpression("/", current, right);
27124
+ case "%=":
27125
+ return t4.binaryExpression("%", current, right);
27126
+ case "**=":
27127
+ return t4.binaryExpression("**", current, right);
27128
+ case "<<=":
27129
+ return t4.binaryExpression("<<", current, right);
27130
+ case ">>=":
27131
+ return t4.binaryExpression(">>", current, right);
27132
+ case ">>>=":
27133
+ return t4.binaryExpression(">>>", current, right);
27134
+ case "|=":
27135
+ return t4.binaryExpression("|", current, right);
27136
+ case "^=":
27137
+ return t4.binaryExpression("^", current, right);
27138
+ case "&=":
27139
+ return t4.binaryExpression("&", current, right);
27140
+ case "&&=":
27141
+ return t4.logicalExpression("&&", current, right);
27142
+ case "||=":
27143
+ return t4.logicalExpression("||", current, right);
27144
+ case "??=":
27145
+ return t4.logicalExpression("??", current, right);
27146
+ default:
27147
+ return right;
27148
+ }
27149
+ };
27150
+ const buildStaticSignalKeyTest = (keyRef, keys) => {
27151
+ if (keys.length === 0) return null;
27152
+ let test = null;
27153
+ for (const key of keys) {
27154
+ const literal = typeof key === "number" ? t4.numericLiteral(key) : t4.stringLiteral(String(key));
27155
+ const eq = t4.binaryExpression("===", t4.cloneNode(keyRef, true), literal);
27156
+ test = test ? t4.logicalExpression("||", test, eq) : eq;
27157
+ }
27158
+ return test;
27159
+ };
27160
+ const lowerComputedHookSignalAssignment = (objectName, keyExpr, signalKeys, operator, rightExpr) => {
27161
+ const keyTestKeys = signalKeys.filter(
27162
+ (key) => typeof key === "number" && Number.isFinite(key) || typeof key === "string"
27163
+ );
27164
+ if (keyTestKeys.length === 0) return null;
27165
+ const keyId = genTemp3(ctx, "key");
27166
+ const keyRef = t4.identifier(keyId.name);
27167
+ const memberForAccessor = t4.memberExpression(
27168
+ t4.identifier(objectName),
27169
+ t4.identifier(keyId.name),
27170
+ true
27171
+ );
27172
+ const current = t4.callExpression(t4.cloneNode(memberForAccessor, true), []);
27173
+ const right = lowerExpression2(rightExpr, ctx);
27174
+ const signalWrite = lowerTrackedWriteCall(
27175
+ memberForAccessor,
27176
+ buildTrackedAssignmentNext(operator, current, t4.cloneNode(right, true))
27177
+ );
27178
+ const fallback = t4.assignmentExpression(
27179
+ operator,
27180
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27181
+ right
27182
+ );
27183
+ const keyTest = buildStaticSignalKeyTest(keyRef, keyTestKeys);
27184
+ if (!keyTest) return null;
27185
+ return t4.callExpression(
27186
+ t4.arrowFunctionExpression(
27187
+ [t4.cloneNode(keyId, true)],
27188
+ t4.conditionalExpression(keyTest, signalWrite, fallback)
27189
+ ),
27190
+ [lowerExpression2(keyExpr, ctx)]
27191
+ );
27192
+ };
27193
+ const lowerComputedHookSignalUpdate = (objectName, keyExpr, signalKeys, operator, prefix) => {
27194
+ const keyTestKeys = signalKeys.filter(
27195
+ (key) => typeof key === "number" && Number.isFinite(key) || typeof key === "string"
27196
+ );
27197
+ if (keyTestKeys.length === 0) return null;
27198
+ const keyId = genTemp3(ctx, "key");
27199
+ const keyRef = t4.identifier(keyId.name);
27200
+ const signalUpdate = lowerTrackedUpdateCall(
27201
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27202
+ operator,
27203
+ prefix
27204
+ );
27205
+ const fallback = t4.updateExpression(
27206
+ operator,
27207
+ t4.memberExpression(t4.identifier(objectName), t4.identifier(keyId.name), true),
27208
+ prefix
27209
+ );
27210
+ const keyTest = buildStaticSignalKeyTest(keyRef, keyTestKeys);
27211
+ if (!keyTest) return null;
27212
+ return t4.callExpression(
27213
+ t4.arrowFunctionExpression(
27214
+ [t4.cloneNode(keyId, true)],
27215
+ t4.conditionalExpression(keyTest, signalUpdate, fallback)
27216
+ ),
27217
+ [lowerExpression2(keyExpr, ctx)]
27218
+ );
27219
+ };
27220
+ const lowerTrackedUpdateCall = (callee, operator, prefix) => {
27221
+ const op = operator === "++" ? "+" : "-";
27222
+ const delta = t4.numericLiteral(1);
27223
+ const current = t4.callExpression(t4.cloneNode(callee, true), []);
27224
+ if (!valueUsed) {
27225
+ return t4.callExpression(t4.cloneNode(callee, true), [t4.binaryExpression(op, current, delta)]);
27226
+ }
27227
+ const prevId = genTemp3(ctx, "prev");
27228
+ const prevForSet = t4.identifier(prevId.name);
27229
+ const prevForResult = t4.identifier(prevId.name);
27230
+ return t4.callExpression(
27231
+ t4.arrowFunctionExpression(
27232
+ [t4.cloneNode(prevId, true)],
27233
+ t4.sequenceExpression([
27234
+ t4.callExpression(t4.cloneNode(callee, true), [t4.binaryExpression(op, prevForSet, delta)]),
27235
+ prefix ? t4.binaryExpression(op, prevForResult, t4.numericLiteral(1)) : prevForResult
27236
+ ])
27237
+ ),
27238
+ [current]
27239
+ );
27240
+ };
26734
27241
  const lowerBlocksToStatements = (blocks) => {
26735
27242
  const stmts = [];
26736
27243
  for (const block of blocks) {
@@ -26900,6 +27407,24 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
26900
27407
  expr.arguments,
26901
27408
  (arg) => arg.kind === "ArrowFunction" || arg.kind === "FunctionExpression" ? withNonReactiveScope(ctx, () => lowerExpression2(arg, ctx)) : lowerExpression2(arg, ctx)
26902
27409
  );
27410
+ const includeDevtools = ctx.options?.dev !== false;
27411
+ if (includeDevtools && expr.loc) {
27412
+ const source = `${ctx.options?.filename ?? ""}:${expr.loc.start.line}:${expr.loc.start.column}`;
27413
+ const sourceProp = t4.objectProperty(
27414
+ t4.identifier("devToolsSource"),
27415
+ t4.stringLiteral(source)
27416
+ );
27417
+ if (args.length === 1) {
27418
+ args.push(t4.objectExpression([sourceProp]));
27419
+ } else if (args.length > 1 && t4.isObjectExpression(args[1])) {
27420
+ const hasSourceProp = args[1].properties.some(
27421
+ (prop) => t4.isObjectProperty(prop) && t4.isIdentifier(prop.key) && prop.key.name === "devToolsSource"
27422
+ );
27423
+ if (!hasSourceProp) {
27424
+ args[1].properties.push(sourceProp);
27425
+ }
27426
+ }
27427
+ }
26903
27428
  if (ctx.inModule) {
26904
27429
  ctx.helpersUsed.add("effect");
26905
27430
  return t4.callExpression(t4.identifier(RUNTIME_ALIASES.effect), args);
@@ -27207,59 +27732,42 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27207
27732
  expr.left.computed,
27208
27733
  expr.left.optional
27209
27734
  );
27210
- const current = t4.callExpression(member, []);
27735
+ const current = t4.callExpression(t4.cloneNode(member, true), []);
27211
27736
  const right = lowerExpression2(expr.right, ctx);
27212
- let next;
27213
- switch (expr.operator) {
27214
- case "=":
27215
- next = right;
27216
- break;
27217
- case "+=":
27218
- next = t4.binaryExpression("+", current, right);
27219
- break;
27220
- case "-=":
27221
- next = t4.binaryExpression("-", current, right);
27222
- break;
27223
- case "*=":
27224
- next = t4.binaryExpression("*", current, right);
27225
- break;
27226
- case "/=":
27227
- next = t4.binaryExpression("/", current, right);
27228
- break;
27229
- default:
27230
- next = right;
27737
+ const next = buildTrackedAssignmentNext(expr.operator, current, right);
27738
+ return lowerTrackedWriteCall(member, next);
27739
+ }
27740
+ if (expr.left.computed) {
27741
+ const signalKeys = [];
27742
+ if (info?.objectProps) {
27743
+ for (const [key, accessorKind] of info.objectProps.entries()) {
27744
+ if (accessorKind === "signal") signalKeys.push(key);
27745
+ }
27231
27746
  }
27232
- return t4.callExpression(member, [next]);
27747
+ if (info?.arrayProps) {
27748
+ for (const [key, accessorKind] of info.arrayProps.entries()) {
27749
+ if (accessorKind === "signal") signalKeys.push(key);
27750
+ }
27751
+ }
27752
+ const lowered = lowerComputedHookSignalAssignment(
27753
+ deSSAVarName(expr.left.object.name),
27754
+ expr.left.property,
27755
+ signalKeys,
27756
+ expr.operator,
27757
+ expr.right
27758
+ );
27759
+ if (lowered) return lowered;
27233
27760
  }
27234
27761
  }
27235
27762
  }
27236
27763
  if (expr.left.kind === "Identifier") {
27237
27764
  const baseName2 = deSSAVarName(expr.left.name);
27238
27765
  if (ctx.trackedVars.has(baseName2)) {
27239
- const id = t4.identifier(baseName2);
27766
+ const callee = t4.identifier(baseName2);
27240
27767
  const current = t4.callExpression(t4.identifier(baseName2), []);
27241
27768
  const right = lowerExpression2(expr.right, ctx);
27242
- let next;
27243
- switch (expr.operator) {
27244
- case "=":
27245
- next = right;
27246
- break;
27247
- case "+=":
27248
- next = t4.binaryExpression("+", current, right);
27249
- break;
27250
- case "-=":
27251
- next = t4.binaryExpression("-", current, right);
27252
- break;
27253
- case "*=":
27254
- next = t4.binaryExpression("*", current, right);
27255
- break;
27256
- case "/=":
27257
- next = t4.binaryExpression("/", current, right);
27258
- break;
27259
- default:
27260
- next = right;
27261
- }
27262
- return t4.callExpression(id, [next]);
27769
+ const next = buildTrackedAssignmentNext(expr.operator, current, right);
27770
+ return lowerTrackedWriteCall(callee, next);
27263
27771
  }
27264
27772
  }
27265
27773
  return t4.assignmentExpression(
@@ -27291,21 +27799,35 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27291
27799
  expr.argument.computed,
27292
27800
  expr.argument.optional
27293
27801
  );
27294
- const current = t4.callExpression(member, []);
27295
- const delta = t4.numericLiteral(1);
27296
- const next = expr.operator === "++" ? t4.binaryExpression("+", current, delta) : t4.binaryExpression("-", current, delta);
27297
- return t4.callExpression(member, [next]);
27802
+ return lowerTrackedUpdateCall(member, expr.operator, expr.prefix);
27803
+ }
27804
+ if (expr.argument.computed) {
27805
+ const signalKeys = [];
27806
+ if (info?.objectProps) {
27807
+ for (const [key, accessorKind] of info.objectProps.entries()) {
27808
+ if (accessorKind === "signal") signalKeys.push(key);
27809
+ }
27810
+ }
27811
+ if (info?.arrayProps) {
27812
+ for (const [key, accessorKind] of info.arrayProps.entries()) {
27813
+ if (accessorKind === "signal") signalKeys.push(key);
27814
+ }
27815
+ }
27816
+ const lowered = lowerComputedHookSignalUpdate(
27817
+ deSSAVarName(expr.argument.object.name),
27818
+ expr.argument.property,
27819
+ signalKeys,
27820
+ expr.operator,
27821
+ expr.prefix
27822
+ );
27823
+ if (lowered) return lowered;
27298
27824
  }
27299
27825
  }
27300
27826
  }
27301
27827
  if (expr.argument.kind === "Identifier") {
27302
27828
  const baseName2 = deSSAVarName(expr.argument.name);
27303
27829
  if (ctx.trackedVars.has(baseName2)) {
27304
- const id = t4.identifier(baseName2);
27305
- const current = t4.callExpression(t4.identifier(baseName2), []);
27306
- const delta = t4.numericLiteral(1);
27307
- const next = expr.operator === "++" ? t4.binaryExpression("+", current, delta) : t4.binaryExpression("-", current, delta);
27308
- return t4.callExpression(id, [next]);
27830
+ return lowerTrackedUpdateCall(t4.identifier(baseName2), expr.operator, expr.prefix);
27309
27831
  }
27310
27832
  }
27311
27833
  return t4.updateExpression(
@@ -27327,7 +27849,11 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
27327
27849
  case "NewExpression":
27328
27850
  return t4.newExpression(lowerExpression2(expr.callee, ctx), lowerCallArguments(expr.arguments));
27329
27851
  case "SequenceExpression":
27330
- return t4.sequenceExpression(expr.expressions.map((e) => lowerExpression2(e, ctx)));
27852
+ return t4.sequenceExpression(
27853
+ expr.expressions.map(
27854
+ (e, index) => lowerExpression2(e, ctx, index === expr.expressions.length - 1 ? valueUsed : false)
27855
+ )
27856
+ );
27331
27857
  case "YieldExpression":
27332
27858
  return t4.yieldExpression(
27333
27859
  expr.argument ? lowerExpression2(expr.argument, ctx) : null,
@@ -27785,16 +28311,17 @@ function lowerIntrinsicElement(jsx, ctx) {
27785
28311
  const hasEventOptions = binding.eventOptions && (binding.eventOptions.capture || binding.eventOptions.passive || binding.eventOptions.once);
27786
28312
  const isDelegated = DelegatedEvents.has(eventName) && !hasEventOptions;
27787
28313
  if (binding.resumable && !hasEventOptions) {
27788
- emitResumableEventBinding(
28314
+ const emitted = emitResumableEventBinding(
27789
28315
  targetId,
27790
28316
  eventName,
27791
28317
  binding.expr,
27792
28318
  statements,
27793
28319
  ctx,
27794
28320
  containingRegion,
27795
- createResumableEventBindingOps()
28321
+ createResumableEventBindingOps(),
28322
+ { explicit: binding.resumableExplicit === true }
27796
28323
  );
27797
- continue;
28324
+ if (emitted) continue;
27798
28325
  }
27799
28326
  const hirDataBinding = isDelegated && binding.expr ? extractDelegatedEventDataFromHIR(binding.expr, ctx) : null;
27800
28327
  if (hirDataBinding) {
@@ -27836,34 +28363,24 @@ function lowerIntrinsicElement(jsx, ctx) {
27836
28363
  const isFn = t4.isArrowFunctionExpression(valueExpr) || t4.isFunctionExpression(valueExpr);
27837
28364
  const ensureHandlerParam = (fn) => {
27838
28365
  if (t4.isArrowFunctionExpression(fn)) {
27839
- if (fn.params.length > 0) return fn;
27840
- return t4.arrowFunctionExpression([eventParam], fn.body, fn.async);
28366
+ return fn;
27841
28367
  }
27842
28368
  if (t4.isFunctionExpression(fn)) {
27843
- if (fn.params.length > 0) return fn;
27844
- return t4.functionExpression(fn.id, [eventParam], fn.body, fn.generator, fn.async);
28369
+ return fn;
27845
28370
  }
27846
28371
  if (t4.isIdentifier(fn) || t4.isMemberExpression(fn)) {
27847
28372
  return fn;
27848
28373
  }
27849
- if (t4.isCallExpression(fn) && fn.arguments.length === 0 && (t4.isIdentifier(fn.callee) || t4.isMemberExpression(fn.callee))) {
27850
- return fn.callee;
27851
- }
27852
28374
  return t4.functionExpression(
27853
28375
  null,
27854
- [eventParam],
27855
- t4.blockStatement([
27856
- t4.returnStatement(
27857
- t4.callExpression(
27858
- t4.memberExpression(fn, t4.identifier("call")),
27859
- [t4.thisExpression(), eventParam]
27860
- )
27861
- )
27862
- ])
28376
+ [],
28377
+ t4.blockStatement([t4.returnStatement(fn)])
27863
28378
  );
27864
28379
  };
27865
28380
  const handlerExpr = !isFn && shouldWrapHandler ? t4.arrowFunctionExpression([], valueExpr) : ensureHandlerParam(valueExpr);
27866
- let dataBinding = isDelegated && !shouldWrapHandler ? extractDelegatedEventData(valueExpr, t4) : null;
28381
+ let dataBinding = isDelegated && !shouldWrapHandler ? extractDelegatedEventData(valueExpr, t4, {
28382
+ isKnownHandlerIdentifier: (name) => ctx.functionVars?.has(deSSAVarName(name)) ?? false
28383
+ }) : null;
27867
28384
  if (dataBinding && t4.isIdentifier(dataBinding.handler)) {
27868
28385
  const handlerName = dataBinding.handler.name;
27869
28386
  if (ctx.signalVars?.has(handlerName) || ctx.memoVars?.has(handlerName) || ctx.aliasVars?.has(handlerName) || ctx.storeVars?.has(handlerName) || ctx.trackedVars.has(handlerName)) {
@@ -27875,8 +28392,6 @@ function lowerIntrinsicElement(jsx, ctx) {
27875
28392
  let handlerName = null;
27876
28393
  if (t4.isIdentifier(valueExpr)) {
27877
28394
  handlerName = valueExpr.name;
27878
- } else if (t4.isCallExpression(valueExpr) && valueExpr.arguments.length === 0 && t4.isIdentifier(valueExpr.callee)) {
27879
- handlerName = valueExpr.callee.name;
27880
28395
  }
27881
28396
  const handlerForCall = handlerName ? t4.identifier(handlerName) : t4.cloneNode(valueExpr, true);
27882
28397
  const finalHandler = !isFn && shouldWrapHandler ? t4.functionExpression(
@@ -27892,9 +28407,6 @@ function lowerIntrinsicElement(jsx, ctx) {
27892
28407
  ])
27893
28408
  ) : handlerExpr;
27894
28409
  const normalizeHandler = (expr) => {
27895
- if (t4.isCallExpression(expr) && (t4.isIdentifier(expr.callee) || t4.isMemberExpression(expr.callee))) {
27896
- return expr.callee;
27897
- }
27898
28410
  return expr;
27899
28411
  };
27900
28412
  const normalizedDataHandler = dataBinding !== null ? normalizeHandler(
@@ -28568,10 +29080,13 @@ function transformControlFlowReturns(statements, ctx) {
28568
29080
  }
28569
29081
  return false;
28570
29082
  };
28571
- function hasNodeMatch(nodes, predicate) {
29083
+ function hasNodeMatch(nodes, predicate, options) {
28572
29084
  let found = false;
28573
- const visit = (node) => {
29085
+ const visit = (node, isRoot = false) => {
28574
29086
  if (!node || found) return;
29087
+ if (!isRoot && options?.skipNestedFunctions && (t4.isFunctionExpression(node) || t4.isArrowFunctionExpression(node) || t4.isFunctionDeclaration(node) || t4.isObjectMethod(node) || t4.isClassMethod(node))) {
29088
+ return;
29089
+ }
28575
29090
  if (predicate(node)) {
28576
29091
  found = true;
28577
29092
  return;
@@ -28594,17 +29109,134 @@ function transformControlFlowReturns(statements, ctx) {
28594
29109
  }
28595
29110
  };
28596
29111
  for (const node of nodes) {
28597
- visit(node);
29112
+ visit(node, true);
28598
29113
  if (found) return true;
28599
29114
  }
28600
29115
  return found;
28601
29116
  }
28602
29117
  const containsReturnStatement = (nodes) => hasNodeMatch(nodes, (node) => t4.isReturnStatement(node));
28603
- const containsReactiveAccessorRead = (nodes) => hasNodeMatch(nodes, (node) => {
28604
- if (!t4.isCallExpression(node) && !t4.isOptionalCallExpression(node)) return false;
28605
- const callee = node.callee;
28606
- return t4.isIdentifier(callee) && reactiveAccessorNames.has(callee.name);
28607
- });
29118
+ const getMemberRootIdentifier = (expr) => {
29119
+ let current = expr.object;
29120
+ while (t4.isMemberExpression(current) || t4.isOptionalMemberExpression(current)) {
29121
+ current = current.object;
29122
+ }
29123
+ return t4.isIdentifier(current) ? current : null;
29124
+ };
29125
+ const containsReactiveAccessorRead = (nodes, options) => hasNodeMatch(
29126
+ nodes,
29127
+ (node) => {
29128
+ if (t4.isCallExpression(node) || t4.isOptionalCallExpression(node)) {
29129
+ const callee = node.callee;
29130
+ return t4.isIdentifier(callee) && reactiveAccessorNames.has(callee.name);
29131
+ }
29132
+ if (t4.isMemberExpression(node) || t4.isOptionalMemberExpression(node)) {
29133
+ const root = getMemberRootIdentifier(node);
29134
+ return !!(root && reactiveAccessorNames.has(root.name));
29135
+ }
29136
+ return false;
29137
+ },
29138
+ options
29139
+ );
29140
+ const containsReactiveControlFlowRead = (nodes) => hasNodeMatch(
29141
+ nodes,
29142
+ (node) => {
29143
+ if (t4.isIfStatement(node)) {
29144
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29145
+ }
29146
+ if (t4.isSwitchStatement(node)) {
29147
+ return containsReactiveAccessorRead([node.discriminant], { skipNestedFunctions: true });
29148
+ }
29149
+ if (t4.isConditionalExpression(node)) {
29150
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29151
+ }
29152
+ if (t4.isWhileStatement(node) || t4.isDoWhileStatement(node)) {
29153
+ return containsReactiveAccessorRead([node.test], { skipNestedFunctions: true });
29154
+ }
29155
+ if (t4.isForStatement(node)) {
29156
+ const parts = [];
29157
+ if (node.init) parts.push(node.init);
29158
+ if (node.test) parts.push(node.test);
29159
+ if (node.update) parts.push(node.update);
29160
+ return parts.length > 0 && containsReactiveAccessorRead(parts, { skipNestedFunctions: true });
29161
+ }
29162
+ if (t4.isForOfStatement(node) || t4.isForInStatement(node)) {
29163
+ return containsReactiveAccessorRead([node.right], { skipNestedFunctions: true });
29164
+ }
29165
+ return false;
29166
+ },
29167
+ { skipNestedFunctions: true }
29168
+ );
29169
+ const hasRiskyBranchControlFlow = (stmts) => {
29170
+ if (stmts.length === 0) return false;
29171
+ return containsReactiveControlFlowRead(stmts);
29172
+ };
29173
+ const isJSXLikeNode = (node) => !!node && (t4.isJSXElement(node) || t4.isJSXFragment(node));
29174
+ const isStoreSourceExpression = (expr) => {
29175
+ if (t4.isIdentifier(expr)) {
29176
+ return !!ctx.storeVars?.has(expr.name);
29177
+ }
29178
+ if (t4.isMemberExpression(expr) || t4.isOptionalMemberExpression(expr)) {
29179
+ const root = getMemberRootIdentifier(expr);
29180
+ return !!(root && ctx.storeVars?.has(root.name));
29181
+ }
29182
+ return false;
29183
+ };
29184
+ const hasRiskyStoreDestructureRead = (stmt) => {
29185
+ if (t4.isVariableDeclaration(stmt)) {
29186
+ for (const decl of stmt.declarations) {
29187
+ if (!decl.init) continue;
29188
+ const hasPattern = t4.isObjectPattern(decl.id) || t4.isArrayPattern(decl.id);
29189
+ if (!hasPattern) continue;
29190
+ if (isStoreSourceExpression(decl.init)) {
29191
+ return true;
29192
+ }
29193
+ }
29194
+ return false;
29195
+ }
29196
+ if (t4.isExpressionStatement(stmt) && t4.isAssignmentExpression(stmt.expression)) {
29197
+ const assignment = stmt.expression;
29198
+ const isPatternLhs = t4.isObjectPattern(assignment.left) || t4.isArrayPattern(assignment.left);
29199
+ if (!isPatternLhs) return false;
29200
+ return isStoreSourceExpression(assignment.right);
29201
+ }
29202
+ return false;
29203
+ };
29204
+ const hasRiskyBranchPreludeReads = (stmts) => {
29205
+ for (const stmt of stmts) {
29206
+ if (hasRiskyStoreDestructureRead(stmt)) {
29207
+ return true;
29208
+ }
29209
+ if (t4.isReturnStatement(stmt)) {
29210
+ const arg = stmt.argument;
29211
+ if (!arg || isJSXLikeNode(arg)) continue;
29212
+ if (containsReactiveAccessorRead([arg], { skipNestedFunctions: true })) {
29213
+ return true;
29214
+ }
29215
+ continue;
29216
+ }
29217
+ if (containsReactiveAccessorRead([stmt], { skipNestedFunctions: true })) {
29218
+ return true;
29219
+ }
29220
+ }
29221
+ return false;
29222
+ };
29223
+ const hasRiskyImmediateInvocationReads = (stmts) => hasNodeMatch(
29224
+ stmts,
29225
+ (node) => {
29226
+ if (!t4.isCallExpression(node) && !t4.isOptionalCallExpression(node)) return false;
29227
+ const callee = node.callee;
29228
+ if (!t4.isFunctionExpression(callee) && !t4.isArrowFunctionExpression(callee)) {
29229
+ return false;
29230
+ }
29231
+ const bodyNodes = t4.isBlockStatement(callee.body) ? callee.body.body : [callee.body];
29232
+ return containsReactiveAccessorRead(bodyNodes, { skipNestedFunctions: true });
29233
+ },
29234
+ { skipNestedFunctions: true }
29235
+ );
29236
+ const needsTrackedBranchReads = (stmts) => {
29237
+ if (stmts.length === 0) return false;
29238
+ return hasRiskyBranchControlFlow(stmts) || hasRiskyBranchPreludeReads(stmts) || hasRiskyImmediateInvocationReads(stmts);
29239
+ };
28608
29240
  const emitControlFlowFallbackWarning = (node, kind) => {
28609
29241
  const onWarn = ctx.options?.onWarn;
28610
29242
  if (!onWarn) return;
@@ -28673,7 +29305,7 @@ function transformControlFlowReturns(statements, ctx) {
28673
29305
  }
28674
29306
  return found;
28675
29307
  }
28676
- function buildConditionalBindingExpr(testExpr, trueFn, falseFn) {
29308
+ function buildConditionalBindingExpr(testExpr, trueFn, falseFn, options) {
28677
29309
  ctx.helpersUsed.add("conditional");
28678
29310
  ctx.helpersUsed.add("createElement");
28679
29311
  ctx.helpersUsed.add("onDestroy");
@@ -28684,6 +29316,16 @@ function transformControlFlowReturns(statements, ctx) {
28684
29316
  t4.identifier(RUNTIME_ALIASES.createElement),
28685
29317
  falseFn
28686
29318
  ];
29319
+ if (options?.trackBranchReads) {
29320
+ const undefinedExpr = t4.unaryExpression("void", t4.numericLiteral(0));
29321
+ args.push(
29322
+ undefinedExpr,
29323
+ t4.cloneNode(undefinedExpr),
29324
+ t4.objectExpression([
29325
+ t4.objectProperty(t4.identifier("trackBranchReads"), t4.booleanLiteral(true))
29326
+ ])
29327
+ );
29328
+ }
28687
29329
  const bindingCall = t4.callExpression(t4.identifier(RUNTIME_ALIASES.conditional), args);
28688
29330
  return t4.callExpression(
28689
29331
  t4.arrowFunctionExpression(
@@ -28717,7 +29359,13 @@ function transformControlFlowReturns(statements, ctx) {
28717
29359
  const trueFn = buildBranchFunction(consequentStmts);
28718
29360
  const falseFn = alternateStmts ? buildBranchFunction(alternateStmts) : null;
28719
29361
  if (!trueFn || !falseFn) return null;
28720
- return buildConditionalBindingExpr(ifStmt.test, trueFn, falseFn);
29362
+ const shouldTrackBranchReads = needsTrackedBranchReads(consequentStmts) || (alternateStmts ? needsTrackedBranchReads(alternateStmts) : false);
29363
+ return buildConditionalBindingExpr(
29364
+ ifStmt.test,
29365
+ trueFn,
29366
+ falseFn,
29367
+ shouldTrackBranchReads ? { trackBranchReads: true } : void 0
29368
+ );
28721
29369
  }
28722
29370
  function isSupportedSwitchDiscriminant(_expr) {
28723
29371
  return true;
@@ -28810,10 +29458,13 @@ function transformControlFlowReturns(statements, ctx) {
28810
29458
  ),
28811
29459
  []
28812
29460
  );
29461
+ let currentExprNeedsTrackedBranchReads = needsTrackedBranchReads(fallbackStatements);
28813
29462
  for (let i = branches.length - 1; i >= 0; i--) {
28814
29463
  const branch = branches[i];
28815
29464
  const trueFn = buildBranchFunction(branch.statements, { disallowRenderHooks: true });
28816
29465
  if (!trueFn) return null;
29466
+ const trueBranchNeedsTrackedBranchReads = needsTrackedBranchReads(branch.statements);
29467
+ const trackBranchReads = trueBranchNeedsTrackedBranchReads || currentExprNeedsTrackedBranchReads;
28817
29468
  const falseFn = t4.arrowFunctionExpression(
28818
29469
  [],
28819
29470
  t4.blockStatement([t4.returnStatement(currentExpr)])
@@ -28830,7 +29481,13 @@ function transformControlFlowReturns(statements, ctx) {
28830
29481
  (acc, expr) => t4.logicalExpression("||", acc, expr),
28831
29482
  comparisons[0]
28832
29483
  );
28833
- currentExpr = buildConditionalBindingExpr(testExpr, trueFn, falseFn);
29484
+ currentExpr = buildConditionalBindingExpr(
29485
+ testExpr,
29486
+ trueFn,
29487
+ falseFn,
29488
+ trackBranchReads ? { trackBranchReads: true } : void 0
29489
+ );
29490
+ currentExprNeedsTrackedBranchReads = trackBranchReads;
28834
29491
  }
28835
29492
  return t4.callExpression(
28836
29493
  t4.arrowFunctionExpression(
@@ -30200,11 +30857,27 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30200
30857
  const constArrays = /* @__PURE__ */ new Map();
30201
30858
  const cseMap = /* @__PURE__ */ new Map();
30202
30859
  const instructions = [];
30860
+ const deleteByBase = (map, name) => {
30861
+ const base = getSSABaseName(name);
30862
+ for (const key of Array.from(map.keys())) {
30863
+ if (getSSABaseName(key) === base) {
30864
+ map.delete(key);
30865
+ }
30866
+ }
30867
+ };
30203
30868
  const invalidateCSE = (name) => {
30869
+ const base = getSSABaseName(name);
30204
30870
  const toDelete = [];
30205
30871
  for (const [hash, entry] of cseMap.entries()) {
30206
- if (entry.name === name || entry.deps.has(name)) {
30872
+ if (getSSABaseName(entry.name) === base) {
30207
30873
  toDelete.push(hash);
30874
+ continue;
30875
+ }
30876
+ for (const dep of entry.deps) {
30877
+ if (getSSABaseName(dep) === base) {
30878
+ toDelete.push(hash);
30879
+ break;
30880
+ }
30208
30881
  }
30209
30882
  }
30210
30883
  toDelete.forEach((hash) => cseMap.delete(hash));
@@ -30214,22 +30887,22 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30214
30887
  const target = instr.target.name;
30215
30888
  const declKind = instr.declarationKind;
30216
30889
  invalidateCSE(target);
30217
- constants.delete(target);
30218
- constObjects.delete(target);
30219
- constArrays.delete(target);
30890
+ deleteByBase(constants, target);
30891
+ deleteByBase(constObjects, target);
30892
+ deleteByBase(constArrays, target);
30220
30893
  const sideWrites = collectWriteTargets(instr.value);
30221
30894
  for (const name of sideWrites) {
30222
30895
  if (name !== target) {
30223
- constants.delete(name);
30896
+ deleteByBase(constants, name);
30224
30897
  invalidateCSE(name);
30225
30898
  }
30226
- constObjects.delete(name);
30227
- constArrays.delete(name);
30899
+ deleteByBase(constObjects, name);
30900
+ deleteByBase(constArrays, name);
30228
30901
  }
30229
30902
  const memberCalls = collectMemberCallTargets(instr.value);
30230
30903
  for (const name of memberCalls) {
30231
- constObjects.delete(name);
30232
- constArrays.delete(name);
30904
+ deleteByBase(constObjects, name);
30905
+ deleteByBase(constArrays, name);
30233
30906
  }
30234
30907
  const dependsOnReactiveValue = expressionDependsOnReactive(instr.value, reactive);
30235
30908
  let value = dependsOnReactiveValue ? instr.value : foldExpressionWithConstants(instr.value, constants, options, constObjects, constArrays);
@@ -30269,15 +30942,15 @@ function optimizeReactiveBlock(block, reactive, purity, options) {
30269
30942
  if (instr.kind === "Expression") {
30270
30943
  const writes = collectWriteTargets(instr.value);
30271
30944
  for (const name of writes) {
30272
- constants.delete(name);
30945
+ deleteByBase(constants, name);
30273
30946
  invalidateCSE(name);
30274
- constObjects.delete(name);
30275
- constArrays.delete(name);
30947
+ deleteByBase(constObjects, name);
30948
+ deleteByBase(constArrays, name);
30276
30949
  }
30277
30950
  const memberCalls = collectMemberCallTargets(instr.value);
30278
30951
  for (const name of memberCalls) {
30279
- constObjects.delete(name);
30280
- constArrays.delete(name);
30952
+ deleteByBase(constObjects, name);
30953
+ deleteByBase(constArrays, name);
30281
30954
  }
30282
30955
  const dependsOnReactiveValue = expressionDependsOnReactive(instr.value, reactive);
30283
30956
  const value = dependsOnReactiveValue ? instr.value : foldExpressionWithConstants(instr.value, constants, options, constObjects, constArrays);
@@ -30927,6 +31600,8 @@ function collectWriteTargets(expr) {
30927
31600
  } else if (left.kind === "MemberExpression" || left.kind === "OptionalMemberExpression") {
30928
31601
  const base = getMemberBaseIdentifier(left);
30929
31602
  if (base) writes.add(base.name);
31603
+ visit(left.object);
31604
+ if (left.computed) visit(left.property);
30930
31605
  } else {
30931
31606
  visit(left);
30932
31607
  }
@@ -30943,8 +31618,10 @@ function collectWriteTargets(expr) {
30943
31618
  const base = getMemberBaseIdentifier(arg);
30944
31619
  if (base) {
30945
31620
  writes.add(base.name);
30946
- return;
30947
31621
  }
31622
+ visit(arg.object);
31623
+ if (arg.computed) visit(arg.property);
31624
+ return;
30948
31625
  }
30949
31626
  visit(arg);
30950
31627
  return;
@@ -31250,6 +31927,16 @@ function propagateConstants(fn, options) {
31250
31927
  }
31251
31928
  function computeConstantMap(fn) {
31252
31929
  const constants = /* @__PURE__ */ new Map();
31930
+ const invalidateWrittenName = (writtenName) => {
31931
+ const writtenBase = getSSABaseName(writtenName);
31932
+ let removed = false;
31933
+ for (const constantName of Array.from(constants.keys())) {
31934
+ if (constantName === writtenName || getSSABaseName(constantName) === writtenBase) {
31935
+ removed = constants.delete(constantName) || removed;
31936
+ }
31937
+ }
31938
+ return removed;
31939
+ };
31253
31940
  let changed = true;
31254
31941
  let iterations = 0;
31255
31942
  const maxIterations = 10;
@@ -31258,6 +31945,14 @@ function computeConstantMap(fn) {
31258
31945
  changed = false;
31259
31946
  for (const block of fn.blocks) {
31260
31947
  for (const instr of block.instructions) {
31948
+ if (instr.kind === "Assign" || instr.kind === "Expression") {
31949
+ const writes = collectWriteTargets(instr.value);
31950
+ for (const writtenName of writes) {
31951
+ if (invalidateWrittenName(writtenName)) {
31952
+ changed = true;
31953
+ }
31954
+ }
31955
+ }
31261
31956
  if (instr.kind === "Assign") {
31262
31957
  const value = evaluateConstant(instr.value, constants);
31263
31958
  if (value !== UNKNOWN_CONST) {
@@ -33179,8 +33874,52 @@ function shouldSuppressWarning(suppressions, code, line) {
33179
33874
  });
33180
33875
  }
33181
33876
  var DEFAULT_ERROR_WARNING_CODES = /* @__PURE__ */ new Set(["FICT-R004"]);
33877
+ var STRICT_REACTIVITY_WARNING_CODES = /* @__PURE__ */ new Set(["FICT-R003", "FICT-R006"]);
33878
+ var STRICT_GUARANTEE_WARNING_CODES = /* @__PURE__ */ new Set([
33879
+ "FICT-P001",
33880
+ "FICT-P002",
33881
+ "FICT-P003",
33882
+ "FICT-P004",
33883
+ "FICT-P005",
33884
+ "FICT-J003",
33885
+ "FICT-S002",
33886
+ "FICT-R001",
33887
+ "FICT-R002",
33888
+ "FICT-R003",
33889
+ "FICT-R006"
33890
+ ]);
33891
+ function readBooleanEnv(name) {
33892
+ const raw = process.env[name];
33893
+ if (!raw) return void 0;
33894
+ const normalized = raw.trim().toLowerCase();
33895
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
33896
+ return true;
33897
+ }
33898
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
33899
+ return false;
33900
+ }
33901
+ return void 0;
33902
+ }
33903
+ function validateStrictGuaranteeConfig(options, suppressions) {
33904
+ if (!options.strictGuarantee) return;
33905
+ if (suppressions.length > 0) {
33906
+ throw new Error(
33907
+ "strictGuarantee does not allow fict-ignore suppression comments. Remove suppressions to keep fail-closed guarantees."
33908
+ );
33909
+ }
33910
+ if (!options.warningLevels) return;
33911
+ for (const [code, level] of Object.entries(options.warningLevels)) {
33912
+ if (!STRICT_GUARANTEE_WARNING_CODES.has(code)) continue;
33913
+ if (level === "error") continue;
33914
+ throw new Error(
33915
+ `strictGuarantee does not allow downgrading ${code} to "${level}". Remove this warningLevels override.`
33916
+ );
33917
+ }
33918
+ }
33182
33919
  function hasErrorEscalation(options) {
33183
33920
  if (DEFAULT_ERROR_WARNING_CODES.size > 0) return true;
33921
+ if (options.strictGuarantee) return true;
33922
+ if (options.strictReactivity) return true;
33184
33923
  if (options.warningsAsErrors === true) return true;
33185
33924
  if (Array.isArray(options.warningsAsErrors) && options.warningsAsErrors.length > 0) return true;
33186
33925
  if (options.warningLevels) {
@@ -33189,8 +33928,10 @@ function hasErrorEscalation(options) {
33189
33928
  return false;
33190
33929
  }
33191
33930
  function resolveWarningLevel(code, options) {
33931
+ if (options.strictGuarantee && STRICT_GUARANTEE_WARNING_CODES.has(code)) return "error";
33192
33932
  const override = options.warningLevels?.[code];
33193
33933
  if (override) return override;
33934
+ if (options.strictReactivity && STRICT_REACTIVITY_WARNING_CODES.has(code)) return "error";
33194
33935
  if (options.warningsAsErrors === true) return "error";
33195
33936
  if (Array.isArray(options.warningsAsErrors) && options.warningsAsErrors.includes(code)) {
33196
33937
  return "error";
@@ -33204,6 +33945,7 @@ function formatWarningAsError(warning) {
33204
33945
  at ${location}`;
33205
33946
  }
33206
33947
  function createWarningDispatcher(onWarn, suppressions, options, dev) {
33948
+ validateStrictGuaranteeConfig(options, suppressions);
33207
33949
  const hasEscalation = hasErrorEscalation(options);
33208
33950
  if (!dev && !hasEscalation) return () => {
33209
33951
  };
@@ -33309,7 +34051,7 @@ function isDynamicPropertyAccess(node, t4) {
33309
34051
  if (!node.computed) return false;
33310
34052
  return !(t4.isStringLiteral(node.property) || t4.isNumericLiteral(node.property));
33311
34053
  }
33312
- function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, reactiveBindingIds, effectMacroNames, warn, fileName, t4) {
34054
+ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, reactiveBindingIds, stateMacroNames, memoMacroNames, effectMacroNames, warn, fileName, t4) {
33313
34055
  const hasTrackedBinding = (path2, name, tracked) => {
33314
34056
  const binding = path2.scope.getBinding(name);
33315
34057
  return !!(binding && tracked.has(binding.identifier));
@@ -33324,6 +34066,107 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33324
34066
  if (!root) return false;
33325
34067
  return hasTrackedBinding(path2, root.name, reactiveBindingIds);
33326
34068
  };
34069
+ const NON_ESCAPING_CALLBACK_METHODS = /* @__PURE__ */ new Set([
34070
+ "map",
34071
+ "forEach",
34072
+ "filter",
34073
+ "some",
34074
+ "every",
34075
+ "find",
34076
+ "findIndex",
34077
+ "findLast",
34078
+ "findLastIndex",
34079
+ "flatMap",
34080
+ "reduce",
34081
+ "reduceRight",
34082
+ "sort",
34083
+ "toSorted",
34084
+ "then",
34085
+ "catch",
34086
+ "finally"
34087
+ ]);
34088
+ const capturedClosureByBinding = /* @__PURE__ */ new Map();
34089
+ const shouldIgnoreIdentifierReference = (idPath) => {
34090
+ if (idPath.parentPath.isMemberExpression({ property: idPath.node }) && !idPath.parent.computed) {
34091
+ return true;
34092
+ }
34093
+ if (idPath.parentPath.isObjectProperty({ key: idPath.node }) && !idPath.parent.computed && !idPath.parent.shorthand) {
34094
+ return true;
34095
+ }
34096
+ return false;
34097
+ };
34098
+ const collectCapturedReactiveNames = (fnPath) => {
34099
+ const captured = /* @__PURE__ */ new Set();
34100
+ fnPath.traverse({
34101
+ Function(inner) {
34102
+ if (inner === fnPath) return;
34103
+ inner.skip();
34104
+ },
34105
+ Identifier(idPath) {
34106
+ if (shouldIgnoreIdentifierReference(idPath)) return;
34107
+ const name = idPath.node.name;
34108
+ const binding = idPath.scope.getBinding(name);
34109
+ if (!binding) return;
34110
+ if (!reactiveBindingIds.has(binding.identifier)) return;
34111
+ if (binding.scope === idPath.scope || binding.scope === fnPath.scope) return;
34112
+ captured.add(name);
34113
+ }
34114
+ });
34115
+ return captured;
34116
+ };
34117
+ const registerClosureCaptureBinding = (fnPath, captured) => {
34118
+ if (captured.size === 0) return;
34119
+ if (fnPath.isFunctionDeclaration() && fnPath.node.id) {
34120
+ const binding = fnPath.parentPath.scope.getBinding(fnPath.node.id.name);
34121
+ if (binding) {
34122
+ capturedClosureByBinding.set(binding.identifier, captured);
34123
+ }
34124
+ return;
34125
+ }
34126
+ if ((fnPath.isFunctionExpression() || fnPath.isArrowFunctionExpression()) && fnPath.parentPath.isVariableDeclarator()) {
34127
+ const id = fnPath.parentPath.node.id;
34128
+ if (!t4.isIdentifier(id)) return;
34129
+ const binding = fnPath.parentPath.scope.getBinding(id.name);
34130
+ if (binding) {
34131
+ capturedClosureByBinding.set(binding.identifier, captured);
34132
+ }
34133
+ return;
34134
+ }
34135
+ if (fnPath.parentPath.isAssignmentExpression({ right: fnPath.node })) {
34136
+ const left = fnPath.parentPath.node.left;
34137
+ if (!t4.isIdentifier(left)) return;
34138
+ const binding = fnPath.parentPath.scope.getBinding(left.name);
34139
+ if (binding) {
34140
+ capturedClosureByBinding.set(binding.identifier, captured);
34141
+ }
34142
+ }
34143
+ };
34144
+ const collectCapturedForArgument = (argPath) => {
34145
+ if (argPath.isArrowFunctionExpression() || argPath.isFunctionExpression()) {
34146
+ const captured2 = collectCapturedReactiveNames(argPath);
34147
+ return captured2.size > 0 ? captured2 : null;
34148
+ }
34149
+ if (!argPath.isIdentifier()) return null;
34150
+ const binding = argPath.scope.getBinding(argPath.node.name);
34151
+ if (!binding) return null;
34152
+ const captured = capturedClosureByBinding.get(binding.identifier);
34153
+ return captured && captured.size > 0 ? captured : null;
34154
+ };
34155
+ const isNonEscapingCallbackHost = (callee) => {
34156
+ const member = t4.isMemberExpression(callee) || t4.isOptionalMemberExpression(callee) ? callee : null;
34157
+ if (!member || member.computed || !t4.isIdentifier(member.property)) return false;
34158
+ return NON_ESCAPING_CALLBACK_METHODS.has(member.property.name);
34159
+ };
34160
+ const emitClosureCaptureWarning = (node, captured) => {
34161
+ const names = Array.from(captured).sort().join(", ");
34162
+ emitWarning(
34163
+ node,
34164
+ "FICT-R005",
34165
+ `Function captures reactive variable(s): ${names}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
34166
+ warn,
34167
+ fileName
34168
+ );
34169
+ };
33327
34170
  const argumentHasReactive = (argPath) => {
33328
34171
  if (argPath.isSpreadElement()) {
33329
34172
  const inner = argPath.get("argument");
@@ -33346,12 +34189,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33346
34189
  path2.skip();
33347
34190
  },
33348
34191
  Identifier(idPath) {
33349
- if (idPath.parentPath.isMemberExpression({ property: idPath.node }) && !idPath.parent.computed) {
33350
- return;
33351
- }
33352
- if (idPath.parentPath.isObjectProperty({ key: idPath.node }) && !idPath.parent.computed && !idPath.parent.shorthand) {
33353
- return;
33354
- }
34192
+ if (shouldIgnoreIdentifierReference(idPath)) return;
33355
34193
  const binding = idPath.scope.getBinding(idPath.node.name);
33356
34194
  if (binding && reactiveBindingIds.has(binding.identifier)) {
33357
34195
  found = true;
@@ -33432,36 +34270,14 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33432
34270
  }
33433
34271
  },
33434
34272
  Function(path2) {
33435
- const captured = /* @__PURE__ */ new Set();
33436
- path2.traverse(
33437
- {
33438
- Function(inner) {
33439
- if (inner === path2) return;
33440
- inner.skip();
33441
- },
33442
- Identifier(idPath) {
33443
- const name = idPath.node.name;
33444
- const binding = idPath.scope.getBinding(name);
33445
- if (!binding) return;
33446
- if (!reactiveBindingIds.has(binding.identifier)) return;
33447
- if (binding.scope === idPath.scope || binding.scope === path2.scope) return;
33448
- captured.add(name);
33449
- }
33450
- },
33451
- {}
33452
- );
33453
- if (captured.size > 0) {
33454
- emitWarning(
33455
- path2.node,
33456
- "FICT-R005",
33457
- `Function captures reactive variable(s): ${Array.from(captured).join(", ")}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
33458
- warn,
33459
- fileName
33460
- );
33461
- }
34273
+ const captured = collectCapturedReactiveNames(path2);
34274
+ registerClosureCaptureBinding(path2, captured);
33462
34275
  },
33463
34276
  CallExpression(path2) {
33464
- const isEffect = isEffectCall(path2.node, t4, effectMacroNames);
34277
+ const callNode = path2.node;
34278
+ if (isStateCall(callNode, t4, stateMacroNames)) return;
34279
+ if (isMemoCall(callNode, t4, memoMacroNames)) return;
34280
+ const isEffect = isEffectCall(callNode, t4, effectMacroNames);
33465
34281
  if (isEffect) {
33466
34282
  const argPath = path2.get("arguments.0");
33467
34283
  if (argPath?.isFunctionExpression() || argPath?.isArrowFunctionExpression()) {
@@ -33484,7 +34300,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33484
34300
  });
33485
34301
  if (!hasReactiveDependency) {
33486
34302
  emitWarning(
33487
- path2.node,
34303
+ callNode,
33488
34304
  "FICT-E001",
33489
34305
  "Effect has no reactive reads; it will run once. Consider removing $effect or adding dependencies.",
33490
34306
  warn,
@@ -33507,6 +34323,7 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33507
34323
  const isSafe = calleeName && SAFE_FUNCTIONS.has(calleeName);
33508
34324
  if (isSafe) return;
33509
34325
  const argPaths = path2.get("arguments");
34326
+ const nonEscapingCallbackHost = isNonEscapingCallbackHost(callee);
33510
34327
  for (const argPath of argPaths) {
33511
34328
  if (argPath.isIdentifier() && hasTrackedBinding(argPath, argPath.node.name, stateBindingIds)) {
33512
34329
  continue;
@@ -33522,6 +34339,13 @@ function runWarningPass(programPath, stateBindingIds, stateRootBindingIds, react
33522
34339
  break;
33523
34340
  }
33524
34341
  }
34342
+ if (nonEscapingCallbackHost) return;
34343
+ for (const argPath of argPaths) {
34344
+ const captured = collectCapturedForArgument(argPath);
34345
+ if (!captured) continue;
34346
+ emitClosureCaptureWarning(argPath.node, captured);
34347
+ break;
34348
+ }
33525
34349
  },
33526
34350
  OptionalMemberExpression(path2) {
33527
34351
  if (!path2.node.computed) return;
@@ -34386,6 +35210,8 @@ or extract the nested logic into a custom hook (useXxx).`
34386
35210
  stateBindingIds,
34387
35211
  stateRootBindingIds,
34388
35212
  reactiveBindingIds,
35213
+ stateMacroNames,
35214
+ memoMacroNames,
34389
35215
  effectMacroNames,
34390
35216
  warn,
34391
35217
  fileName,
@@ -34430,13 +35256,17 @@ var createFictPlugin = (0, import_helper_plugin_utils.declare)(
34430
35256
  (api, options = {}) => {
34431
35257
  api.assertVersion(7);
34432
35258
  const t4 = api.types;
35259
+ const strictGuaranteeFromEnv = readBooleanEnv("FICT_STRICT_GUARANTEE") === true;
34433
35260
  const normalizedOptions = {
34434
35261
  ...options,
34435
35262
  fineGrainedDom: options.fineGrainedDom ?? true,
35263
+ lazyConditional: options.lazyConditional ?? true,
35264
+ getterCache: options.getterCache ?? true,
34436
35265
  optimize: options.optimize ?? true,
34437
35266
  optimizeLevel: options.optimizeLevel ?? "safe",
34438
35267
  inlineDerivedMemos: options.inlineDerivedMemos ?? true,
34439
35268
  emitModuleMetadata: options.emitModuleMetadata ?? "auto",
35269
+ strictGuarantee: strictGuaranteeFromEnv || options.strictGuarantee !== false,
34440
35270
  dev: options.dev ?? (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test")
34441
35271
  };
34442
35272
  return {