@fictjs/compiler 0.0.3 → 0.0.5

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
@@ -14128,6 +14128,7 @@ var RUNTIME_HELPERS = {
14128
14128
  propsRest: "__fictPropsRest",
14129
14129
  mergeProps: "mergeProps",
14130
14130
  useProp: "useProp",
14131
+ runInScope: "runInScope",
14131
14132
  createElement: "createElement",
14132
14133
  conditional: "createConditional",
14133
14134
  list: "createList",
@@ -14140,6 +14141,7 @@ var RUNTIME_HELPERS = {
14140
14141
  bindClass: "bindClass",
14141
14142
  bindStyle: "bindStyle",
14142
14143
  bindEvent: "bindEvent",
14144
+ callEventHandler: "callEventHandler",
14143
14145
  bindRef: "bindRef",
14144
14146
  toNodeArray: "toNodeArray",
14145
14147
  createKeyedListContainer: "createKeyedListContainer",
@@ -14167,6 +14169,7 @@ var RUNTIME_ALIASES = {
14167
14169
  propsRest: "__fictPropsRest",
14168
14170
  useProp: "useProp",
14169
14171
  mergeProps: "mergeProps",
14172
+ runInScope: "runInScope",
14170
14173
  createElement: "createElement",
14171
14174
  conditional: "createConditional",
14172
14175
  list: "createList",
@@ -14179,6 +14182,7 @@ var RUNTIME_ALIASES = {
14179
14182
  bindClass: "bindClass",
14180
14183
  bindStyle: "bindStyle",
14181
14184
  bindEvent: "bindEvent",
14185
+ callEventHandler: "callEventHandler",
14182
14186
  bindRef: "bindRef",
14183
14187
  toNodeArray: "toNodeArray",
14184
14188
  createKeyedListContainer: "createKeyedListContainer",
@@ -15204,7 +15208,8 @@ function processStatement(stmt, bb, jumpTarget, ctx) {
15204
15208
  push({
15205
15209
  kind: "Assign",
15206
15210
  target: { kind: "Identifier", name: stmt.id.name },
15207
- value: convertExpression(fnExpr)
15211
+ value: convertExpression(fnExpr),
15212
+ declarationKind: "function"
15208
15213
  });
15209
15214
  return bb;
15210
15215
  }
@@ -17693,6 +17698,110 @@ function structurizeTry(ctx, block, term) {
17693
17698
  }
17694
17699
 
17695
17700
  // src/ir/regions.ts
17701
+ var REACTIVE_CREATORS = /* @__PURE__ */ new Set(["createEffect", "createMemo", "createSelector"]);
17702
+ function expressionCreatesReactive(expr) {
17703
+ if (expr.kind === "CallExpression" && expr.callee.kind === "Identifier") {
17704
+ const base = getSSABaseName(expr.callee.name);
17705
+ return REACTIVE_CREATORS.has(base);
17706
+ }
17707
+ return false;
17708
+ }
17709
+ function expressionContainsReactiveCreation(expr) {
17710
+ if (expressionCreatesReactive(expr)) return true;
17711
+ switch (expr.kind) {
17712
+ case "CallExpression":
17713
+ return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
17714
+ case "MemberExpression":
17715
+ return expressionContainsReactiveCreation(expr.object) || expressionContainsReactiveCreation(expr.property);
17716
+ case "BinaryExpression":
17717
+ case "LogicalExpression":
17718
+ return expressionContainsReactiveCreation(expr.left) || expressionContainsReactiveCreation(expr.right);
17719
+ case "UnaryExpression":
17720
+ return expressionContainsReactiveCreation(expr.argument);
17721
+ case "ConditionalExpression":
17722
+ return expressionContainsReactiveCreation(expr.test) || expressionContainsReactiveCreation(expr.consequent) || expressionContainsReactiveCreation(expr.alternate);
17723
+ case "ArrayExpression":
17724
+ return expr.elements.some((el) => el && expressionContainsReactiveCreation(el));
17725
+ case "ObjectExpression":
17726
+ return expr.properties.some(
17727
+ (prop) => prop.kind === "SpreadElement" ? expressionContainsReactiveCreation(prop.argument) : expressionContainsReactiveCreation(prop.value)
17728
+ );
17729
+ case "ArrowFunction":
17730
+ if (expr.isExpression) {
17731
+ return expressionContainsReactiveCreation(expr.body);
17732
+ }
17733
+ return Array.isArray(expr.body) ? expr.body.some(
17734
+ (block) => block.instructions.some((i) => instructionContainsReactiveCreation(i))
17735
+ ) : false;
17736
+ case "FunctionExpression":
17737
+ return expr.body.some(
17738
+ (block) => block.instructions.some((i) => instructionContainsReactiveCreation(i))
17739
+ );
17740
+ case "AssignmentExpression":
17741
+ return expressionContainsReactiveCreation(expr.left) || expressionContainsReactiveCreation(expr.right);
17742
+ case "UpdateExpression":
17743
+ return expressionContainsReactiveCreation(expr.argument);
17744
+ case "TemplateLiteral":
17745
+ return expr.expressions.some((e) => expressionContainsReactiveCreation(e));
17746
+ case "SpreadElement":
17747
+ return expressionContainsReactiveCreation(expr.argument);
17748
+ case "AwaitExpression":
17749
+ return expressionContainsReactiveCreation(expr.argument);
17750
+ case "YieldExpression":
17751
+ return expr.argument ? expressionContainsReactiveCreation(expr.argument) : false;
17752
+ case "NewExpression":
17753
+ return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
17754
+ case "OptionalCallExpression":
17755
+ return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
17756
+ case "JSXElement":
17757
+ return typeof expr.tagName !== "string" && expressionContainsReactiveCreation(expr.tagName) || expr.attributes.some(
17758
+ (attr) => attr.isSpread ? !!attr.spreadExpr && expressionContainsReactiveCreation(attr.spreadExpr) : attr.value ? expressionContainsReactiveCreation(attr.value) : false
17759
+ ) || expr.children.some(
17760
+ (child) => child.kind === "expression" ? expressionContainsReactiveCreation(child.value) : false
17761
+ );
17762
+ default:
17763
+ return false;
17764
+ }
17765
+ }
17766
+ function instructionContainsReactiveCreation(instr) {
17767
+ if (instr.kind === "Assign") {
17768
+ return expressionContainsReactiveCreation(instr.value);
17769
+ }
17770
+ if (instr.kind === "Expression") {
17771
+ return expressionContainsReactiveCreation(instr.value);
17772
+ }
17773
+ return false;
17774
+ }
17775
+ function instructionIsReactiveSetup(instr) {
17776
+ if (instr.kind === "Assign") {
17777
+ return expressionCreatesReactive(instr.value);
17778
+ }
17779
+ if (instr.kind === "Expression") {
17780
+ return expressionCreatesReactive(instr.value);
17781
+ }
17782
+ return false;
17783
+ }
17784
+ function nodeIsPureReactiveScope(node) {
17785
+ let found = false;
17786
+ const visit = (n) => {
17787
+ switch (n.kind) {
17788
+ case "instruction": {
17789
+ const ok = instructionIsReactiveSetup(n.instruction);
17790
+ if (ok && instructionContainsReactiveCreation(n.instruction)) found = true;
17791
+ return ok;
17792
+ }
17793
+ case "sequence":
17794
+ if (n.nodes.length === 0) return false;
17795
+ return n.nodes.every((child) => visit(child));
17796
+ case "block":
17797
+ if (n.statements.length === 0) return false;
17798
+ return n.statements.every((child) => visit(child));
17799
+ default:
17800
+ return false;
17801
+ }
17802
+ };
17803
+ return visit(node) && found;
17804
+ }
17696
17805
  function generateRegions(fn, scopeResult, shapeResult = analyzeObjectShapes(fn)) {
17697
17806
  const regions = [];
17698
17807
  const regionsByBlock = /* @__PURE__ */ new Map();
@@ -18033,14 +18142,67 @@ function lowerNodeWithRegionContext(node, t2, ctx, declaredVars, regionCtx) {
18033
18142
  case "if": {
18034
18143
  const prevConditional = ctx.inConditional ?? 0;
18035
18144
  ctx.inConditional = prevConditional + 1;
18036
- const conseq = t2.blockStatement(
18037
- lowerNodeWithRegionContext(node.consequent, t2, ctx, declaredVars, regionCtx)
18145
+ const conseqStmts = lowerNodeWithRegionContext(
18146
+ node.consequent,
18147
+ t2,
18148
+ ctx,
18149
+ declaredVars,
18150
+ regionCtx
18038
18151
  );
18039
- const alt = node.alternate ? t2.blockStatement(
18040
- lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx)
18041
- ) : null;
18152
+ const altStmts = node.alternate ? lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx) : null;
18042
18153
  ctx.inConditional = prevConditional;
18043
- const ifStmt = t2.ifStatement(lowerExpressionWithDeSSA(node.test, ctx), conseq, alt);
18154
+ const conseqReactiveOnly = nodeIsPureReactiveScope(node.consequent);
18155
+ const altReactiveOnly = node.alternate ? nodeIsPureReactiveScope(node.alternate) : false;
18156
+ const testExpr = lowerExpressionWithDeSSA(node.test, ctx);
18157
+ const unwrapTestExpr = () => {
18158
+ if (t2.isArrowFunctionExpression(testExpr) && testExpr.params.length === 0 && !t2.isBlockStatement(testExpr.body)) {
18159
+ return t2.cloneNode(testExpr.body);
18160
+ }
18161
+ return t2.cloneNode(testExpr);
18162
+ };
18163
+ const createFlagExpr = (negate = false) => {
18164
+ const body = unwrapTestExpr();
18165
+ const bodyExpr = negate ? t2.unaryExpression("!", body) : body;
18166
+ return t2.arrowFunctionExpression([], bodyExpr);
18167
+ };
18168
+ if (conseqReactiveOnly || altReactiveOnly) {
18169
+ const stmts = [];
18170
+ const runInScopeId = t2.identifier(RUNTIME_ALIASES.runInScope);
18171
+ const addScoped = (flagExpr, body) => {
18172
+ ctx.helpersUsed.add("runInScope");
18173
+ stmts.push(
18174
+ t2.expressionStatement(
18175
+ t2.callExpression(runInScopeId, [
18176
+ flagExpr,
18177
+ t2.arrowFunctionExpression([], t2.blockStatement(body))
18178
+ ])
18179
+ )
18180
+ );
18181
+ };
18182
+ if (conseqReactiveOnly) {
18183
+ addScoped(createFlagExpr(false), conseqStmts);
18184
+ }
18185
+ if (altReactiveOnly && altStmts) {
18186
+ addScoped(createFlagExpr(true), altStmts);
18187
+ }
18188
+ const needsFallbackConseq = !conseqReactiveOnly && conseqStmts.length > 0;
18189
+ const needsFallbackAlt = !altReactiveOnly && altStmts && altStmts.length > 0;
18190
+ if (needsFallbackConseq || needsFallbackAlt) {
18191
+ stmts.push(
18192
+ t2.ifStatement(
18193
+ unwrapTestExpr(),
18194
+ needsFallbackConseq ? t2.blockStatement(conseqStmts) : t2.blockStatement([]),
18195
+ needsFallbackAlt && altStmts ? t2.blockStatement(altStmts) : null
18196
+ )
18197
+ );
18198
+ }
18199
+ return stmts;
18200
+ }
18201
+ const ifStmt = t2.ifStatement(
18202
+ testExpr,
18203
+ t2.blockStatement(conseqStmts),
18204
+ altStmts ? t2.blockStatement(altStmts) : null
18205
+ );
18044
18206
  const shouldWrapEffect = ctx.wrapTrackedExpressions !== false && !ctx.inRegionMemo && expressionUsesTracked(node.test, ctx) && !statementHasEarlyExit(ifStmt, t2);
18045
18207
  if (shouldWrapEffect) {
18046
18208
  ctx.helpersUsed.add("useEffect");
@@ -19034,6 +19196,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19034
19196
  if (instr.kind === "Assign") {
19035
19197
  const ssaName = instr.target.name;
19036
19198
  const baseName2 = deSSAVarName(ssaName);
19199
+ const declKindRaw = instr.declarationKind;
19037
19200
  propagateHookResultAlias(baseName2, instr.value, ctx);
19038
19201
  const hookMember = resolveHookMemberValue(instr.value, ctx);
19039
19202
  if (hookMember) {
@@ -19043,9 +19206,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19043
19206
  } else if (hookMember.kind === "memo") {
19044
19207
  ctx.memoVars?.add(baseName2);
19045
19208
  }
19046
- if (instr.declarationKind) {
19209
+ const declKind2 = declKindRaw && declKindRaw !== "function" ? declKindRaw : null;
19210
+ if (declKind2) {
19047
19211
  declaredVars.add(baseName2);
19048
- return t2.variableDeclaration(instr.declarationKind, [
19212
+ return t2.variableDeclaration(declKind2, [
19049
19213
  t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
19050
19214
  ]);
19051
19215
  }
@@ -19058,11 +19222,25 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19058
19222
  t2.assignmentExpression("=", t2.identifier(baseName2), hookMember.member)
19059
19223
  );
19060
19224
  }
19061
- const declKind = instr.declarationKind;
19225
+ const declKind = declKindRaw && declKindRaw !== "function" ? declKindRaw : void 0;
19226
+ const isFunctionDecl = instr.value.kind === "FunctionExpression" && (declKindRaw === "function" || !declKindRaw && instr.value.name === baseName2);
19227
+ if (isFunctionDecl) {
19228
+ const loweredFn = lowerExpressionWithDeSSA(instr.value, ctx);
19229
+ if (t2.isFunctionExpression(loweredFn)) {
19230
+ declaredVars.add(baseName2);
19231
+ return t2.functionDeclaration(
19232
+ t2.identifier(baseName2),
19233
+ loweredFn.params,
19234
+ loweredFn.body,
19235
+ loweredFn.generator ?? false,
19236
+ loweredFn.async ?? false
19237
+ );
19238
+ }
19239
+ }
19062
19240
  const isTracked = ctx.trackedVars.has(baseName2);
19063
19241
  const isSignal = ctx.signalVars?.has(baseName2) ?? false;
19064
19242
  const aliasVars = ctx.aliasVars ?? (ctx.aliasVars = /* @__PURE__ */ new Set());
19065
- const dependsOnTracked2 = expressionUsesTracked(instr.value, ctx);
19243
+ const dependsOnTracked2 = expressionUsesTracked(instr.value, ctx) || (ctx.memoVars?.has(baseName2) ?? false);
19066
19244
  const capturedTracked = ctx.externalTracked && ctx.externalTracked.has(baseName2) && !declaredVars.has(baseName2);
19067
19245
  const isShadowDeclaration = !!declKind && declaredVars.has(baseName2);
19068
19246
  const treatAsTracked = !isShadowDeclaration && isTracked;
@@ -19070,6 +19248,9 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19070
19248
  const isStateCall2 = instr.value.kind === "CallExpression" && instr.value.callee.kind === "Identifier" && instr.value.callee.name === "$state";
19071
19249
  const inRegionMemo = ctx.inRegionMemo ?? false;
19072
19250
  const isFunctionValue = instr.value.kind === "ArrowFunction" || instr.value.kind === "FunctionExpression";
19251
+ const isAccessorReturningCall = instr.value.kind === "CallExpression" && instr.value.callee.kind === "Identifier" && ["$memo", "createMemo", "useProp"].includes(instr.value.callee.name);
19252
+ const isReactiveObjectCall = instr.value.kind === "CallExpression" && instr.value.callee.kind === "Identifier" && ["mergeProps", "prop"].includes(instr.value.callee.name);
19253
+ const isMemoReturningCall = isAccessorReturningCall || isReactiveObjectCall;
19073
19254
  const lowerAssignedValue = (forceAssigned = false) => lowerExpressionWithDeSSA(instr.value, ctx, forceAssigned || isFunctionValue);
19074
19255
  const buildMemoCall = (expr) => {
19075
19256
  const args = [
@@ -19116,7 +19297,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19116
19297
  t2.variableDeclarator(t2.identifier(baseName2), derivedExpr)
19117
19298
  ]);
19118
19299
  }
19119
- ctx.memoVars?.add(baseName2);
19300
+ if (!isReactiveObjectCall) ctx.memoVars?.add(baseName2);
19120
19301
  if (ctx.noMemo) {
19121
19302
  return t2.variableDeclaration(normalizedDecl, [
19122
19303
  t2.variableDeclarator(
@@ -19126,7 +19307,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19126
19307
  ]);
19127
19308
  }
19128
19309
  return t2.variableDeclaration(normalizedDecl, [
19129
- t2.variableDeclarator(t2.identifier(baseName2), buildMemoCall(derivedExpr))
19310
+ t2.variableDeclarator(
19311
+ t2.identifier(baseName2),
19312
+ isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19313
+ )
19130
19314
  ]);
19131
19315
  }
19132
19316
  }
@@ -19137,7 +19321,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19137
19321
  t2.variableDeclarator(t2.identifier(baseName2), derivedExpr)
19138
19322
  ]);
19139
19323
  }
19140
- ctx.memoVars?.add(baseName2);
19324
+ if (!isReactiveObjectCall) ctx.memoVars?.add(baseName2);
19141
19325
  if (ctx.noMemo) {
19142
19326
  return t2.variableDeclaration(normalizedDecl, [
19143
19327
  t2.variableDeclarator(
@@ -19147,7 +19331,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19147
19331
  ]);
19148
19332
  }
19149
19333
  return t2.variableDeclaration(normalizedDecl, [
19150
- t2.variableDeclarator(t2.identifier(baseName2), buildMemoCall(derivedExpr))
19334
+ t2.variableDeclarator(
19335
+ t2.identifier(baseName2),
19336
+ isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19337
+ )
19151
19338
  ]);
19152
19339
  }
19153
19340
  return t2.variableDeclaration(fallbackDecl, [
@@ -19205,7 +19392,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19205
19392
  t2.variableDeclarator(t2.identifier(baseName2), derivedExpr)
19206
19393
  ]);
19207
19394
  }
19208
- ctx.memoVars?.add(baseName2);
19395
+ if (!isReactiveObjectCall) ctx.memoVars?.add(baseName2);
19209
19396
  if (ctx.noMemo) {
19210
19397
  return t2.variableDeclaration("const", [
19211
19398
  t2.variableDeclarator(
@@ -19215,7 +19402,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19215
19402
  ]);
19216
19403
  }
19217
19404
  return t2.variableDeclaration("const", [
19218
- t2.variableDeclarator(t2.identifier(baseName2), buildMemoCall(derivedExpr))
19405
+ t2.variableDeclarator(
19406
+ t2.identifier(baseName2),
19407
+ isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19408
+ )
19219
19409
  ]);
19220
19410
  }
19221
19411
  return t2.variableDeclaration("let", [
@@ -19229,22 +19419,17 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19229
19419
  t2.variableDeclarator(t2.identifier(baseName2), derivedExpr)
19230
19420
  ]);
19231
19421
  }
19232
- if (ctx.memoVars?.has(baseName2)) {
19233
- if (buildMemoCall) {
19234
- console.log("Declaring memo var:", baseName2);
19235
- return t2.variableDeclaration("const", [
19236
- t2.variableDeclarator(t2.identifier(baseName2), buildMemoCall(derivedExpr))
19237
- ]);
19238
- }
19239
- }
19240
- ctx.memoVars?.add(baseName2);
19422
+ if (!isReactiveObjectCall) ctx.memoVars?.add(baseName2);
19241
19423
  if (ctx.noMemo) {
19242
19424
  return t2.variableDeclaration("const", [
19243
19425
  t2.variableDeclarator(t2.identifier(baseName2), t2.arrowFunctionExpression([], derivedExpr))
19244
19426
  ]);
19245
19427
  }
19246
19428
  return t2.variableDeclaration("const", [
19247
- t2.variableDeclarator(t2.identifier(baseName2), buildMemoCall(derivedExpr))
19429
+ t2.variableDeclarator(
19430
+ t2.identifier(baseName2),
19431
+ isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19432
+ )
19248
19433
  ]);
19249
19434
  }
19250
19435
  return t2.variableDeclaration("let", [
@@ -20322,7 +20507,8 @@ function collectExpressionIdentifiersDeep(expr, into, bound = /* @__PURE__ */ ne
20322
20507
  case "CallExpression":
20323
20508
  case "OptionalCallExpression": {
20324
20509
  const isMacroCallee = expr.callee.kind === "Identifier" && (expr.callee.name === "$state" || expr.callee.name === "$effect" || expr.callee.name === "$store");
20325
- if (!isMacroCallee) {
20510
+ const isIIFE = expr.callee.kind === "ArrowFunction" || expr.callee.kind === "FunctionExpression";
20511
+ if (!isMacroCallee && !isIIFE) {
20326
20512
  collectExpressionIdentifiersDeep(expr.callee, into, bound);
20327
20513
  }
20328
20514
  expr.arguments.forEach(
@@ -20363,73 +20549,81 @@ function collectExpressionIdentifiersDeep(expr, into, bound = /* @__PURE__ */ ne
20363
20549
  );
20364
20550
  return;
20365
20551
  case "ArrowFunction": {
20366
- const nextBound = new Set(bound);
20367
- expr.params.forEach((p) => nextBound.add(deSSAVarName(p.name)));
20552
+ const tempSet = /* @__PURE__ */ new Set();
20368
20553
  if (expr.isExpression && expr.body && !Array.isArray(expr.body)) {
20369
- collectExpressionIdentifiersDeep(expr.body, into, nextBound);
20554
+ collectExpressionIdentifiers(expr.body, tempSet);
20370
20555
  } else if (Array.isArray(expr.body)) {
20371
20556
  for (const block of expr.body) {
20372
20557
  for (const instr of block.instructions) {
20373
20558
  if (instr.kind === "Assign") {
20374
- collectExpressionIdentifiersDeep(instr.value, into, nextBound);
20559
+ collectExpressionIdentifiers(instr.value, tempSet);
20375
20560
  } else if (instr.kind === "Expression") {
20376
- collectExpressionIdentifiersDeep(instr.value, into, nextBound);
20561
+ collectExpressionIdentifiers(instr.value, tempSet);
20377
20562
  } else if (instr.kind === "Phi") {
20378
- instr.sources.forEach((src) => addIdentifier(src.id.name));
20563
+ instr.sources.forEach((src) => tempSet.add(deSSAVarName(src.id.name)));
20379
20564
  }
20380
20565
  }
20381
20566
  const term = block.terminator;
20382
20567
  if (term.kind === "Branch") {
20383
- collectExpressionIdentifiersDeep(term.test, into, nextBound);
20568
+ collectExpressionIdentifiers(term.test, tempSet);
20384
20569
  } else if (term.kind === "Switch") {
20385
- collectExpressionIdentifiersDeep(term.discriminant, into, nextBound);
20570
+ collectExpressionIdentifiers(term.discriminant, tempSet);
20386
20571
  term.cases.forEach((c) => {
20387
- if (c.test) collectExpressionIdentifiersDeep(c.test, into, nextBound);
20572
+ if (c.test) collectExpressionIdentifiers(c.test, tempSet);
20388
20573
  });
20389
20574
  } else if (term.kind === "ForOf") {
20390
- collectExpressionIdentifiersDeep(term.iterable, into, nextBound);
20575
+ collectExpressionIdentifiers(term.iterable, tempSet);
20391
20576
  } else if (term.kind === "ForIn") {
20392
- collectExpressionIdentifiersDeep(term.object, into, nextBound);
20577
+ collectExpressionIdentifiers(term.object, tempSet);
20393
20578
  } else if (term.kind === "Return" && term.argument) {
20394
- collectExpressionIdentifiersDeep(term.argument, into, nextBound);
20579
+ collectExpressionIdentifiers(term.argument, tempSet);
20395
20580
  } else if (term.kind === "Throw") {
20396
- collectExpressionIdentifiersDeep(term.argument, into, nextBound);
20581
+ collectExpressionIdentifiers(term.argument, tempSet);
20397
20582
  }
20398
20583
  }
20399
20584
  }
20585
+ const paramNames = new Set(expr.params.map((p) => deSSAVarName(p.name)));
20586
+ for (const name of bound) paramNames.add(name);
20587
+ for (const dep of tempSet) {
20588
+ if (!paramNames.has(dep)) into.add(dep);
20589
+ }
20400
20590
  return;
20401
20591
  }
20402
20592
  case "FunctionExpression": {
20403
- const nextBound = new Set(bound);
20404
- expr.params.forEach((p) => nextBound.add(deSSAVarName(p.name)));
20593
+ const tempSet = /* @__PURE__ */ new Set();
20405
20594
  for (const block of expr.body) {
20406
20595
  for (const instr of block.instructions) {
20407
20596
  if (instr.kind === "Assign") {
20408
- collectExpressionIdentifiersDeep(instr.value, into, nextBound);
20597
+ collectExpressionIdentifiers(instr.value, tempSet);
20409
20598
  } else if (instr.kind === "Expression") {
20410
- collectExpressionIdentifiersDeep(instr.value, into, nextBound);
20599
+ collectExpressionIdentifiers(instr.value, tempSet);
20411
20600
  } else if (instr.kind === "Phi") {
20412
- instr.sources.forEach((src) => addIdentifier(src.id.name));
20601
+ instr.sources.forEach((src) => tempSet.add(deSSAVarName(src.id.name)));
20413
20602
  }
20414
20603
  }
20415
20604
  const term = block.terminator;
20416
20605
  if (term.kind === "Branch") {
20417
- collectExpressionIdentifiersDeep(term.test, into, nextBound);
20606
+ collectExpressionIdentifiers(term.test, tempSet);
20418
20607
  } else if (term.kind === "Switch") {
20419
- collectExpressionIdentifiersDeep(term.discriminant, into, nextBound);
20608
+ collectExpressionIdentifiers(term.discriminant, tempSet);
20420
20609
  term.cases.forEach((c) => {
20421
- if (c.test) collectExpressionIdentifiersDeep(c.test, into, nextBound);
20610
+ if (c.test) collectExpressionIdentifiers(c.test, tempSet);
20422
20611
  });
20423
20612
  } else if (term.kind === "ForOf") {
20424
- collectExpressionIdentifiersDeep(term.iterable, into, nextBound);
20613
+ collectExpressionIdentifiers(term.iterable, tempSet);
20425
20614
  } else if (term.kind === "ForIn") {
20426
- collectExpressionIdentifiersDeep(term.object, into, nextBound);
20615
+ collectExpressionIdentifiers(term.object, tempSet);
20427
20616
  } else if (term.kind === "Return" && term.argument) {
20428
- collectExpressionIdentifiersDeep(term.argument, into, nextBound);
20617
+ collectExpressionIdentifiers(term.argument, tempSet);
20429
20618
  } else if (term.kind === "Throw") {
20430
- collectExpressionIdentifiersDeep(term.argument, into, nextBound);
20619
+ collectExpressionIdentifiers(term.argument, tempSet);
20431
20620
  }
20432
20621
  }
20622
+ const paramNames = new Set(expr.params.map((p) => deSSAVarName(p.name)));
20623
+ for (const name of bound) paramNames.add(name);
20624
+ for (const dep of tempSet) {
20625
+ if (!paramNames.has(dep)) into.add(dep);
20626
+ }
20433
20627
  return;
20434
20628
  }
20435
20629
  case "AssignmentExpression":
@@ -20498,6 +20692,13 @@ function getExpressionIdentifiers(expr) {
20498
20692
  }
20499
20693
  return deps;
20500
20694
  }
20695
+ function getExpressionIdentifiersDeep(expr) {
20696
+ const deps = /* @__PURE__ */ new Set();
20697
+ if (expr) {
20698
+ collectExpressionIdentifiersDeep(expr, deps);
20699
+ }
20700
+ return deps;
20701
+ }
20501
20702
  function buildControlDependencyMap(fn) {
20502
20703
  const depsByInstruction = /* @__PURE__ */ new Map();
20503
20704
  let structured;
@@ -20624,7 +20825,7 @@ function computeReactiveAccessors(fn, ctx) {
20624
20825
  for (const instr of block.instructions) {
20625
20826
  if (instr.kind === "Assign") {
20626
20827
  const target = deSSAVarName(instr.target.name);
20627
- const dataDeps = getExpressionIdentifiers(instr.value);
20828
+ const dataDeps = getExpressionIdentifiersDeep(instr.value);
20628
20829
  addDepsToTarget(target, dataDeps, dataDepsByTarget);
20629
20830
  const controlDeps = controlDepsByInstr.get(instr) ?? /* @__PURE__ */ new Set();
20630
20831
  addDepsToTarget(target, controlDeps, controlDepsByTarget);
@@ -20685,7 +20886,7 @@ function computeReactiveAccessors(fn, ctx) {
20685
20886
  if (instr.kind === "Assign") {
20686
20887
  const target = deSSAVarName(instr.target.name);
20687
20888
  if (isFunctionVar(target)) continue;
20688
- const dataDeps = getExpressionIdentifiers(instr.value);
20889
+ const dataDeps = getExpressionIdentifiersDeep(instr.value);
20689
20890
  const controlDepsForInstr = controlDepsByInstr.get(instr) ?? /* @__PURE__ */ new Set();
20690
20891
  const hasDataDep = Array.from(dataDeps).some((dep) => tracked.has(dep));
20691
20892
  const hasControlDep = Array.from(controlDepsForInstr).some((dep) => tracked.has(dep));
@@ -20695,7 +20896,8 @@ function computeReactiveAccessors(fn, ctx) {
20695
20896
  tracked.add(target);
20696
20897
  changed = true;
20697
20898
  }
20698
- if (hasDataDep && !isSignal(target) && !isStore(target)) {
20899
+ const isReactiveObjectCall = instr.value.kind === "CallExpression" && instr.value.callee.kind === "Identifier" && ["mergeProps", "prop"].includes(instr.value.callee.name);
20900
+ if (hasDataDep && !isSignal(target) && !isStore(target) && !isReactiveObjectCall) {
20699
20901
  memo.add(target);
20700
20902
  }
20701
20903
  } else if (instr.kind === "Phi") {
@@ -20783,6 +20985,20 @@ function lowerInstruction(instr, ctx) {
20783
20985
  const { t: t2 } = ctx;
20784
20986
  if (instr.kind === "Assign") {
20785
20987
  const baseName2 = deSSAVarName(instr.target.name);
20988
+ const isFunctionDecl = instr.value.kind === "FunctionExpression" && (instr.declarationKind === "function" || !instr.declarationKind && instr.value.name === baseName2);
20989
+ if (isFunctionDecl) {
20990
+ const loweredFn = lowerExpression(instr.value, ctx);
20991
+ if (t2.isFunctionExpression(loweredFn)) {
20992
+ return t2.functionDeclaration(
20993
+ t2.identifier(baseName2),
20994
+ loweredFn.params,
20995
+ loweredFn.body,
20996
+ loweredFn.generator ?? false,
20997
+ loweredFn.async ?? false
20998
+ );
20999
+ }
21000
+ }
21001
+ const declKind = instr.declarationKind === "function" ? void 0 : instr.declarationKind;
20786
21002
  propagateHookResultAlias(baseName2, instr.value, ctx);
20787
21003
  const hookMember = resolveHookMemberValue(instr.value, ctx);
20788
21004
  if (hookMember) {
@@ -20792,8 +21008,8 @@ function lowerInstruction(instr, ctx) {
20792
21008
  } else if (hookMember.kind === "memo") {
20793
21009
  ctx.memoVars?.add(baseName2);
20794
21010
  }
20795
- if (instr.declarationKind) {
20796
- return t2.variableDeclaration(instr.declarationKind, [
21011
+ if (declKind) {
21012
+ return t2.variableDeclaration(declKind, [
20797
21013
  t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
20798
21014
  ]);
20799
21015
  }
@@ -21192,6 +21408,13 @@ function lowerExpressionImpl(expr, ctx, isAssigned = false) {
21192
21408
  return t2.callExpression(t2.identifier(RUNTIME_ALIASES.mergeProps), args);
21193
21409
  }
21194
21410
  const isIIFE = (expr.callee.kind === "ArrowFunction" || expr.callee.kind === "FunctionExpression") && expr.arguments.length === 0 && expr.callee.params.length === 0;
21411
+ const calleeName = expr.callee.kind === "Identifier" ? deSSAVarName(expr.callee.name) : null;
21412
+ const calleeIsMemoAccessor = !!calleeName && ctx.memoVars?.has(calleeName);
21413
+ const calleeIsSignalLike = !!calleeName && (ctx.signalVars?.has(calleeName) || ctx.storeVars?.has(calleeName));
21414
+ if (calleeIsMemoAccessor && !calleeIsSignalLike && expr.arguments.length > 0) {
21415
+ const loweredArgs = expr.arguments.map((a) => lowerExpression(a, ctx));
21416
+ return t2.callExpression(t2.callExpression(t2.identifier(calleeName), []), loweredArgs);
21417
+ }
21195
21418
  const lowerCallee = () => isIIFE ? withNonReactiveScope(ctx, () => lowerExpression(expr.callee, ctx)) : lowerExpression(expr.callee, ctx);
21196
21419
  return t2.callExpression(
21197
21420
  lowerCallee(),
@@ -21544,11 +21767,12 @@ function lowerExpressionImpl(expr, ctx, isAssigned = false) {
21544
21767
  return t2.identifier("undefined");
21545
21768
  }
21546
21769
  }
21547
- function lowerDomExpression(expr, ctx, region) {
21770
+ function lowerDomExpression(expr, ctx, region, options) {
21548
21771
  let lowered = lowerExpression(expr, ctx);
21549
- if (ctx.t.isMemberExpression(lowered) && ctx.t.isIdentifier(lowered.object) && ctx.hookResultVarMap?.has(deSSAVarName(lowered.object.name))) {
21772
+ const skipHookAccessors = options?.skipHookAccessors ?? false;
21773
+ if (!skipHookAccessors && ctx.t.isMemberExpression(lowered) && ctx.t.isIdentifier(lowered.object) && ctx.hookResultVarMap?.has(deSSAVarName(lowered.object.name))) {
21550
21774
  lowered = ctx.t.callExpression(lowered, []);
21551
- } else if (ctx.t.isIdentifier(lowered)) {
21775
+ } else if (!skipHookAccessors && ctx.t.isIdentifier(lowered)) {
21552
21776
  const hookName = ctx.hookResultVarMap?.get(deSSAVarName(lowered.name));
21553
21777
  if (hookName) {
21554
21778
  const info = getHookReturnInfo(hookName, ctx);
@@ -21557,7 +21781,9 @@ function lowerDomExpression(expr, ctx, region) {
21557
21781
  }
21558
21782
  }
21559
21783
  }
21560
- return applyRegionMetadataToExpression(lowered, ctx, region);
21784
+ return applyRegionMetadataToExpression(lowered, ctx, region, {
21785
+ skipRootOverride: options?.skipRegionRootOverride
21786
+ });
21561
21787
  }
21562
21788
  function lowerJSXChildNonFineGrained(child, ctx) {
21563
21789
  const { t: t2 } = ctx;
@@ -21766,12 +21992,13 @@ function getDependencyPathFromNode(node, t2) {
21766
21992
  }
21767
21993
  return null;
21768
21994
  }
21769
- function applyRegionMetadataToExpression(expr, ctx, regionOverride) {
21995
+ function applyRegionMetadataToExpression(expr, ctx, regionOverride, options) {
21770
21996
  if (ctx.inReturn && ctx.currentFnIsHook) {
21771
21997
  return expr;
21772
21998
  }
21773
21999
  const region = regionOverride ?? ctx.currentRegion;
21774
22000
  if (!region) return expr;
22001
+ const skipRootOverride = options?.skipRootOverride ?? false;
21775
22002
  const metadata = regionInfoToMetadata(region);
21776
22003
  const state = {};
21777
22004
  applyRegionMetadata(state, {
@@ -21824,7 +22051,7 @@ function applyRegionMetadataToExpression(expr, ctx, regionOverride) {
21824
22051
  if (Object.keys(overrides).length === 0) {
21825
22052
  return expr;
21826
22053
  }
21827
- if (ctx.t.isIdentifier(expr)) {
22054
+ if (!skipRootOverride && ctx.t.isIdentifier(expr)) {
21828
22055
  const key = normalizeDependencyKey2(expr.name);
21829
22056
  const direct = overrides[key] ?? overrides[expr.name];
21830
22057
  if (direct) {
@@ -21832,10 +22059,10 @@ function applyRegionMetadataToExpression(expr, ctx, regionOverride) {
21832
22059
  }
21833
22060
  }
21834
22061
  const cloned = ctx.t.cloneNode(expr, true);
21835
- replaceIdentifiersWithOverrides(cloned, overrides, ctx.t);
22062
+ replaceIdentifiersWithOverrides(cloned, overrides, ctx.t, void 0, void 0, skipRootOverride);
21836
22063
  return cloned;
21837
22064
  }
21838
- function replaceIdentifiersWithOverrides(node, overrides, t2, parentKind, parentKey) {
22065
+ function replaceIdentifiersWithOverrides(node, overrides, t2, parentKind, parentKey, skipCurrentNode = false) {
21839
22066
  const isCallTarget = parentKey === "callee" && (parentKind === "CallExpression" || parentKind === "OptionalCallExpression");
21840
22067
  const collectParamNames = (params) => {
21841
22068
  const names = /* @__PURE__ */ new Set();
@@ -21871,7 +22098,7 @@ function replaceIdentifiersWithOverrides(node, overrides, t2, parentKind, parent
21871
22098
  params.forEach((p) => visitPattern(p));
21872
22099
  return names;
21873
22100
  };
21874
- if (t2.isMemberExpression(node) || t2.isOptionalMemberExpression(node)) {
22101
+ if (!skipCurrentNode && (t2.isMemberExpression(node) || t2.isOptionalMemberExpression(node))) {
21875
22102
  const path = getDependencyPathFromNode(node, t2);
21876
22103
  const normalized = path ? normalizeDependencyKey2(path) : null;
21877
22104
  const override = normalized && overrides[normalized] || (path ? overrides[path] : void 0);
@@ -21881,7 +22108,7 @@ function replaceIdentifiersWithOverrides(node, overrides, t2, parentKind, parent
21881
22108
  return;
21882
22109
  }
21883
22110
  }
21884
- if (t2.isIdentifier(node)) {
22111
+ if (!skipCurrentNode && t2.isIdentifier(node)) {
21885
22112
  const key = normalizeDependencyKey2(node.name);
21886
22113
  const override = overrides[key] ?? overrides[node.name];
21887
22114
  if (override && !isCallTarget) {
@@ -21926,7 +22153,8 @@ function replaceIdentifiersWithOverrides(node, overrides, t2, parentKind, parent
21926
22153
  overrides,
21927
22154
  t2,
21928
22155
  node.type,
21929
- key
22156
+ key,
22157
+ false
21930
22158
  );
21931
22159
  }
21932
22160
  }
@@ -22355,7 +22583,10 @@ function lowerIntrinsicElement(jsx, ctx) {
22355
22583
  const shouldWrapHandler = isExpressionReactive(binding.expr, ctx);
22356
22584
  const prevWrapTracked = ctx.wrapTrackedExpressions;
22357
22585
  ctx.wrapTrackedExpressions = false;
22358
- const valueExpr = lowerDomExpression(binding.expr, ctx, containingRegion);
22586
+ const valueExpr = lowerDomExpression(binding.expr, ctx, containingRegion, {
22587
+ skipHookAccessors: true,
22588
+ skipRegionRootOverride: true
22589
+ });
22359
22590
  ctx.wrapTrackedExpressions = prevWrapTracked;
22360
22591
  const eventParam = t2.identifier("_e");
22361
22592
  const isFn = t2.isArrowFunctionExpression(valueExpr) || t2.isFunctionExpression(valueExpr);
@@ -24081,6 +24312,9 @@ function isInsideNestedFunction(path) {
24081
24312
  }
24082
24313
  return false;
24083
24314
  }
24315
+ function isInsideJSX(path) {
24316
+ return !!path.findParent((p) => p.isJSXElement?.() || p.isJSXFragment?.());
24317
+ }
24084
24318
  function emitWarning(node, code, message, options, fileName) {
24085
24319
  if (!options.onWarn) return;
24086
24320
  const loc = node.loc?.start;
@@ -24178,6 +24412,7 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
24178
24412
  const root = getRootIdentifier(expr, t2);
24179
24413
  return !!(root && stateVars.has(root.name));
24180
24414
  };
24415
+ const reactiveNames = /* @__PURE__ */ new Set([...stateVars, ...derivedVars]);
24181
24416
  programPath.traverse({
24182
24417
  AssignmentExpression(path) {
24183
24418
  const { left } = path.node;
@@ -24240,6 +24475,35 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
24240
24475
  );
24241
24476
  }
24242
24477
  },
24478
+ Function(path) {
24479
+ const captured = /* @__PURE__ */ new Set();
24480
+ path.traverse(
24481
+ {
24482
+ Function(inner) {
24483
+ if (inner === path) return;
24484
+ inner.skip();
24485
+ },
24486
+ Identifier(idPath) {
24487
+ const name = idPath.node.name;
24488
+ if (!reactiveNames.has(name)) return;
24489
+ const binding = idPath.scope.getBinding(name);
24490
+ if (!binding) return;
24491
+ if (binding.scope === idPath.scope || binding.scope === path.scope) return;
24492
+ captured.add(name);
24493
+ }
24494
+ },
24495
+ {}
24496
+ );
24497
+ if (captured.size > 0) {
24498
+ emitWarning(
24499
+ path.node,
24500
+ "FICT-R005",
24501
+ `Function captures reactive variable(s): ${Array.from(captured).join(", ")}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
24502
+ options,
24503
+ fileName
24504
+ );
24505
+ }
24506
+ },
24243
24507
  CallExpression(path) {
24244
24508
  if (t2.isIdentifier(path.node.callee, { name: "$effect" })) {
24245
24509
  const argPath = path.get("arguments.0");
@@ -24561,6 +24825,15 @@ function createHIREntrypointVisitor(t2, options) {
24561
24825
  }
24562
24826
  const callee = callPath.node.callee;
24563
24827
  const calleeId = t2.isIdentifier(callee) ? callee.name : null;
24828
+ if (calleeId && (calleeId === "createEffect" || calleeId === "createMemo" || calleeId === "createSelector") && fictImports.has(calleeId) && (isInsideLoop(callPath) || isInsideConditional(callPath)) && !isInsideJSX(callPath)) {
24829
+ emitWarning(
24830
+ callPath.node,
24831
+ "FICT-R004",
24832
+ "Reactive creation inside non-JSX control flow will not auto-dispose; wrap it in createScope/runInScope or move it into JSX-managed regions.",
24833
+ options,
24834
+ fileName
24835
+ );
24836
+ }
24564
24837
  const allowedStateCallees = /* @__PURE__ */ new Set([
24565
24838
  "$effect",
24566
24839
  "$memo",