@fictjs/compiler 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3059,7 +3059,7 @@ var require_identifier = __commonJS({
3059
3059
  value: true
3060
3060
  });
3061
3061
  exports.isIdentifierChar = isIdentifierChar;
3062
- exports.isIdentifierName = isIdentifierName;
3062
+ exports.isIdentifierName = isIdentifierName2;
3063
3063
  exports.isIdentifierStart = isIdentifierStart;
3064
3064
  var nonASCIIidentifierStartChars = "\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088F\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5C\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDC-\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C8A\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7DC\uA7F1-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC";
3065
3065
  var nonASCIIidentifierChars = "\xB7\u0300-\u036F\u0387\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0897-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u180F-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19D0-\u19DA\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1ABF-\u1ADD\u1AE0-\u1AEB\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u200C\u200D\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\u30FB\uA620-\uA629\uA66F\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F1\uA8FF-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F\uFF65";
@@ -3100,7 +3100,7 @@ var require_identifier = __commonJS({
3100
3100
  }
3101
3101
  return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
3102
3102
  }
3103
- function isIdentifierName(name) {
3103
+ function isIdentifierName2(name) {
3104
3104
  let isFirst = true;
3105
3105
  for (let i = 0; i < name.length; i++) {
3106
3106
  let cp = name.charCodeAt(i);
@@ -17073,6 +17073,37 @@ function clearModuleMetadata(options) {
17073
17073
  }
17074
17074
 
17075
17075
  // src/validation.ts
17076
+ var DiagnosticCode = /* @__PURE__ */ ((DiagnosticCode2) => {
17077
+ DiagnosticCode2["FICT_P001"] = "FICT-P001";
17078
+ DiagnosticCode2["FICT_P002"] = "FICT-P002";
17079
+ DiagnosticCode2["FICT_P003"] = "FICT-P003";
17080
+ DiagnosticCode2["FICT_P004"] = "FICT-P004";
17081
+ DiagnosticCode2["FICT_P005"] = "FICT-P005";
17082
+ DiagnosticCode2["FICT_S001"] = "FICT-S001";
17083
+ DiagnosticCode2["FICT_S002"] = "FICT-S002";
17084
+ DiagnosticCode2["FICT_E001"] = "FICT-E001";
17085
+ DiagnosticCode2["FICT_E002"] = "FICT-E002";
17086
+ DiagnosticCode2["FICT_E003"] = "FICT-E003";
17087
+ DiagnosticCode2["FICT_M001"] = "FICT-M001";
17088
+ DiagnosticCode2["FICT_M002"] = "FICT-M002";
17089
+ DiagnosticCode2["FICT_M003"] = "FICT-M003";
17090
+ DiagnosticCode2["FICT_C001"] = "FICT-C001";
17091
+ DiagnosticCode2["FICT_C002"] = "FICT-C002";
17092
+ DiagnosticCode2["FICT_C003"] = "FICT-C003";
17093
+ DiagnosticCode2["FICT_C004"] = "FICT-C004";
17094
+ DiagnosticCode2["FICT_J001"] = "FICT-J001";
17095
+ DiagnosticCode2["FICT_J002"] = "FICT-J002";
17096
+ DiagnosticCode2["FICT_J003"] = "FICT-J003";
17097
+ DiagnosticCode2["FICT_R001"] = "FICT-R001";
17098
+ DiagnosticCode2["FICT_R002"] = "FICT-R002";
17099
+ DiagnosticCode2["FICT_R003"] = "FICT-R003";
17100
+ DiagnosticCode2["FICT_R004"] = "FICT-R004";
17101
+ DiagnosticCode2["FICT_R005"] = "FICT-R005";
17102
+ DiagnosticCode2["FICT_X001"] = "FICT-X001";
17103
+ DiagnosticCode2["FICT_X002"] = "FICT-X002";
17104
+ DiagnosticCode2["FICT_X003"] = "FICT-X003";
17105
+ return DiagnosticCode2;
17106
+ })(DiagnosticCode || {});
17076
17107
  var DiagnosticMessages = {
17077
17108
  ["FICT-P001" /* FICT_P001 */]: "Props destructuring falls back to non-reactive binding.",
17078
17109
  ["FICT-P002" /* FICT_P002 */]: "Array rest in props destructuring falls back to non-reactive binding.",
@@ -17160,6 +17191,16 @@ function reportDiagnostic(ctx, code, node, context) {
17160
17191
  });
17161
17192
  }
17162
17193
  }
17194
+ function getAllDiagnosticCodes() {
17195
+ return Object.values(DiagnosticCode);
17196
+ }
17197
+ function getDiagnosticInfo(code) {
17198
+ return {
17199
+ code,
17200
+ severity: DiagnosticSeverities[code],
17201
+ message: DiagnosticMessages[code]
17202
+ };
17203
+ }
17163
17204
 
17164
17205
  // src/ir/ssa.ts
17165
17206
  function enterSSA(program) {
@@ -24252,34 +24293,138 @@ function extractKeyFromAttributes(attributes) {
24252
24293
  }
24253
24294
  return void 0;
24254
24295
  }
24296
+ function collectReturnedJSXFromExpression(expression, returned) {
24297
+ if (expression.kind === "JSXElement") {
24298
+ returned.push(expression);
24299
+ return;
24300
+ }
24301
+ if (expression.kind === "ConditionalExpression") {
24302
+ collectReturnedJSXFromExpression(expression.consequent, returned);
24303
+ collectReturnedJSXFromExpression(expression.alternate, returned);
24304
+ return;
24305
+ }
24306
+ if (expression.kind === "LogicalExpression") {
24307
+ collectReturnedJSXFromExpression(expression.left, returned);
24308
+ collectReturnedJSXFromExpression(expression.right, returned);
24309
+ return;
24310
+ }
24311
+ if (expression.kind === "SequenceExpression") {
24312
+ const tail = expression.expressions[expression.expressions.length - 1];
24313
+ if (tail) collectReturnedJSXFromExpression(tail, returned);
24314
+ }
24315
+ }
24316
+ function extractKeyExpressionFromReturnedExpression(expression) {
24317
+ if (expression.kind === "JSXElement") {
24318
+ return extractKeyFromAttributes(expression.attributes);
24319
+ }
24320
+ if (expression.kind === "ConditionalExpression") {
24321
+ const consequentKey = extractKeyExpressionFromReturnedExpression(expression.consequent);
24322
+ const alternateKey = extractKeyExpressionFromReturnedExpression(expression.alternate);
24323
+ if (!consequentKey || !alternateKey) return void 0;
24324
+ return {
24325
+ kind: "ConditionalExpression",
24326
+ test: expression.test,
24327
+ consequent: consequentKey,
24328
+ alternate: alternateKey,
24329
+ loc: expression.loc
24330
+ };
24331
+ }
24332
+ if (expression.kind === "SequenceExpression") {
24333
+ const tail = expression.expressions[expression.expressions.length - 1];
24334
+ return tail ? extractKeyExpressionFromReturnedExpression(tail) : void 0;
24335
+ }
24336
+ return void 0;
24337
+ }
24338
+ function getReturnedKeyExpressionsFromCallback(callback) {
24339
+ const returned = [];
24340
+ if (callback.kind === "ArrowFunction") {
24341
+ if (callback.isExpression && !Array.isArray(callback.body)) {
24342
+ const keyExpr = extractKeyExpressionFromReturnedExpression(callback.body);
24343
+ if (keyExpr) returned.push(keyExpr);
24344
+ return returned;
24345
+ }
24346
+ if (Array.isArray(callback.body)) {
24347
+ for (const block of callback.body) {
24348
+ const term = block.terminator;
24349
+ if (term.kind !== "Return" || !term.argument) continue;
24350
+ const keyExpr = extractKeyExpressionFromReturnedExpression(term.argument);
24351
+ if (keyExpr) returned.push(keyExpr);
24352
+ }
24353
+ }
24354
+ return returned;
24355
+ }
24356
+ if (callback.kind === "FunctionExpression") {
24357
+ for (const block of callback.body ?? []) {
24358
+ const term = block.terminator;
24359
+ if (term.kind !== "Return" || !term.argument) continue;
24360
+ const keyExpr = extractKeyExpressionFromReturnedExpression(term.argument);
24361
+ if (keyExpr) returned.push(keyExpr);
24362
+ }
24363
+ }
24364
+ return returned;
24365
+ }
24255
24366
  function getReturnedJSXFromCallback(callback) {
24367
+ const returned = [];
24256
24368
  if (callback.kind === "ArrowFunction") {
24257
- if (callback.isExpression && !Array.isArray(callback.body) && callback.body.kind === "JSXElement") {
24258
- return callback.body;
24369
+ if (callback.isExpression && !Array.isArray(callback.body)) {
24370
+ collectReturnedJSXFromExpression(callback.body, returned);
24371
+ return returned;
24259
24372
  }
24260
24373
  if (Array.isArray(callback.body)) {
24261
24374
  for (const block of callback.body) {
24262
24375
  const term = block.terminator;
24263
- if (term.kind === "Return" && term.argument?.kind === "JSXElement") {
24264
- return term.argument;
24376
+ if (term.kind === "Return" && term.argument) {
24377
+ collectReturnedJSXFromExpression(term.argument, returned);
24265
24378
  }
24266
24379
  }
24267
24380
  }
24381
+ return returned;
24268
24382
  }
24269
24383
  if (callback.kind === "FunctionExpression") {
24270
24384
  for (const block of callback.body ?? []) {
24271
24385
  const term = block.terminator;
24272
- if (term.kind === "Return" && term.argument?.kind === "JSXElement") {
24273
- return term.argument;
24386
+ if (term.kind === "Return" && term.argument) {
24387
+ collectReturnedJSXFromExpression(term.argument, returned);
24274
24388
  }
24275
24389
  }
24276
24390
  }
24277
- return null;
24391
+ return returned;
24392
+ }
24393
+ function keyExpressionSignature(expression) {
24394
+ try {
24395
+ return JSON.stringify(expression, (key, value) => {
24396
+ if (key === "loc") return void 0;
24397
+ if (typeof value === "bigint") return `__bigint:${value.toString()}`;
24398
+ return value;
24399
+ }) ?? "";
24400
+ } catch {
24401
+ return "";
24402
+ }
24278
24403
  }
24279
24404
  function extractKeyFromMapCallback(callback) {
24280
- const jsx = getReturnedJSXFromCallback(callback);
24281
- if (!jsx) return void 0;
24282
- return extractKeyFromAttributes(jsx.attributes);
24405
+ const returnedKeyExpressions = getReturnedKeyExpressionsFromCallback(callback);
24406
+ if (returnedKeyExpressions.length === 1) {
24407
+ return returnedKeyExpressions[0];
24408
+ }
24409
+ if (returnedKeyExpressions.length > 1) {
24410
+ const [firstKey2, ...restKeys2] = returnedKeyExpressions;
24411
+ const firstSignature2 = keyExpressionSignature(firstKey2);
24412
+ if (firstSignature2 && restKeys2.every((keyExpr) => keyExpressionSignature(keyExpr) === firstSignature2)) {
24413
+ return firstKey2;
24414
+ }
24415
+ }
24416
+ const returned = getReturnedJSXFromCallback(callback);
24417
+ if (returned.length === 0) return void 0;
24418
+ const keyExpressions = returned.map((jsx) => extractKeyFromAttributes(jsx.attributes));
24419
+ if (keyExpressions.some((expr) => !expr)) return void 0;
24420
+ const [firstKey, ...restKeys] = keyExpressions;
24421
+ const firstSignature = keyExpressionSignature(firstKey);
24422
+ if (!firstSignature) return void 0;
24423
+ const allBranchesSameKey = restKeys.every(
24424
+ (keyExpr) => keyExpressionSignature(keyExpr) === firstSignature
24425
+ );
24426
+ if (!allBranchesSameKey) return void 0;
24427
+ return firstKey;
24283
24428
  }
24284
24429
 
24285
24430
  // src/ir/codegen-overrides.ts
@@ -24884,6 +25029,15 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24884
25029
  ctx.inListRender = true;
24885
25030
  let callbackExpr = ops.lowerExpression(mapCallback, ctx);
24886
25031
  ctx.inListRender = prevInListRender;
25032
+ const shouldDeferOptionalCallbackEvaluation = isOptional && !t4.isArrowFunctionExpression(callbackExpr) && !t4.isFunctionExpression(callbackExpr);
25033
+ let deferredCallbackId = null;
25034
+ let deferredCallbackInitId = null;
25035
+ let deferredItemsId = null;
25036
+ if (shouldDeferOptionalCallbackEvaluation) {
25037
+ deferredCallbackId = ops.genTemp(ctx, "mapCb");
25038
+ deferredCallbackInitId = ops.genTemp(ctx, "mapCbReady");
25039
+ deferredItemsId = ops.genTemp(ctx, "mapItems");
25040
+ }
24887
25041
  const capturedKeyParamName = ctx.listKeyParamName;
24888
25042
  ctx.listKeyExpr = prevListKeyExpr;
24889
25043
  ctx.listItemParamName = prevListItemParamName;
@@ -24983,7 +25137,7 @@ function buildListCallExpression(expr, statements, ctx, ops) {
24983
25137
  ],
24984
25138
  keyExprAst
24985
25139
  );
24986
- const hasIndexParam = (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) && callbackExpr.params.length >= 2;
25140
+ const hasIndexParam = shouldDeferOptionalCallbackEvaluation || (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) && callbackExpr.params.length >= 2;
24987
25141
  if (canConstifyKey && (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr))) {
24988
25142
  const newParams = [...callbackExpr.params];
24989
25143
  while (newParams.length < 2) {
@@ -25003,25 +25157,131 @@ function buildListCallExpression(expr, statements, ctx, ops) {
25003
25157
  }
25004
25158
  }
25005
25159
  statements.push(...hoistedStatements);
25160
+ if (shouldDeferOptionalCallbackEvaluation) {
25161
+ statements.push(
25162
+ t4.variableDeclaration("let", [
25163
+ t4.variableDeclarator(t4.cloneNode(deferredCallbackId, true))
25164
+ ]),
25165
+ t4.variableDeclaration("let", [
25166
+ t4.variableDeclarator(t4.cloneNode(deferredCallbackInitId, true), t4.booleanLiteral(false))
25167
+ ])
25168
+ );
25169
+ }
25170
+ const getItemsExpr = shouldDeferOptionalCallbackEvaluation ? t4.arrowFunctionExpression(
25171
+ [],
25172
+ t4.blockStatement([
25173
+ t4.variableDeclaration("const", [
25174
+ t4.variableDeclarator(
25175
+ t4.cloneNode(deferredItemsId, true),
25176
+ t4.cloneNode(arrayExprBase, true)
25177
+ )
25178
+ ]),
25179
+ t4.ifStatement(
25180
+ t4.binaryExpression("==", t4.cloneNode(deferredItemsId, true), t4.nullLiteral()),
25181
+ t4.blockStatement([t4.returnStatement(t4.arrayExpression([]))])
25182
+ ),
25183
+ t4.ifStatement(
25184
+ t4.unaryExpression("!", t4.cloneNode(deferredCallbackInitId, true)),
25185
+ t4.blockStatement([
25186
+ t4.expressionStatement(
25187
+ t4.assignmentExpression(
25188
+ "=",
25189
+ t4.cloneNode(deferredCallbackId, true),
25190
+ t4.cloneNode(callbackExpr, true)
25191
+ )
25192
+ ),
25193
+ t4.expressionStatement(
25194
+ t4.assignmentExpression(
25195
+ "=",
25196
+ t4.cloneNode(deferredCallbackInitId, true),
25197
+ t4.booleanLiteral(true)
25198
+ )
25199
+ )
25200
+ ])
25201
+ ),
25202
+ t4.returnStatement(t4.cloneNode(deferredItemsId, true))
25203
+ ])
25204
+ ) : t4.arrowFunctionExpression([], arrayExpr);
25205
+ const renderExpr = shouldDeferOptionalCallbackEvaluation ? t4.arrowFunctionExpression(
25206
+ [t4.identifier("__item"), t4.identifier("__index"), t4.identifier("__key")],
25207
+ t4.callExpression(t4.cloneNode(deferredCallbackId, true), [
25208
+ t4.identifier("__item"),
25209
+ t4.identifier("__index"),
25210
+ t4.identifier("__key")
25211
+ ])
25212
+ ) : callbackExpr;
25006
25213
  listCall = t4.callExpression(t4.identifier(RUNTIME_ALIASES.keyedList), [
25007
- t4.arrowFunctionExpression([], arrayExpr),
25214
+ getItemsExpr,
25008
25215
  keyFn,
25009
- callbackExpr,
25216
+ renderExpr,
25010
25217
  t4.booleanLiteral(hasIndexParam)
25011
25218
  ]);
25012
25219
  } else {
25013
25220
  statements.push(...hoistedStatements);
25221
+ if (shouldDeferOptionalCallbackEvaluation) {
25222
+ statements.push(
25223
+ t4.variableDeclaration("let", [
25224
+ t4.variableDeclarator(t4.cloneNode(deferredCallbackId, true))
25225
+ ]),
25226
+ t4.variableDeclaration("let", [
25227
+ t4.variableDeclarator(t4.cloneNode(deferredCallbackInitId, true), t4.booleanLiteral(false))
25228
+ ])
25229
+ );
25230
+ }
25014
25231
  const itemParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? t4.isIdentifier(callbackExpr.params[0]) ? callbackExpr.params[0].name : "__item" : "__item";
25015
25232
  const indexParamName = t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr) ? t4.isIdentifier(callbackExpr.params[1]) ? callbackExpr.params[1].name : "__index" : "__index";
25016
- const hasIndexParam = (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) && callbackExpr.params.length >= 2;
25233
+ const hasIndexParam = shouldDeferOptionalCallbackEvaluation || (t4.isArrowFunctionExpression(callbackExpr) || t4.isFunctionExpression(callbackExpr)) && callbackExpr.params.length >= 2;
25234
+ const getItemsExpr = shouldDeferOptionalCallbackEvaluation ? t4.arrowFunctionExpression(
25235
+ [],
25236
+ t4.blockStatement([
25237
+ t4.variableDeclaration("const", [
25238
+ t4.variableDeclarator(
25239
+ t4.cloneNode(deferredItemsId, true),
25240
+ t4.cloneNode(arrayExprBase, true)
25241
+ )
25242
+ ]),
25243
+ t4.ifStatement(
25244
+ t4.binaryExpression("==", t4.cloneNode(deferredItemsId, true), t4.nullLiteral()),
25245
+ t4.blockStatement([t4.returnStatement(t4.arrayExpression([]))])
25246
+ ),
25247
+ t4.ifStatement(
25248
+ t4.unaryExpression("!", t4.cloneNode(deferredCallbackInitId, true)),
25249
+ t4.blockStatement([
25250
+ t4.expressionStatement(
25251
+ t4.assignmentExpression(
25252
+ "=",
25253
+ t4.cloneNode(deferredCallbackId, true),
25254
+ t4.cloneNode(callbackExpr, true)
25255
+ )
25256
+ ),
25257
+ t4.expressionStatement(
25258
+ t4.assignmentExpression(
25259
+ "=",
25260
+ t4.cloneNode(deferredCallbackInitId, true),
25261
+ t4.booleanLiteral(true)
25262
+ )
25263
+ )
25264
+ ])
25265
+ ),
25266
+ t4.returnStatement(t4.cloneNode(deferredItemsId, true))
25267
+ ])
25268
+ ) : t4.arrowFunctionExpression([], arrayExpr);
25269
+ const renderExpr = shouldDeferOptionalCallbackEvaluation ? t4.arrowFunctionExpression(
25270
+ [t4.identifier("__item"), t4.identifier("__index"), t4.identifier("__key")],
25271
+ t4.callExpression(t4.cloneNode(deferredCallbackId, true), [
25272
+ t4.identifier("__item"),
25273
+ t4.identifier("__index"),
25274
+ t4.identifier("__key")
25275
+ ])
25276
+ ) : callbackExpr;
25017
25277
  const keyFn = t4.arrowFunctionExpression(
25018
25278
  [t4.identifier(itemParamName), t4.identifier(indexParamName)],
25019
25279
  t4.identifier(indexParamName)
25020
25280
  );
25021
25281
  listCall = t4.callExpression(t4.identifier(RUNTIME_ALIASES.keyedList), [
25022
- t4.arrowFunctionExpression([], arrayExpr),
25282
+ getItemsExpr,
25023
25283
  keyFn,
25024
- callbackExpr,
25284
+ renderExpr,
25025
25285
  t4.booleanLiteral(hasIndexParam)
25026
25286
  ]);
25027
25287
  }
@@ -33920,6 +34180,506 @@ function getRootIdentifier(expr, t4) {
33920
34180
  return null;
33921
34181
  }
33922
34182
 
34183
+ // src/tooling/analyze.ts
34184
+ import { parseSync, transformSync } from "@babel/core";
34185
+
34186
+ // src/tooling/trace-infer.ts
34187
+ var TRACE_REGEX_ESCAPES = /[.*+?^${}()|[\]\\]/g;
34188
+ var IDENTIFIER_NAME = /^[A-Za-z_$][\w$]*$/;
34189
+ function isIdentifierName(name) {
34190
+ return IDENTIFIER_NAME.test(name);
34191
+ }
34192
+ function lineContainsIdentifier(lineText, identifier2) {
34193
+ const pattern = new RegExp(`\\b${identifier2.replace(TRACE_REGEX_ESCAPES, "\\$&")}\\b`);
34194
+ return pattern.test(lineText);
34195
+ }
34196
+ function lineContainsAnyIdentifier(lineText, identifiers) {
34197
+ for (const id of identifiers) {
34198
+ if (lineContainsIdentifier(lineText, id)) return true;
34199
+ }
34200
+ return false;
34201
+ }
34202
+ function pushTraceMarker(markersByLine, line, marker) {
34203
+ const markers = markersByLine.get(line);
34204
+ if (!markers) {
34205
+ markersByLine.set(line, [marker]);
34206
+ return;
34207
+ }
34208
+ const duplicate = markers.some(
34209
+ (existing) => existing.kind === marker.kind && existing.label === marker.label
34210
+ );
34211
+ if (!duplicate) markers.push(marker);
34212
+ }
34213
+ function inferReactiveLocalNames(startLine, endLine, sourceLines, baseReactiveNames) {
34214
+ const reactiveNames = new Set(baseReactiveNames);
34215
+ const declarationLines = [];
34216
+ const declarationPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?)(?:;)?$/;
34217
+ for (let line = startLine; line <= endLine; line++) {
34218
+ const text = (sourceLines[line - 1] ?? "").trim();
34219
+ if (!text) continue;
34220
+ const withoutComment = text.replace(/\/\/.*$/, "").trim();
34221
+ if (!withoutComment) continue;
34222
+ const match = withoutComment.match(declarationPattern);
34223
+ if (!match) continue;
34224
+ const name = match[1];
34225
+ const expression = match[2];
34226
+ if (!name || !expression) continue;
34227
+ declarationLines.push({ name, expression });
34228
+ }
34229
+ let changed = true;
34230
+ while (changed) {
34231
+ changed = false;
34232
+ for (const declaration of declarationLines) {
34233
+ if (reactiveNames.has(declaration.name)) continue;
34234
+ if (!lineContainsAnyIdentifier(declaration.expression, reactiveNames)) continue;
34235
+ reactiveNames.add(declaration.name);
34236
+ changed = true;
34237
+ }
34238
+ }
34239
+ return reactiveNames;
34240
+ }
34241
+ function isStateCallInstruction(instr) {
34242
+ if (instr.kind !== "Assign") return false;
34243
+ const value = instr.value;
34244
+ return value.kind === "CallExpression" && value.callee.kind === "Identifier" && value.callee.name === "$state";
34245
+ }
34246
+ function collectStateDeclNames(fn) {
34247
+ const result = [];
34248
+ for (const block of fn.blocks) {
34249
+ for (const instr of block.instructions) {
34250
+ if (!isStateCallInstruction(instr) || !instr.loc) continue;
34251
+ const name = deSSAVarName(instr.target.name);
34252
+ if (!isIdentifierName(name)) continue;
34253
+ result.push({ name, line: instr.loc.start.line });
34254
+ }
34255
+ }
34256
+ return result;
34257
+ }
34258
+ function expressionContainsEffectCall(instr) {
34259
+ const value = instr.kind === "Assign" || instr.kind === "Expression" ? instr.value : null;
34260
+ if (!value || !instr.loc) return null;
34261
+ let found = false;
34262
+ walkExpression(value, (expr) => {
34263
+ if (expr.kind === "CallExpression" && expr.callee.kind === "Identifier" && deSSAVarName(expr.callee.name) === "$effect") {
34264
+ found = true;
34265
+ }
34266
+ });
34267
+ return found ? instr.loc.start.line : null;
34268
+ }
34269
+ function flattenRegions2(regions) {
34270
+ if (!regions || regions.length === 0) return [];
34271
+ const result = [];
34272
+ const visit = (region) => {
34273
+ result.push(region);
34274
+ region.children?.forEach((child) => visit(child));
34275
+ };
34276
+ regions.forEach((region) => visit(region));
34277
+ return result;
34278
+ }
34279
+ function findContainingRegion2(line, flatRegions) {
34280
+ let best;
34281
+ for (const region of flatRegions) {
34282
+ if (region.startLine === void 0 || region.endLine === void 0 || line < region.startLine || line > region.endLine) {
34283
+ continue;
34284
+ }
34285
+ const bestSpan = best && best.startLine !== void 0 && best.endLine !== void 0 ? best.endLine - best.startLine : Number.POSITIVE_INFINITY;
34286
+ const span = region.endLine - region.startLine;
34287
+ if (span <= bestSpan) best = region;
34288
+ }
34289
+ return best;
34290
+ }
34291
+ function inferTraceMarkersForComponent(input) {
34292
+ const { fn, sourceLines, startLine, endLine, verbosity, regions } = input;
34293
+ const markersByLine = /* @__PURE__ */ new Map();
34294
+ pushTraceMarker(markersByLine, startLine, {
34295
+ kind: "once",
34296
+ label: "Component setup runs on mount"
34297
+ });
34298
+ const stateDecls = collectStateDeclNames(fn);
34299
+ const reactiveNames = inferReactiveLocalNames(
34300
+ startLine,
34301
+ endLine,
34302
+ sourceLines,
34303
+ stateDecls.map((item) => item.name)
34304
+ );
34305
+ for (const stateDecl of stateDecls) {
34306
+ pushTraceMarker(markersByLine, stateDecl.line, {
34307
+ kind: "once",
34308
+ label: "Signal initialization runs once"
34309
+ });
34310
+ }
34311
+ for (const block of fn.blocks) {
34312
+ for (const instr of block.instructions) {
34313
+ const line = expressionContainsEffectCall(instr);
34314
+ if (!line || line < startLine || line > endLine) continue;
34315
+ pushTraceMarker(markersByLine, line, {
34316
+ kind: "effect",
34317
+ label: "Effect reruns when dependencies change"
34318
+ });
34319
+ }
34320
+ }
34321
+ const flatRegions = flattenRegions2(regions);
34322
+ for (let line = startLine; line <= endLine; line++) {
34323
+ const lineText = sourceLines[line - 1] ?? "";
34324
+ if (!lineText) continue;
34325
+ const hasReactiveName = lineContainsAnyIdentifier(lineText, reactiveNames);
34326
+ if (hasReactiveName && /\{[^}]*\b[A-Za-z_$][\w$]*\b[^}]*\}/.test(lineText)) {
34327
+ pushTraceMarker(markersByLine, line, {
34328
+ kind: "reactive",
34329
+ label: "JSX expression updates with reactive values"
34330
+ });
34331
+ }
34332
+ if (hasReactiveName && /\bconsole\.(?:log|debug|info|warn|error)\s*\(/.test(lineText)) {
34333
+ pushTraceMarker(markersByLine, line, {
34334
+ kind: "reactive",
34335
+ label: "Statement reruns when reactive values change"
34336
+ });
34337
+ }
34338
+ if (/\b(?:\$effect|effect)\s*\(/.test(lineText)) {
34339
+ pushTraceMarker(markersByLine, line, {
34340
+ kind: "effect",
34341
+ label: "Effect callback executes reactively"
34342
+ });
34343
+ }
34344
+ if (verbosity === "verbose" && !hasReactiveName && /\{[^}]*\}/.test(lineText)) {
34345
+ pushTraceMarker(markersByLine, line, {
34346
+ kind: "once",
34347
+ label: "JSX expression runs during setup only"
34348
+ });
34349
+ }
34350
+ if (/\bconsole\.(?:log|debug|info|warn|error)\s*\(/.test(lineText) && !hasReactiveName && line !== startLine) {
34351
+ pushTraceMarker(markersByLine, line, {
34352
+ kind: "once",
34353
+ label: "Statement runs only during setup"
34354
+ });
34355
+ }
34356
+ const containingRegion = findContainingRegion2(line, flatRegions);
34357
+ if (!containingRegion) continue;
34358
+ const markers = markersByLine.get(line);
34359
+ if (!markers) continue;
34360
+ for (const marker of markers) {
34361
+ marker.regionId = containingRegion.id;
34362
+ marker.deps = containingRegion.dependencies;
34363
+ }
34364
+ }
34365
+ return [...markersByLine.entries()].sort((a, b) => a[0] - b[0]).map(([line, markers]) => ({ line, markers }));
34366
+ }
34367
+
34368
+ // src/tooling/analyze.ts
34369
+ function mergeLoc(a, b) {
34370
+ if (!a) return b ?? null;
34371
+ if (!b) return a;
34372
+ const start = b.start.line < a.start.line || b.start.line === a.start.line && b.start.column < a.start.column ? b.start : a.start;
34373
+ const end = b.end.line > a.end.line || b.end.line === a.end.line && b.end.column > a.end.column ? b.end : a.end;
34374
+ return {
34375
+ start,
34376
+ end,
34377
+ filename: a.filename ?? b.filename,
34378
+ identifierName: a.identifierName ?? b.identifierName
34379
+ };
34380
+ }
34381
+ function expressionContainsMacroCall(expr, macroName) {
34382
+ let found = false;
34383
+ const visit = (value) => {
34384
+ if (found) return;
34385
+ if (value.kind === "CallExpression" && value.callee.kind === "Identifier" && deSSAVarName(value.callee.name) === macroName) {
34386
+ found = true;
34387
+ return;
34388
+ }
34389
+ switch (value.kind) {
34390
+ case "CallExpression":
34391
+ case "OptionalCallExpression":
34392
+ visit(value.callee);
34393
+ value.arguments.forEach((arg) => visit(arg));
34394
+ return;
34395
+ case "MemberExpression":
34396
+ case "OptionalMemberExpression":
34397
+ visit(value.object);
34398
+ if (value.computed) visit(value.property);
34399
+ return;
34400
+ case "BinaryExpression":
34401
+ case "LogicalExpression":
34402
+ visit(value.left);
34403
+ visit(value.right);
34404
+ return;
34405
+ case "UnaryExpression":
34406
+ case "AwaitExpression":
34407
+ case "UpdateExpression":
34408
+ case "SpreadElement":
34409
+ visit(value.argument);
34410
+ return;
34411
+ case "ConditionalExpression":
34412
+ visit(value.test);
34413
+ visit(value.consequent);
34414
+ visit(value.alternate);
34415
+ return;
34416
+ case "ArrayExpression":
34417
+ value.elements.forEach((el) => visit(el));
34418
+ return;
34419
+ case "ObjectExpression":
34420
+ value.properties.forEach((prop) => {
34421
+ if (prop.kind === "SpreadElement") {
34422
+ visit(prop.argument);
34423
+ return;
34424
+ }
34425
+ if (prop.computed) visit(prop.key);
34426
+ visit(prop.value);
34427
+ });
34428
+ return;
34429
+ case "TemplateLiteral":
34430
+ value.expressions.forEach((item) => visit(item));
34431
+ return;
34432
+ case "AssignmentExpression":
34433
+ visit(value.left);
34434
+ visit(value.right);
34435
+ return;
34436
+ case "SequenceExpression":
34437
+ value.expressions.forEach((item) => visit(item));
34438
+ return;
34439
+ case "YieldExpression":
34440
+ if (value.argument) visit(value.argument);
34441
+ return;
34442
+ case "TaggedTemplateExpression":
34443
+ visit(value.tag);
34444
+ value.quasi.expressions.forEach((item) => visit(item));
34445
+ return;
34446
+ case "ImportExpression":
34447
+ visit(value.source);
34448
+ return;
34449
+ case "ArrowFunction":
34450
+ if (Array.isArray(value.body)) {
34451
+ value.body.forEach((block) => {
34452
+ block.instructions.forEach((instr) => {
34453
+ if (instr.kind === "Assign" || instr.kind === "Expression") {
34454
+ visit(instr.value);
34455
+ }
34456
+ });
34457
+ });
34458
+ } else {
34459
+ visit(value.body);
34460
+ }
34461
+ return;
34462
+ case "FunctionExpression":
34463
+ value.body.forEach((block) => {
34464
+ block.instructions.forEach((instr) => {
34465
+ if (instr.kind === "Assign" || instr.kind === "Expression") {
34466
+ visit(instr.value);
34467
+ }
34468
+ });
34469
+ });
34470
+ return;
34471
+ case "JSXElement":
34472
+ if (typeof value.tagName !== "string") visit(value.tagName);
34473
+ value.attributes.forEach((attr) => {
34474
+ if (attr.isSpread && attr.spreadExpr) {
34475
+ visit(attr.spreadExpr);
34476
+ } else if (attr.value) {
34477
+ visit(attr.value);
34478
+ }
34479
+ });
34480
+ value.children.forEach((child) => {
34481
+ if (child.kind === "expression") visit(child.value);
34482
+ if (child.kind === "element") visit(child.value);
34483
+ });
34484
+ return;
34485
+ case "Identifier":
34486
+ case "Literal":
34487
+ case "MetaProperty":
34488
+ case "NewExpression":
34489
+ case "ClassExpression":
34490
+ case "ThisExpression":
34491
+ case "SuperExpression":
34492
+ if (value.kind === "NewExpression") {
34493
+ visit(value.callee);
34494
+ value.arguments.forEach((arg) => visit(arg));
34495
+ }
34496
+ if (value.kind === "ClassExpression" && value.superClass) visit(value.superClass);
34497
+ return;
34498
+ default:
34499
+ return;
34500
+ }
34501
+ };
34502
+ visit(expr);
34503
+ return found;
34504
+ }
34505
+ function instructionContainsMacroCall(instruction, macroName) {
34506
+ if (instruction.kind !== "Assign" && instruction.kind !== "Expression") return false;
34507
+ return expressionContainsMacroCall(instruction.value, macroName);
34508
+ }
34509
+ function functionUsesMacro(fn, macroName) {
34510
+ for (const block of fn.blocks) {
34511
+ for (const instruction of block.instructions) {
34512
+ if (instructionContainsMacroCall(instruction, macroName)) return true;
34513
+ }
34514
+ const term = block.terminator;
34515
+ if ("argument" in term && term.argument && expressionContainsMacroCall(term.argument, macroName)) {
34516
+ return true;
34517
+ }
34518
+ if (term.kind === "Branch" && expressionContainsMacroCall(term.test, macroName)) {
34519
+ return true;
34520
+ }
34521
+ if (term.kind === "Switch") {
34522
+ if (expressionContainsMacroCall(term.discriminant, macroName)) return true;
34523
+ if (term.cases.some((entry) => entry.test && expressionContainsMacroCall(entry.test, macroName))) {
34524
+ return true;
34525
+ }
34526
+ }
34527
+ }
34528
+ return false;
34529
+ }
34530
+ function computeRegionLoc(region, fn) {
34531
+ let loc = null;
34532
+ for (const instruction of region.instructions) {
34533
+ loc = mergeLoc(loc, instruction.loc);
34534
+ }
34535
+ for (const blockId of region.blocks) {
34536
+ const block = fn.blocks.find((entry) => entry.id === blockId);
34537
+ if (!block) continue;
34538
+ loc = mergeLoc(loc, block.terminator.loc);
34539
+ }
34540
+ return loc;
34541
+ }
34542
+ function regionToSerializable(region, fn) {
34543
+ const loc = computeRegionLoc(region, fn);
34544
+ return {
34545
+ id: region.id,
34546
+ startLine: loc?.start.line,
34547
+ startColumn: loc?.start.column,
34548
+ endLine: loc?.end.line,
34549
+ endColumn: loc?.end.column,
34550
+ dependencies: [...region.dependencies].map(deSSAVarName),
34551
+ declarations: [...region.declarations].map(deSSAVarName),
34552
+ hasControlFlow: region.hasControlFlow,
34553
+ hasReactiveWrites: region.declarations.size > 0,
34554
+ children: region.children.map((child) => regionToSerializable(child, fn))
34555
+ };
34556
+ }
34557
+ function warningSeverity(code) {
34558
+ const diagnosticCodes = new Set(getAllDiagnosticCodes());
34559
+ if (!diagnosticCodes.has(code)) return "warning" /* Warning */;
34560
+ return getDiagnosticInfo(code).severity;
34561
+ }
34562
+ function normalizeWarningToDiagnostic(warning) {
34563
+ return {
34564
+ code: warning.code,
34565
+ message: warning.message,
34566
+ severity: warningSeverity(warning.code),
34567
+ line: warning.line,
34568
+ column: warning.column
34569
+ };
34570
+ }
34571
+ function normalizeThrownError(error) {
34572
+ const message = error instanceof Error ? error.message : String(error);
34573
+ return {
34574
+ code: "FICT-COMPILE",
34575
+ message,
34576
+ severity: "error" /* Error */,
34577
+ line: 0,
34578
+ column: 0
34579
+ };
34580
+ }
34581
+ function analyzeDiagnostics(code, fileName, options) {
34582
+ const warnings = [];
34583
+ const pluginOptions = {
34584
+ dev: true,
34585
+ filename: fileName,
34586
+ emitModuleMetadata: false,
34587
+ strictGuarantee: false,
34588
+ warningLevels: {
34589
+ ...options.compilerOptions?.warningLevels ?? {},
34590
+ "FICT-R004": "warn"
34591
+ },
34592
+ ...options.compilerOptions,
34593
+ onWarn: (warning) => warnings.push(warning)
34594
+ };
34595
+ try {
34596
+ transformSync(code, {
34597
+ filename: fileName,
34598
+ configFile: false,
34599
+ babelrc: false,
34600
+ sourceType: "module",
34601
+ parserOpts: {
34602
+ sourceType: "module",
34603
+ plugins: ["typescript", "jsx"],
34604
+ allowReturnOutsideFunction: true
34605
+ },
34606
+ plugins: [[index_default, pluginOptions]],
34607
+ generatorOpts: {
34608
+ compact: false
34609
+ }
34610
+ });
34611
+ } catch (error) {
34612
+ return [...warnings.map(normalizeWarningToDiagnostic), normalizeThrownError(error)];
34613
+ }
34614
+ return warnings.map(normalizeWarningToDiagnostic);
34615
+ }
34616
+ function shouldIncludeFunction(fn) {
34617
+ return functionContainsJSX(fn) || functionUsesMacro(fn, "$state") || functionUsesMacro(fn, "$effect");
34618
+ }
34619
+ function parseFileAst(code, fileName) {
34620
+ const ast = parseSync(code, {
34621
+ filename: fileName,
34622
+ sourceType: "module",
34623
+ parserOpts: {
34624
+ sourceType: "module",
34625
+ plugins: ["typescript", "jsx"],
34626
+ allowReturnOutsideFunction: true
34627
+ }
34628
+ });
34629
+ if (!ast || ast.type !== "File") {
34630
+ throw new Error("Failed to parse source file for Fict analysis.");
34631
+ }
34632
+ return ast;
34633
+ }
34634
+ function analyzeFictFile(code, fileName, options = {}) {
34635
+ const includeRegions = options.includeRegions ?? true;
34636
+ const includeDiagnostics = options.includeDiagnostics ?? true;
34637
+ const verbosity = options.verbosity ?? "minimal";
34638
+ const ast = parseFileAst(code, fileName);
34639
+ const hir = buildHIR(
34640
+ ast,
34641
+ {
34642
+ state: /* @__PURE__ */ new Set(["$state"]),
34643
+ effect: /* @__PURE__ */ new Set(["$effect"])
34644
+ },
34645
+ {
34646
+ dev: true,
34647
+ fileName
34648
+ }
34649
+ );
34650
+ const sourceLines = code.split(/\r?\n/);
34651
+ const components = [];
34652
+ for (const fn of hir.functions) {
34653
+ if (!fn.loc || !shouldIncludeFunction(fn)) continue;
34654
+ const startLine = fn.loc.start.line;
34655
+ const endLine = fn.loc.end.line;
34656
+ const scopeResult = analyzeReactiveScopesWithSSA(fn);
34657
+ const regionResult = generateRegions(fn, scopeResult);
34658
+ const regions = includeRegions ? regionResult.topLevelRegions.map((region) => regionToSerializable(region, fn)) : void 0;
34659
+ const trace = inferTraceMarkersForComponent({
34660
+ fn,
34661
+ sourceLines,
34662
+ startLine,
34663
+ endLine,
34664
+ verbosity,
34665
+ regions
34666
+ });
34667
+ components.push({
34668
+ name: fn.name ?? "<anonymous>",
34669
+ startLine,
34670
+ endLine,
34671
+ trace,
34672
+ regions
34673
+ });
34674
+ }
34675
+ const diagnostics = includeDiagnostics ? analyzeDiagnostics(code, fileName, options) : [];
34676
+ return {
34677
+ fileName,
34678
+ components,
34679
+ diagnostics
34680
+ };
34681
+ }
34682
+
33923
34683
  // src/index.ts
