@fictjs/compiler 0.0.4 → 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.cjs +244 -12
- package/dist/index.js +244 -12
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -14140,6 +14140,7 @@ var RUNTIME_HELPERS = {
|
|
|
14140
14140
|
propsRest: "__fictPropsRest",
|
|
14141
14141
|
mergeProps: "mergeProps",
|
|
14142
14142
|
useProp: "useProp",
|
|
14143
|
+
runInScope: "runInScope",
|
|
14143
14144
|
createElement: "createElement",
|
|
14144
14145
|
conditional: "createConditional",
|
|
14145
14146
|
list: "createList",
|
|
@@ -14180,6 +14181,7 @@ var RUNTIME_ALIASES = {
|
|
|
14180
14181
|
propsRest: "__fictPropsRest",
|
|
14181
14182
|
useProp: "useProp",
|
|
14182
14183
|
mergeProps: "mergeProps",
|
|
14184
|
+
runInScope: "runInScope",
|
|
14183
14185
|
createElement: "createElement",
|
|
14184
14186
|
conditional: "createConditional",
|
|
14185
14187
|
list: "createList",
|
|
@@ -15218,7 +15220,8 @@ function processStatement(stmt, bb, jumpTarget, ctx) {
|
|
|
15218
15220
|
push({
|
|
15219
15221
|
kind: "Assign",
|
|
15220
15222
|
target: { kind: "Identifier", name: stmt.id.name },
|
|
15221
|
-
value: convertExpression(fnExpr)
|
|
15223
|
+
value: convertExpression(fnExpr),
|
|
15224
|
+
declarationKind: "function"
|
|
15222
15225
|
});
|
|
15223
15226
|
return bb;
|
|
15224
15227
|
}
|
|
@@ -17707,6 +17710,110 @@ function structurizeTry(ctx, block, term) {
|
|
|
17707
17710
|
}
|
|
17708
17711
|
|
|
17709
17712
|
// src/ir/regions.ts
|
|
17713
|
+
var REACTIVE_CREATORS = /* @__PURE__ */ new Set(["createEffect", "createMemo", "createSelector"]);
|
|
17714
|
+
function expressionCreatesReactive(expr) {
|
|
17715
|
+
if (expr.kind === "CallExpression" && expr.callee.kind === "Identifier") {
|
|
17716
|
+
const base = getSSABaseName(expr.callee.name);
|
|
17717
|
+
return REACTIVE_CREATORS.has(base);
|
|
17718
|
+
}
|
|
17719
|
+
return false;
|
|
17720
|
+
}
|
|
17721
|
+
function expressionContainsReactiveCreation(expr) {
|
|
17722
|
+
if (expressionCreatesReactive(expr)) return true;
|
|
17723
|
+
switch (expr.kind) {
|
|
17724
|
+
case "CallExpression":
|
|
17725
|
+
return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
|
|
17726
|
+
case "MemberExpression":
|
|
17727
|
+
return expressionContainsReactiveCreation(expr.object) || expressionContainsReactiveCreation(expr.property);
|
|
17728
|
+
case "BinaryExpression":
|
|
17729
|
+
case "LogicalExpression":
|
|
17730
|
+
return expressionContainsReactiveCreation(expr.left) || expressionContainsReactiveCreation(expr.right);
|
|
17731
|
+
case "UnaryExpression":
|
|
17732
|
+
return expressionContainsReactiveCreation(expr.argument);
|
|
17733
|
+
case "ConditionalExpression":
|
|
17734
|
+
return expressionContainsReactiveCreation(expr.test) || expressionContainsReactiveCreation(expr.consequent) || expressionContainsReactiveCreation(expr.alternate);
|
|
17735
|
+
case "ArrayExpression":
|
|
17736
|
+
return expr.elements.some((el) => el && expressionContainsReactiveCreation(el));
|
|
17737
|
+
case "ObjectExpression":
|
|
17738
|
+
return expr.properties.some(
|
|
17739
|
+
(prop) => prop.kind === "SpreadElement" ? expressionContainsReactiveCreation(prop.argument) : expressionContainsReactiveCreation(prop.value)
|
|
17740
|
+
);
|
|
17741
|
+
case "ArrowFunction":
|
|
17742
|
+
if (expr.isExpression) {
|
|
17743
|
+
return expressionContainsReactiveCreation(expr.body);
|
|
17744
|
+
}
|
|
17745
|
+
return Array.isArray(expr.body) ? expr.body.some(
|
|
17746
|
+
(block) => block.instructions.some((i) => instructionContainsReactiveCreation(i))
|
|
17747
|
+
) : false;
|
|
17748
|
+
case "FunctionExpression":
|
|
17749
|
+
return expr.body.some(
|
|
17750
|
+
(block) => block.instructions.some((i) => instructionContainsReactiveCreation(i))
|
|
17751
|
+
);
|
|
17752
|
+
case "AssignmentExpression":
|
|
17753
|
+
return expressionContainsReactiveCreation(expr.left) || expressionContainsReactiveCreation(expr.right);
|
|
17754
|
+
case "UpdateExpression":
|
|
17755
|
+
return expressionContainsReactiveCreation(expr.argument);
|
|
17756
|
+
case "TemplateLiteral":
|
|
17757
|
+
return expr.expressions.some((e) => expressionContainsReactiveCreation(e));
|
|
17758
|
+
case "SpreadElement":
|
|
17759
|
+
return expressionContainsReactiveCreation(expr.argument);
|
|
17760
|
+
case "AwaitExpression":
|
|
17761
|
+
return expressionContainsReactiveCreation(expr.argument);
|
|
17762
|
+
case "YieldExpression":
|
|
17763
|
+
return expr.argument ? expressionContainsReactiveCreation(expr.argument) : false;
|
|
17764
|
+
case "NewExpression":
|
|
17765
|
+
return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
|
|
17766
|
+
case "OptionalCallExpression":
|
|
17767
|
+
return expressionContainsReactiveCreation(expr.callee) || expr.arguments.some((arg) => expressionContainsReactiveCreation(arg));
|
|
17768
|
+
case "JSXElement":
|
|
17769
|
+
return typeof expr.tagName !== "string" && expressionContainsReactiveCreation(expr.tagName) || expr.attributes.some(
|
|
17770
|
+
(attr) => attr.isSpread ? !!attr.spreadExpr && expressionContainsReactiveCreation(attr.spreadExpr) : attr.value ? expressionContainsReactiveCreation(attr.value) : false
|
|
17771
|
+
) || expr.children.some(
|
|
17772
|
+
(child) => child.kind === "expression" ? expressionContainsReactiveCreation(child.value) : false
|
|
17773
|
+
);
|
|
17774
|
+
default:
|
|
17775
|
+
return false;
|
|
17776
|
+
}
|
|
17777
|
+
}
|
|
17778
|
+
function instructionContainsReactiveCreation(instr) {
|
|
17779
|
+
if (instr.kind === "Assign") {
|
|
17780
|
+
return expressionContainsReactiveCreation(instr.value);
|
|
17781
|
+
}
|
|
17782
|
+
if (instr.kind === "Expression") {
|
|
17783
|
+
return expressionContainsReactiveCreation(instr.value);
|
|
17784
|
+
}
|
|
17785
|
+
return false;
|
|
17786
|
+
}
|
|
17787
|
+
function instructionIsReactiveSetup(instr) {
|
|
17788
|
+
if (instr.kind === "Assign") {
|
|
17789
|
+
return expressionCreatesReactive(instr.value);
|
|
17790
|
+
}
|
|
17791
|
+
if (instr.kind === "Expression") {
|
|
17792
|
+
return expressionCreatesReactive(instr.value);
|
|
17793
|
+
}
|
|
17794
|
+
return false;
|
|
17795
|
+
}
|
|
17796
|
+
function nodeIsPureReactiveScope(node) {
|
|
17797
|
+
let found = false;
|
|
17798
|
+
const visit = (n) => {
|
|
17799
|
+
switch (n.kind) {
|
|
17800
|
+
case "instruction": {
|
|
17801
|
+
const ok = instructionIsReactiveSetup(n.instruction);
|
|
17802
|
+
if (ok && instructionContainsReactiveCreation(n.instruction)) found = true;
|
|
17803
|
+
return ok;
|
|
17804
|
+
}
|
|
17805
|
+
case "sequence":
|
|
17806
|
+
if (n.nodes.length === 0) return false;
|
|
17807
|
+
return n.nodes.every((child) => visit(child));
|
|
17808
|
+
case "block":
|
|
17809
|
+
if (n.statements.length === 0) return false;
|
|
17810
|
+
return n.statements.every((child) => visit(child));
|
|
17811
|
+
default:
|
|
17812
|
+
return false;
|
|
17813
|
+
}
|
|
17814
|
+
};
|
|
17815
|
+
return visit(node) && found;
|
|
17816
|
+
}
|
|
17710
17817
|
function generateRegions(fn, scopeResult, shapeResult = analyzeObjectShapes(fn)) {
|
|
17711
17818
|
const regions = [];
|
|
17712
17819
|
const regionsByBlock = /* @__PURE__ */ new Map();
|
|
@@ -18047,14 +18154,67 @@ function lowerNodeWithRegionContext(node, t2, ctx, declaredVars, regionCtx) {
|
|
|
18047
18154
|
case "if": {
|
|
18048
18155
|
const prevConditional = ctx.inConditional ?? 0;
|
|
18049
18156
|
ctx.inConditional = prevConditional + 1;
|
|
18050
|
-
const
|
|
18051
|
-
|
|
18157
|
+
const conseqStmts = lowerNodeWithRegionContext(
|
|
18158
|
+
node.consequent,
|
|
18159
|
+
t2,
|
|
18160
|
+
ctx,
|
|
18161
|
+
declaredVars,
|
|
18162
|
+
regionCtx
|
|
18052
18163
|
);
|
|
18053
|
-
const
|
|
18054
|
-
lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx)
|
|
18055
|
-
) : null;
|
|
18164
|
+
const altStmts = node.alternate ? lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx) : null;
|
|
18056
18165
|
ctx.inConditional = prevConditional;
|
|
18057
|
-
const
|
|
18166
|
+
const conseqReactiveOnly = nodeIsPureReactiveScope(node.consequent);
|
|
18167
|
+
const altReactiveOnly = node.alternate ? nodeIsPureReactiveScope(node.alternate) : false;
|
|
18168
|
+
const testExpr = lowerExpressionWithDeSSA(node.test, ctx);
|
|
18169
|
+
const unwrapTestExpr = () => {
|
|
18170
|
+
if (t2.isArrowFunctionExpression(testExpr) && testExpr.params.length === 0 && !t2.isBlockStatement(testExpr.body)) {
|
|
18171
|
+
return t2.cloneNode(testExpr.body);
|
|
18172
|
+
}
|
|
18173
|
+
return t2.cloneNode(testExpr);
|
|
18174
|
+
};
|
|
18175
|
+
const createFlagExpr = (negate = false) => {
|
|
18176
|
+
const body = unwrapTestExpr();
|
|
18177
|
+
const bodyExpr = negate ? t2.unaryExpression("!", body) : body;
|
|
18178
|
+
return t2.arrowFunctionExpression([], bodyExpr);
|
|
18179
|
+
};
|
|
18180
|
+
if (conseqReactiveOnly || altReactiveOnly) {
|
|
18181
|
+
const stmts = [];
|
|
18182
|
+
const runInScopeId = t2.identifier(RUNTIME_ALIASES.runInScope);
|
|
18183
|
+
const addScoped = (flagExpr, body) => {
|
|
18184
|
+
ctx.helpersUsed.add("runInScope");
|
|
18185
|
+
stmts.push(
|
|
18186
|
+
t2.expressionStatement(
|
|
18187
|
+
t2.callExpression(runInScopeId, [
|
|
18188
|
+
flagExpr,
|
|
18189
|
+
t2.arrowFunctionExpression([], t2.blockStatement(body))
|
|
18190
|
+
])
|
|
18191
|
+
)
|
|
18192
|
+
);
|
|
18193
|
+
};
|
|
18194
|
+
if (conseqReactiveOnly) {
|
|
18195
|
+
addScoped(createFlagExpr(false), conseqStmts);
|
|
18196
|
+
}
|
|
18197
|
+
if (altReactiveOnly && altStmts) {
|
|
18198
|
+
addScoped(createFlagExpr(true), altStmts);
|
|
18199
|
+
}
|
|
18200
|
+
const needsFallbackConseq = !conseqReactiveOnly && conseqStmts.length > 0;
|
|
18201
|
+
const needsFallbackAlt = !altReactiveOnly && altStmts && altStmts.length > 0;
|
|
18202
|
+
if (needsFallbackConseq || needsFallbackAlt) {
|
|
18203
|
+
stmts.push(
|
|
18204
|
+
t2.ifStatement(
|
|
18205
|
+
unwrapTestExpr(),
|
|
18206
|
+
needsFallbackConseq ? t2.blockStatement(conseqStmts) : t2.blockStatement([]),
|
|
18207
|
+
needsFallbackAlt && altStmts ? t2.blockStatement(altStmts) : null
|
|
18208
|
+
)
|
|
18209
|
+
);
|
|
18210
|
+
}
|
|
18211
|
+
return stmts;
|
|
18212
|
+
}
|
|
18213
|
+
const ifStmt = t2.ifStatement(
|
|
18214
|
+
testExpr,
|
|
18215
|
+
t2.blockStatement(conseqStmts),
|
|
18216
|
+
altStmts ? t2.blockStatement(altStmts) : null
|
|
18217
|
+
);
|
|
18058
18218
|
const shouldWrapEffect = ctx.wrapTrackedExpressions !== false && !ctx.inRegionMemo && expressionUsesTracked(node.test, ctx) && !statementHasEarlyExit(ifStmt, t2);
|
|
18059
18219
|
if (shouldWrapEffect) {
|
|
18060
18220
|
ctx.helpersUsed.add("useEffect");
|
|
@@ -19048,6 +19208,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19048
19208
|
if (instr.kind === "Assign") {
|
|
19049
19209
|
const ssaName = instr.target.name;
|
|
19050
19210
|
const baseName2 = deSSAVarName(ssaName);
|
|
19211
|
+
const declKindRaw = instr.declarationKind;
|
|
19051
19212
|
propagateHookResultAlias(baseName2, instr.value, ctx);
|
|
19052
19213
|
const hookMember = resolveHookMemberValue(instr.value, ctx);
|
|
19053
19214
|
if (hookMember) {
|
|
@@ -19057,9 +19218,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19057
19218
|
} else if (hookMember.kind === "memo") {
|
|
19058
19219
|
ctx.memoVars?.add(baseName2);
|
|
19059
19220
|
}
|
|
19060
|
-
|
|
19221
|
+
const declKind2 = declKindRaw && declKindRaw !== "function" ? declKindRaw : null;
|
|
19222
|
+
if (declKind2) {
|
|
19061
19223
|
declaredVars.add(baseName2);
|
|
19062
|
-
return t2.variableDeclaration(
|
|
19224
|
+
return t2.variableDeclaration(declKind2, [
|
|
19063
19225
|
t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
|
|
19064
19226
|
]);
|
|
19065
19227
|
}
|
|
@@ -19072,7 +19234,21 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19072
19234
|
t2.assignmentExpression("=", t2.identifier(baseName2), hookMember.member)
|
|
19073
19235
|
);
|
|
19074
19236
|
}
|
|
19075
|
-
const declKind =
|
|
19237
|
+
const declKind = declKindRaw && declKindRaw !== "function" ? declKindRaw : void 0;
|
|
19238
|
+
const isFunctionDecl = instr.value.kind === "FunctionExpression" && (declKindRaw === "function" || !declKindRaw && instr.value.name === baseName2);
|
|
19239
|
+
if (isFunctionDecl) {
|
|
19240
|
+
const loweredFn = lowerExpressionWithDeSSA(instr.value, ctx);
|
|
19241
|
+
if (t2.isFunctionExpression(loweredFn)) {
|
|
19242
|
+
declaredVars.add(baseName2);
|
|
19243
|
+
return t2.functionDeclaration(
|
|
19244
|
+
t2.identifier(baseName2),
|
|
19245
|
+
loweredFn.params,
|
|
19246
|
+
loweredFn.body,
|
|
19247
|
+
loweredFn.generator ?? false,
|
|
19248
|
+
loweredFn.async ?? false
|
|
19249
|
+
);
|
|
19250
|
+
}
|
|
19251
|
+
}
|
|
19076
19252
|
const isTracked = ctx.trackedVars.has(baseName2);
|
|
19077
19253
|
const isSignal = ctx.signalVars?.has(baseName2) ?? false;
|
|
19078
19254
|
const aliasVars = ctx.aliasVars ?? (ctx.aliasVars = /* @__PURE__ */ new Set());
|
|
@@ -20821,6 +20997,20 @@ function lowerInstruction(instr, ctx) {
|
|
|
20821
20997
|
const { t: t2 } = ctx;
|
|
20822
20998
|
if (instr.kind === "Assign") {
|
|
20823
20999
|
const baseName2 = deSSAVarName(instr.target.name);
|
|
21000
|
+
const isFunctionDecl = instr.value.kind === "FunctionExpression" && (instr.declarationKind === "function" || !instr.declarationKind && instr.value.name === baseName2);
|
|
21001
|
+
if (isFunctionDecl) {
|
|
21002
|
+
const loweredFn = lowerExpression(instr.value, ctx);
|
|
21003
|
+
if (t2.isFunctionExpression(loweredFn)) {
|
|
21004
|
+
return t2.functionDeclaration(
|
|
21005
|
+
t2.identifier(baseName2),
|
|
21006
|
+
loweredFn.params,
|
|
21007
|
+
loweredFn.body,
|
|
21008
|
+
loweredFn.generator ?? false,
|
|
21009
|
+
loweredFn.async ?? false
|
|
21010
|
+
);
|
|
21011
|
+
}
|
|
21012
|
+
}
|
|
21013
|
+
const declKind = instr.declarationKind === "function" ? void 0 : instr.declarationKind;
|
|
20824
21014
|
propagateHookResultAlias(baseName2, instr.value, ctx);
|
|
20825
21015
|
const hookMember = resolveHookMemberValue(instr.value, ctx);
|
|
20826
21016
|
if (hookMember) {
|
|
@@ -20830,8 +21020,8 @@ function lowerInstruction(instr, ctx) {
|
|
|
20830
21020
|
} else if (hookMember.kind === "memo") {
|
|
20831
21021
|
ctx.memoVars?.add(baseName2);
|
|
20832
21022
|
}
|
|
20833
|
-
if (
|
|
20834
|
-
return t2.variableDeclaration(
|
|
21023
|
+
if (declKind) {
|
|
21024
|
+
return t2.variableDeclaration(declKind, [
|
|
20835
21025
|
t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
|
|
20836
21026
|
]);
|
|
20837
21027
|
}
|
|
@@ -24134,6 +24324,9 @@ function isInsideNestedFunction(path) {
|
|
|
24134
24324
|
}
|
|
24135
24325
|
return false;
|
|
24136
24326
|
}
|
|
24327
|
+
function isInsideJSX(path) {
|
|
24328
|
+
return !!path.findParent((p) => p.isJSXElement?.() || p.isJSXFragment?.());
|
|
24329
|
+
}
|
|
24137
24330
|
function emitWarning(node, code, message, options, fileName) {
|
|
24138
24331
|
if (!options.onWarn) return;
|
|
24139
24332
|
const loc = node.loc?.start;
|
|
@@ -24231,6 +24424,7 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
|
|
|
24231
24424
|
const root = getRootIdentifier(expr, t2);
|
|
24232
24425
|
return !!(root && stateVars.has(root.name));
|
|
24233
24426
|
};
|
|
24427
|
+
const reactiveNames = /* @__PURE__ */ new Set([...stateVars, ...derivedVars]);
|
|
24234
24428
|
programPath.traverse({
|
|
24235
24429
|
AssignmentExpression(path) {
|
|
24236
24430
|
const { left } = path.node;
|
|
@@ -24293,6 +24487,35 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
|
|
|
24293
24487
|
);
|
|
24294
24488
|
}
|
|
24295
24489
|
},
|
|
24490
|
+
Function(path) {
|
|
24491
|
+
const captured = /* @__PURE__ */ new Set();
|
|
24492
|
+
path.traverse(
|
|
24493
|
+
{
|
|
24494
|
+
Function(inner) {
|
|
24495
|
+
if (inner === path) return;
|
|
24496
|
+
inner.skip();
|
|
24497
|
+
},
|
|
24498
|
+
Identifier(idPath) {
|
|
24499
|
+
const name = idPath.node.name;
|
|
24500
|
+
if (!reactiveNames.has(name)) return;
|
|
24501
|
+
const binding = idPath.scope.getBinding(name);
|
|
24502
|
+
if (!binding) return;
|
|
24503
|
+
if (binding.scope === idPath.scope || binding.scope === path.scope) return;
|
|
24504
|
+
captured.add(name);
|
|
24505
|
+
}
|
|
24506
|
+
},
|
|
24507
|
+
{}
|
|
24508
|
+
);
|
|
24509
|
+
if (captured.size > 0) {
|
|
24510
|
+
emitWarning(
|
|
24511
|
+
path.node,
|
|
24512
|
+
"FICT-R005",
|
|
24513
|
+
`Function captures reactive variable(s): ${Array.from(captured).join(", ")}. Pass them as parameters or memoize explicitly to avoid hidden dependencies.`,
|
|
24514
|
+
options,
|
|
24515
|
+
fileName
|
|
24516
|
+
);
|
|
24517
|
+
}
|
|
24518
|
+
},
|
|
24296
24519
|
CallExpression(path) {
|
|
24297
24520
|
if (t2.isIdentifier(path.node.callee, { name: "$effect" })) {
|
|
24298
24521
|
const argPath = path.get("arguments.0");
|
|
@@ -24614,6 +24837,15 @@ function createHIREntrypointVisitor(t2, options) {
|
|
|
24614
24837
|
}
|
|
24615
24838
|
const callee = callPath.node.callee;
|
|
24616
24839
|
const calleeId = t2.isIdentifier(callee) ? callee.name : null;
|
|
24840
|
+
if (calleeId && (calleeId === "createEffect" || calleeId === "createMemo" || calleeId === "createSelector") && fictImports.has(calleeId) && (isInsideLoop(callPath) || isInsideConditional(callPath)) && !isInsideJSX(callPath)) {
|
|
24841
|
+
emitWarning(
|
|
24842
|
+
callPath.node,
|
|
24843
|
+
"FICT-R004",
|
|
24844
|
+
"Reactive creation inside non-JSX control flow will not auto-dispose; wrap it in createScope/runInScope or move it into JSX-managed regions.",
|
|
24845
|
+
options,
|
|
24846
|
+
fileName
|
|
24847
|
+
);
|
|
24848
|
+
}
|
|
24617
24849
|
const allowedStateCallees = /* @__PURE__ */ new Set([
|
|
24618
24850
|
"$effect",
|
|
24619
24851
|
"$memo",
|
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",
|
|
@@ -14168,6 +14169,7 @@ var RUNTIME_ALIASES = {
|
|
|
14168
14169
|
propsRest: "__fictPropsRest",
|
|
14169
14170
|
useProp: "useProp",
|
|
14170
14171
|
mergeProps: "mergeProps",
|
|
14172
|
+
runInScope: "runInScope",
|
|
14171
14173
|
createElement: "createElement",
|
|
14172
14174
|
conditional: "createConditional",
|
|
14173
14175
|
list: "createList",
|
|
@@ -15206,7 +15208,8 @@ function processStatement(stmt, bb, jumpTarget, ctx) {
|
|
|
15206
15208
|
push({
|
|
15207
15209
|
kind: "Assign",
|
|
15208
15210
|
target: { kind: "Identifier", name: stmt.id.name },
|
|
15209
|
-
value: convertExpression(fnExpr)
|
|
15211
|
+
value: convertExpression(fnExpr),
|
|
15212
|
+
declarationKind: "function"
|
|
15210
15213
|
});
|
|
15211
15214
|
return bb;
|
|
15212
15215
|
}
|
|
@@ -17695,6 +17698,110 @@ function structurizeTry(ctx, block, term) {
|
|
|
17695
17698
|
}
|
|
17696
17699
|
|
|
17697
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
|
+
}
|
|
17698
17805
|
function generateRegions(fn, scopeResult, shapeResult = analyzeObjectShapes(fn)) {
|
|
17699
17806
|
const regions = [];
|
|
17700
17807
|
const regionsByBlock = /* @__PURE__ */ new Map();
|
|
@@ -18035,14 +18142,67 @@ function lowerNodeWithRegionContext(node, t2, ctx, declaredVars, regionCtx) {
|
|
|
18035
18142
|
case "if": {
|
|
18036
18143
|
const prevConditional = ctx.inConditional ?? 0;
|
|
18037
18144
|
ctx.inConditional = prevConditional + 1;
|
|
18038
|
-
const
|
|
18039
|
-
|
|
18145
|
+
const conseqStmts = lowerNodeWithRegionContext(
|
|
18146
|
+
node.consequent,
|
|
18147
|
+
t2,
|
|
18148
|
+
ctx,
|
|
18149
|
+
declaredVars,
|
|
18150
|
+
regionCtx
|
|
18040
18151
|
);
|
|
18041
|
-
const
|
|
18042
|
-
lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx)
|
|
18043
|
-
) : null;
|
|
18152
|
+
const altStmts = node.alternate ? lowerNodeWithRegionContext(node.alternate, t2, ctx, declaredVars, regionCtx) : null;
|
|
18044
18153
|
ctx.inConditional = prevConditional;
|
|
18045
|
-
const
|
|
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
|
+
);
|
|
18046
18206
|
const shouldWrapEffect = ctx.wrapTrackedExpressions !== false && !ctx.inRegionMemo && expressionUsesTracked(node.test, ctx) && !statementHasEarlyExit(ifStmt, t2);
|
|
18047
18207
|
if (shouldWrapEffect) {
|
|
18048
18208
|
ctx.helpersUsed.add("useEffect");
|
|
@@ -19036,6 +19196,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19036
19196
|
if (instr.kind === "Assign") {
|
|
19037
19197
|
const ssaName = instr.target.name;
|
|
19038
19198
|
const baseName2 = deSSAVarName(ssaName);
|
|
19199
|
+
const declKindRaw = instr.declarationKind;
|
|
19039
19200
|
propagateHookResultAlias(baseName2, instr.value, ctx);
|
|
19040
19201
|
const hookMember = resolveHookMemberValue(instr.value, ctx);
|
|
19041
19202
|
if (hookMember) {
|
|
@@ -19045,9 +19206,10 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19045
19206
|
} else if (hookMember.kind === "memo") {
|
|
19046
19207
|
ctx.memoVars?.add(baseName2);
|
|
19047
19208
|
}
|
|
19048
|
-
|
|
19209
|
+
const declKind2 = declKindRaw && declKindRaw !== "function" ? declKindRaw : null;
|
|
19210
|
+
if (declKind2) {
|
|
19049
19211
|
declaredVars.add(baseName2);
|
|
19050
|
-
return t2.variableDeclaration(
|
|
19212
|
+
return t2.variableDeclaration(declKind2, [
|
|
19051
19213
|
t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
|
|
19052
19214
|
]);
|
|
19053
19215
|
}
|
|
@@ -19060,7 +19222,21 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
|
|
|
19060
19222
|
t2.assignmentExpression("=", t2.identifier(baseName2), hookMember.member)
|
|
19061
19223
|
);
|
|
19062
19224
|
}
|
|
19063
|
-
const declKind =
|
|
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
|
+
}
|
|
19064
19240
|
const isTracked = ctx.trackedVars.has(baseName2);
|
|
19065
19241
|
const isSignal = ctx.signalVars?.has(baseName2) ?? false;
|
|
19066
19242
|
const aliasVars = ctx.aliasVars ?? (ctx.aliasVars = /* @__PURE__ */ new Set());
|
|
@@ -20809,6 +20985,20 @@ function lowerInstruction(instr, ctx) {
|
|
|
20809
20985
|
const { t: t2 } = ctx;
|
|
20810
20986
|
if (instr.kind === "Assign") {
|
|
20811
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;
|
|
20812
21002
|
propagateHookResultAlias(baseName2, instr.value, ctx);
|
|
20813
21003
|
const hookMember = resolveHookMemberValue(instr.value, ctx);
|
|
20814
21004
|
if (hookMember) {
|
|
@@ -20818,8 +21008,8 @@ function lowerInstruction(instr, ctx) {
|
|
|
20818
21008
|
} else if (hookMember.kind === "memo") {
|
|
20819
21009
|
ctx.memoVars?.add(baseName2);
|
|
20820
21010
|
}
|
|
20821
|
-
if (
|
|
20822
|
-
return t2.variableDeclaration(
|
|
21011
|
+
if (declKind) {
|
|
21012
|
+
return t2.variableDeclaration(declKind, [
|
|
20823
21013
|
t2.variableDeclarator(t2.identifier(baseName2), hookMember.member)
|
|
20824
21014
|
]);
|
|
20825
21015
|
}
|
|
@@ -24122,6 +24312,9 @@ function isInsideNestedFunction(path) {
|
|
|
24122
24312
|
}
|
|
24123
24313
|
return false;
|
|
24124
24314
|
}
|
|
24315
|
+
function isInsideJSX(path) {
|
|
24316
|
+
return !!path.findParent((p) => p.isJSXElement?.() || p.isJSXFragment?.());
|
|
24317
|
+
}
|
|
24125
24318
|
function emitWarning(node, code, message, options, fileName) {
|
|
24126
24319
|
if (!options.onWarn) return;
|
|
24127
24320
|
const loc = node.loc?.start;
|
|
@@ -24219,6 +24412,7 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
|
|
|
24219
24412
|
const root = getRootIdentifier(expr, t2);
|
|
24220
24413
|
return !!(root && stateVars.has(root.name));
|
|
24221
24414
|
};
|
|
24415
|
+
const reactiveNames = /* @__PURE__ */ new Set([...stateVars, ...derivedVars]);
|
|
24222
24416
|
programPath.traverse({
|
|
24223
24417
|
AssignmentExpression(path) {
|
|
24224
24418
|
const { left } = path.node;
|
|
@@ -24281,6 +24475,35 @@ function runWarningPass(programPath, stateVars, derivedVars, options, t2) {
|
|
|
24281
24475
|
);
|
|
24282
24476
|
}
|
|
24283
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
|
+
},
|
|
24284
24507
|
CallExpression(path) {
|
|
24285
24508
|
if (t2.isIdentifier(path.node.callee, { name: "$effect" })) {
|
|
24286
24509
|
const argPath = path.get("arguments.0");
|
|
@@ -24602,6 +24825,15 @@ function createHIREntrypointVisitor(t2, options) {
|
|
|
24602
24825
|
}
|
|
24603
24826
|
const callee = callPath.node.callee;
|
|
24604
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
|
+
}
|
|
24605
24837
|
const allowedStateCallees = /* @__PURE__ */ new Set([
|
|
24606
24838
|
"$effect",
|
|
24607
24839
|
"$memo",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fictjs/compiler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Babel plugin for Fict Compiler",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@types/babel__helper-plugin-utils": "^7.10.3",
|
|
39
39
|
"@types/babel__traverse": "^7.28.0",
|
|
40
40
|
"tsup": "^8.5.1",
|
|
41
|
-
"@fictjs/runtime": "0.0.
|
|
41
|
+
"@fictjs/runtime": "0.0.5"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|