33924
34684
  function stripMacroImports(path2, t4) {
33925
34685
  path2.traverse({
@@ -34813,46 +35573,77 @@ function createHIREntrypointVisitor(t4, options) {
34813
35573
  path2.traverse({
34814
35574
  JSXExpressionContainer(exprPath) {
34815
35575
  const expr = exprPath.node.expression;
34816
- if (!t4.isCallExpression(expr)) return;
34817
- if (!t4.isMemberExpression(expr.callee) || !t4.isIdentifier(expr.callee.property, { name: "map" })) {
35576
+ if (!t4.isCallExpression(expr) && !t4.isOptionalCallExpression(expr)) return;
35577
+ if (!t4.isMemberExpression(expr.callee) && !t4.isOptionalMemberExpression(expr.callee) || !t4.isIdentifier(expr.callee.property, { name: "map" })) {
34818
35578
  return;
34819
35579
  }
34820
- const cb = expr.arguments[0];
34821
- if (!cb || !t4.isArrowFunctionExpression(cb) && !t4.isFunctionExpression(cb)) return;
34822
- const getReturnedJsx = (fn) => {
34823
- if (t4.isJSXElement(fn.body) || t4.isJSXFragment(fn.body)) return fn.body;
34824
- if (t4.isBlockStatement(fn.body)) {
34825
- const ret = fn.body.body.find((stmt) => t4.isReturnStatement(stmt));
34826
- if (ret && t4.isReturnStatement(ret) && ret.argument && (t4.isJSXElement(ret.argument) || t4.isJSXFragment(ret.argument))) {
34827
- return ret.argument;
34828
- }
34829
- }
34830
- return null;
34831
- };
34832
- const jsx = getReturnedJsx(cb);
34833
- if (!jsx) return;
34834
- if (t4.isJSXFragment(jsx)) {
34835
- warn({
34836
- code: "FICT-J002",
34837
- message: "Missing key prop in list rendering.",
34838
- fileName,
34839
- line: expr.loc?.start.line ?? 0,
34840
- column: expr.loc ? expr.loc.start.column + 1 : 0
34841
- });
35580
+ const callExprPath = exprPath.get("expression");
35581
+ if (!callExprPath.isCallExpression() && !callExprPath.isOptionalCallExpression()) return;
35582
+ const argPaths = callExprPath.get("arguments");
35583
+ const cbPath = Array.isArray(argPaths) ? argPaths[0] : void 0;
35584
+ if (!cbPath || !cbPath.isArrowFunctionExpression() && !cbPath.isFunctionExpression()) {
34842
35585
  return;
34843
35586
  }
34844
- let hasKey = false;
34845
- let hasUnknownSpread = false;
34846
- for (const attr of jsx.openingElement.attributes) {
34847
- if (t4.isJSXAttribute(attr) && t4.isJSXIdentifier(attr.name, { name: "key" })) {
34848
- hasKey = true;
34849
- break;
35587
+ const getReturnedJsx = (fnPath) => {
35588
+ const collectReturnedJsxFromExpression = (node, returned2) => {
35589
+ if (!node) return;
35590
+ if (t4.isJSXElement(node) || t4.isJSXFragment(node)) {
35591
+ returned2.push(node);
35592
+ return;
35593
+ }
35594
+ if (t4.isConditionalExpression(node)) {
35595
+ collectReturnedJsxFromExpression(node.consequent, returned2);
35596
+ collectReturnedJsxFromExpression(node.alternate, returned2);
35597
+ return;
35598
+ }
35599
+ if (t4.isLogicalExpression(node)) {
35600
+ collectReturnedJsxFromExpression(node.left, returned2);
35601
+ collectReturnedJsxFromExpression(node.right, returned2);
35602
+ return;
35603
+ }
35604
+ if (t4.isSequenceExpression(node)) {
35605
+ const tail = node.expressions[node.expressions.length - 1];
35606
+ collectReturnedJsxFromExpression(tail, returned2);
35607
+ return;
35608
+ }
35609
+ if (t4.isParenthesizedExpression(node)) {
35610
+ collectReturnedJsxFromExpression(node.expression, returned2);
35611
+ return;
35612
+ }
35613
+ if (t4.isTSAsExpression(node) || t4.isTSTypeAssertion(node) || t4.isTSNonNullExpression(node) || t4.isTSSatisfiesExpression(node) || t4.isTypeCastExpression(node)) {
35614
+ collectReturnedJsxFromExpression(node.expression, returned2);
35615
+ }
35616
+ };
35617
+ const fn = fnPath.node;
35618
+ const returned = [];
35619
+ if (!t4.isBlockStatement(fn.body)) {
35620
+ collectReturnedJsxFromExpression(fn.body, returned);
35621
+ return returned;
34850
35622
  }
34851
- if (t4.isJSXSpreadAttribute(attr)) {
34852
- hasUnknownSpread = true;
35623
+ fnPath.get("body").traverse({
35624
+ Function(innerFnPath) {
35625
+ innerFnPath.skip();
35626
+ },
35627
+ ReturnStatement(retPath) {
35628
+ collectReturnedJsxFromExpression(retPath.node.argument, returned);
35629
+ }
35630
+ });
35631
+ return returned;
35632
+ };
35633
+ const returnedJsx = getReturnedJsx(cbPath);
35634
+ if (returnedJsx.length === 0) return;
35635
+ const hasMissingKeyBranch = returnedJsx.some((jsx) => {
35636
+ if (t4.isJSXFragment(jsx)) return true;
35637
+ let hasKey = false;
35638
+ for (const attr of jsx.openingElement.attributes) {
35639
+ if (t4.isJSXAttribute(attr) && t4.isJSXIdentifier(attr.name, { name: "key" })) {
35640
+ hasKey = true;
35641
+ break;
35642
+ }
34853
35643
  }
34854
- }
34855
- if (hasKey || hasUnknownSpread) return;
35644
+ return !hasKey;
35645
+ });
35646
+ if (!hasMissingKeyBranch) return;
34856
35647
  warn({
34857
35648
  code: "FICT-J002",
34858
35649
  message: "Missing key prop in list rendering.",
@@ -35414,9 +36205,11 @@ var createFictPlugin = declare(
35414
36205
  );
35415
36206
  var index_default = createFictPlugin;
35416
36207
  export {
36208
+ analyzeFictFile,
35417
36209
  clearModuleMetadata,
35418
36210
  createFictPlugin,
35419
36211
  index_default as default,
36212
+ inferTraceMarkersForComponent,
35420
36213
  resolveModuleMetadata,
35421
36214
  setModuleMetadata
35422
36215
  };