@barefootjs/test 0.26.2 → 0.26.4

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.
Files changed (2) hide show
  1. package/dist/index.js +671 -142
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -187301,6 +187301,7 @@ var UNSUPPORTED_METHODS = new Set([
187301
187301
  "some",
187302
187302
  "forEach",
187303
187303
  "flatMap",
187304
+ "fill",
187304
187305
  "charAt",
187305
187306
  "charCodeAt",
187306
187307
  "codePointAt",
@@ -189191,6 +189192,16 @@ function computeFileScope(entryPath) {
189191
189192
 
189192
189193
  // ../jsx/src/ir-to-client-js/csr-substitute.ts
189193
189194
  var import_typescript4 = __toESM(require_typescript(), 1);
189195
+ function extractFreeIdentifiersFromText(text) {
189196
+ if (!text || text.trim().length === 0)
189197
+ return new Set;
189198
+ const sf = import_typescript4.default.createSourceFile("__free_ids__.ts", `(${text});`, import_typescript4.default.ScriptTarget.Latest, true, import_typescript4.default.ScriptKind.TS);
189199
+ const stmt = sf.statements[0];
189200
+ if (!stmt || !import_typescript4.default.isExpressionStatement(stmt))
189201
+ return new Set;
189202
+ const expr = import_typescript4.default.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
189203
+ return extractFreeIdentifiersFromNode(expr);
189204
+ }
189194
189205
 
189195
189206
  // ../jsx/src/ir-to-client-js/html-template.ts
189196
189207
  function splitTemplateInterpolations(inner) {
@@ -189412,6 +189423,39 @@ function reconstructWithoutTypes(node, sourceFile, ranges) {
189412
189423
  }
189413
189424
  return result;
189414
189425
  }
189426
+ function reconstructAsSegments(node, sourceFile, ranges, markers) {
189427
+ const nodeStart = node.getStart(sourceFile);
189428
+ const nodeEnd = node.getEnd();
189429
+ const fullText = sourceFile.text;
189430
+ const edits = [];
189431
+ for (const r of ranges) {
189432
+ if (r.end <= nodeStart || r.start >= nodeEnd)
189433
+ continue;
189434
+ edits.push({ start: r.start, end: r.end, marker: null });
189435
+ }
189436
+ markers.forEach((m, i2) => edits.push({ start: m.start, end: m.end, marker: i2 }));
189437
+ edits.sort((a, b) => a.start - b.start || b.end - a.end);
189438
+ const segments = [];
189439
+ let jsBuf = "";
189440
+ let pos = nodeStart;
189441
+ for (const edit of edits) {
189442
+ if (edit.start < pos)
189443
+ continue;
189444
+ jsBuf += fullText.slice(pos, edit.start);
189445
+ if (edit.marker !== null) {
189446
+ if (jsBuf)
189447
+ segments.push({ js: jsBuf });
189448
+ jsBuf = "";
189449
+ segments.push({ marker: edit.marker });
189450
+ }
189451
+ pos = edit.end;
189452
+ }
189453
+ if (pos < nodeEnd)
189454
+ jsBuf += fullText.slice(pos, nodeEnd);
189455
+ if (jsBuf)
189456
+ segments.push({ js: jsBuf });
189457
+ return segments;
189458
+ }
189415
189459
  function mergeRanges(ranges) {
189416
189460
  if (ranges.length === 0)
189417
189461
  return [];
@@ -189557,10 +189601,11 @@ function findAngleBracketAfter(lastTypeArg, fullText) {
189557
189601
  }
189558
189602
 
189559
189603
  // ../jsx/src/analyzer-context.ts
189560
- function createAnalyzerContext(sourceFile, filePath) {
189604
+ function createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody) {
189561
189605
  return {
189562
189606
  sourceFile,
189563
189607
  filePath,
189608
+ acceptsCallbackBody,
189564
189609
  componentName: null,
189565
189610
  componentNode: null,
189566
189611
  hasDefaultExport: false,
@@ -189606,6 +189651,9 @@ function createAnalyzerContext(sourceFile, filePath) {
189606
189651
  } catch {
189607
189652
  ownSourceFile = undefined;
189608
189653
  }
189654
+ if (process.env.BF_ASSERT_NO_JSX_IN_GETJS === "1" && this.errors.length === 0 && nodeContainsJsx(node)) {
189655
+ throw new Error("getJS() called on a JSX-bearing node — raw JSX must never be spliced " + "into emitted output. Carry mixed content as structured segments " + "(MapCallbackPreamble / FlatMapCallback) instead.");
189656
+ }
189609
189657
  if (ownSourceFile && ownSourceFile !== sourceFile) {
189610
189658
  return node.getText(ownSourceFile);
189611
189659
  }
@@ -189613,6 +189661,11 @@ function createAnalyzerContext(sourceFile, filePath) {
189613
189661
  }
189614
189662
  };
189615
189663
  }
189664
+ function nodeContainsJsx(node) {
189665
+ if (import_typescript7.default.isJsxElement(node) || import_typescript7.default.isJsxSelfClosingElement(node) || import_typescript7.default.isJsxFragment(node))
189666
+ return true;
189667
+ return import_typescript7.default.forEachChild(node, nodeContainsJsx) ?? false;
189668
+ }
189616
189669
  function getSourceLocation(node, sourceFile, filePath) {
189617
189670
  const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
189618
189671
  const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
@@ -190038,7 +190091,7 @@ function createProgramForFile(source, filePath) {
190038
190091
  return null;
190039
190092
  }
190040
190093
  }
190041
- function analyzeComponent(source, filePath, targetComponentName, program) {
190094
+ function analyzeComponent(source, filePath, targetComponentName, program, acceptsCallbackBody) {
190042
190095
  incrementCounter("filesAnalyzed");
190043
190096
  const hadSharedProgram = program !== undefined;
190044
190097
  const prescan = prescanReactiveFactoriesInSource(source, filePath);
@@ -190071,7 +190124,7 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
190071
190124
  checker = result.checker;
190072
190125
  }
190073
190126
  }
190074
- const ctx = createAnalyzerContext(sourceFile, filePath);
190127
+ const ctx = createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody);
190075
190128
  ctx.checker = checker;
190076
190129
  ctx.reactiveFactories = prescan.factories;
190077
190130
  ctx.declinedReactiveFactories = prescan.declined;
@@ -191123,9 +191176,15 @@ function extractSingleJsxReturn(body) {
191123
191176
  return null;
191124
191177
  return jsxReturn;
191125
191178
  }
191126
- function extractMultiReturnJsxBranches(body) {
191179
+ function preambleUnsafe(preamble, branches, fallback) {
191180
+ if (preamble.length === 0)
191181
+ return false;
191182
+ return fallback === null || branches.some((b) => b.jsxReturn === null);
191183
+ }
191184
+ function extractMultiReturnJsxBranches(body, allowPreamble = false) {
191127
191185
  const branches = [];
191128
191186
  let fallback = null;
191187
+ const preamble = [];
191129
191188
  const stmts = body.statements;
191130
191189
  for (let i2 = 0;i2 < stmts.length; i2++) {
191131
191190
  const stmt = stmts[i2];
@@ -191155,7 +191214,9 @@ function extractMultiReturnJsxBranches(body) {
191155
191214
  }
191156
191215
  if (branches.length === 0)
191157
191216
  return null;
191158
- return { branches, fallback };
191217
+ if (preambleUnsafe(preamble, branches, fallback))
191218
+ return null;
191219
+ return { branches, fallback, preamble };
191159
191220
  }
191160
191221
  break;
191161
191222
  }
@@ -191170,23 +191231,38 @@ function extractMultiReturnJsxBranches(body) {
191170
191231
  const hasDefault = stmt.caseBlock.clauses.some((c) => import_typescript8.default.isDefaultClause(c));
191171
191232
  if (!hasDefault)
191172
191233
  return null;
191234
+ let pendingCases = [];
191173
191235
  for (const clause of stmt.caseBlock.clauses) {
191236
+ if (import_typescript8.default.isCaseClause(clause) && clause.statements.length === 0) {
191237
+ pendingCases.push(clause.expression);
191238
+ continue;
191239
+ }
191174
191240
  const jsxReturn = findJsxReturnInCaseClause(clause);
191175
191241
  const nullReturn = findNullReturnInCaseClause(clause);
191176
191242
  if (!jsxReturn && !nullReturn)
191177
191243
  return null;
191244
+ if (!caseClauseIsDirectReturn(clause))
191245
+ return null;
191178
191246
  if (import_typescript8.default.isCaseClause(clause)) {
191179
191247
  branches.push({
191180
191248
  condition: clause.expression,
191181
- jsxReturn: jsxReturn ?? null
191249
+ jsxReturn: jsxReturn ?? null,
191250
+ extraCaseConditions: pendingCases.length > 0 ? pendingCases : undefined
191182
191251
  });
191252
+ pendingCases = [];
191183
191253
  } else {
191254
+ if (pendingCases.length > 0)
191255
+ return null;
191184
191256
  fallback = jsxReturn ?? null;
191185
191257
  }
191186
191258
  }
191259
+ if (pendingCases.length > 0)
191260
+ return null;
191187
191261
  if (branches.length === 0)
191188
191262
  return null;
191189
- return { branches, fallback, switchDiscriminant: stmt.expression };
191263
+ if (preambleUnsafe(preamble, branches, fallback))
191264
+ return null;
191265
+ return { branches, fallback, switchDiscriminant: stmt.expression, preamble };
191190
191266
  }
191191
191267
  if (import_typescript8.default.isReturnStatement(stmt) && stmt.expression) {
191192
191268
  const expr = unwrapJsxTransparent(stmt.expression);
@@ -191197,13 +191273,22 @@ function extractMultiReturnJsxBranches(body) {
191197
191273
  }
191198
191274
  continue;
191199
191275
  }
191200
- if (import_typescript8.default.isVariableStatement(stmt))
191276
+ if (import_typescript8.default.isVariableStatement(stmt)) {
191277
+ const declFlags = stmt.declarationList.flags;
191278
+ const isConstOrLet = (declFlags & import_typescript8.default.NodeFlags.Const) !== 0 || (declFlags & import_typescript8.default.NodeFlags.Let) !== 0;
191279
+ if (allowPreamble && isConstOrLet && branches.length === 0 && fallback === null) {
191280
+ preamble.push(stmt);
191281
+ continue;
191282
+ }
191201
191283
  return null;
191284
+ }
191202
191285
  return null;
191203
191286
  }
191204
191287
  if (branches.length === 0)
191205
191288
  return null;
191206
- return { branches, fallback };
191289
+ if (preambleUnsafe(preamble, branches, fallback))
191290
+ return null;
191291
+ return { branches, fallback, preamble };
191207
191292
  }
191208
191293
  function isDirectReturnBlock(node) {
191209
191294
  if (import_typescript8.default.isReturnStatement(node))
@@ -191213,9 +191298,9 @@ function isDirectReturnBlock(node) {
191213
191298
  for (const stmt of node.statements) {
191214
191299
  if (import_typescript8.default.isReturnStatement(stmt)) {
191215
191300
  returnCount++;
191216
- } else if (import_typescript8.default.isIfStatement(stmt) || import_typescript8.default.isSwitchStatement(stmt) || import_typescript8.default.isForStatement(stmt) || import_typescript8.default.isForOfStatement(stmt) || import_typescript8.default.isForInStatement(stmt) || import_typescript8.default.isWhileStatement(stmt) || import_typescript8.default.isDoStatement(stmt) || import_typescript8.default.isTryStatement(stmt)) {
191217
- return false;
191301
+ continue;
191218
191302
  }
191303
+ return false;
191219
191304
  }
191220
191305
  return returnCount === 1;
191221
191306
  }
@@ -191246,6 +191331,24 @@ function findJsxReturnInCaseClause(clause) {
191246
191331
  }
191247
191332
  return null;
191248
191333
  }
191334
+ function caseClauseIsDirectReturn(clause) {
191335
+ let returnCount = 0;
191336
+ let seenReturn = false;
191337
+ for (const stmt of clause.statements) {
191338
+ if (import_typescript8.default.isReturnStatement(stmt)) {
191339
+ returnCount++;
191340
+ seenReturn = true;
191341
+ continue;
191342
+ }
191343
+ if (import_typescript8.default.isBreakStatement(stmt)) {
191344
+ if (!seenReturn)
191345
+ return false;
191346
+ continue;
191347
+ }
191348
+ return false;
191349
+ }
191350
+ return returnCount === 1;
191351
+ }
191249
191352
  function findNullReturnInCaseClause(clause) {
191250
191353
  for (const stmt of clause.statements) {
191251
191354
  if (import_typescript8.default.isReturnStatement(stmt) && stmt.expression) {
@@ -193261,6 +193364,9 @@ var REACTIVE_BINDING_KINDS = new Set([
193261
193364
  function isReactiveOrigin(origin) {
193262
193365
  return origin.freeRefs?.some((r) => REACTIVE_BINDING_KINDS.has(r.kind)) ?? false;
193263
193366
  }
193367
+ function tsxSourceText(raw) {
193368
+ return raw;
193369
+ }
193264
193370
  var AttrValueOf = {
193265
193371
  literal(value) {
193266
193372
  return { kind: "literal", value };
@@ -194391,8 +194497,13 @@ function attachParsedExpressions(node, analyzer, bound = EMPTY_BOUND) {
194391
194497
  for (const child of nested.children)
194392
194498
  attachParsedExpressions(child, analyzer, loopBound);
194393
194499
  }
194394
- for (const frag of node.flatMapCallback?.fragments ?? []) {
194395
- attachParsedExpressions(frag.ir, analyzer, loopBound);
194500
+ for (const seg of node.flatMapCallback?.segments ?? []) {
194501
+ if (seg.kind === "jsx")
194502
+ attachParsedExpressions(seg.ir, analyzer, loopBound);
194503
+ }
194504
+ for (const seg of node.preamble?.segments ?? []) {
194505
+ if (seg.kind === "jsx")
194506
+ attachParsedExpressions(seg.ir, analyzer, loopBound);
194396
194507
  }
194397
194508
  break;
194398
194509
  }
@@ -195027,74 +195138,78 @@ function transformMultiReturnJsxFunctionCall(callExpr, info, ctx) {
195027
195138
  ctx.analyzer.getJS = substitutedGetJS;
195028
195139
  try {
195029
195140
  const loc = getSourceLocation(callExpr, ctx.sourceFile, ctx.filePath);
195030
- const nullExpr = {
195031
- type: "expression",
195032
- expr: "null",
195033
- typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
195034
- reactive: false,
195035
- slotId: null,
195036
- loc,
195037
- origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
195038
- };
195039
- let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
195040
- for (let i2 = info.branches.length - 1;i2 >= 0; i2--) {
195041
- const branch = info.branches[i2];
195042
- let conditionText;
195043
- if (info.switchDiscriminant) {
195044
- const discText = substitutedGetJS(info.switchDiscriminant);
195045
- const caseText = substitutedGetJS(branch.condition);
195046
- conditionText = `${discText} === ${caseText}`;
195047
- } else {
195048
- conditionText = substitutedGetJS(branch.condition);
195049
- }
195050
- const env = makeBindingEnv(ctx);
195051
- const caseFreeRefs = resolveFreeRefs(branch.condition, env);
195052
- const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
195053
- const conditionOrigin = {
195054
- phase: "tick",
195055
- scope: "template",
195056
- effect: "pure",
195057
- freeRefs: [...discFreeRefs, ...caseFreeRefs]
195058
- };
195059
- const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
195060
- const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
195061
- const callsReactive = exprCallsReactiveGetters(branch.condition, ctx) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
195062
- const hasCalls = exprHasFunctionCalls(branch.condition) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
195063
- const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
195064
- const slotId = needsSlot ? generateSlotId(ctx) : null;
195065
- const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
195066
- let templateCondition;
195067
- if (info.switchDiscriminant) {
195068
- const discRewritten = rewriteBarePropRefs2(substitutedGetJS(info.switchDiscriminant), info.switchDiscriminant, ctx);
195069
- const caseRewritten = rewriteBarePropRefs2(substitutedGetJS(branch.condition), branch.condition, ctx);
195070
- const discPart = discRewritten ?? substitutedGetJS(info.switchDiscriminant);
195071
- const casePart = caseRewritten ?? substitutedGetJS(branch.condition);
195072
- templateCondition = `${discPart} === ${casePart}`;
195073
- } else {
195074
- templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
195075
- }
195076
- const conditional = {
195077
- type: "conditional",
195078
- condition: conditionText,
195079
- templateCondition,
195080
- conditionType: null,
195081
- reactive,
195082
- whenTrue,
195083
- whenFalse: result,
195084
- slotId,
195085
- callsReactiveGetters: callsReactive || undefined,
195086
- hasFunctionCalls: hasCalls || undefined,
195087
- loc,
195088
- origin: conditionOrigin
195089
- };
195090
- result = conditional;
195091
- }
195092
- return result;
195141
+ return foldMultiReturnBranches(info, ctx, loc, substitutedGetJS);
195093
195142
  } finally {
195094
195143
  ctx.getJS = originalCtxGetJS;
195095
195144
  ctx.analyzer.getJS = originalAnalyzerGetJS;
195096
195145
  }
195097
195146
  }
195147
+ function foldMultiReturnBranches(info, ctx, loc, getText) {
195148
+ const nullExpr = {
195149
+ type: "expression",
195150
+ expr: "null",
195151
+ typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
195152
+ reactive: false,
195153
+ slotId: null,
195154
+ loc,
195155
+ origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
195156
+ };
195157
+ let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
195158
+ for (let i2 = info.branches.length - 1;i2 >= 0; i2--) {
195159
+ const branch = info.branches[i2];
195160
+ const caseConds = info.switchDiscriminant ? [branch.condition, ...branch.extraCaseConditions ?? []] : [branch.condition];
195161
+ let conditionText;
195162
+ if (info.switchDiscriminant) {
195163
+ const discText = getText(info.switchDiscriminant);
195164
+ conditionText = caseConds.map((c) => `(${discText}) === (${getText(c)})`).join(" || ");
195165
+ } else {
195166
+ conditionText = getText(branch.condition);
195167
+ }
195168
+ const env = makeBindingEnv(ctx);
195169
+ const caseFreeRefs = caseConds.flatMap((c) => resolveFreeRefs(c, env));
195170
+ const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
195171
+ const conditionOrigin = {
195172
+ phase: "tick",
195173
+ scope: "template",
195174
+ effect: "pure",
195175
+ freeRefs: [...discFreeRefs, ...caseFreeRefs]
195176
+ };
195177
+ const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
195178
+ const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
195179
+ const callsReactive = caseConds.some((c) => exprCallsReactiveGetters(c, ctx)) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
195180
+ const hasCalls = caseConds.some((c) => exprHasFunctionCalls(c)) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
195181
+ const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
195182
+ const slotId = needsSlot ? generateSlotId(ctx) : null;
195183
+ const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
195184
+ let templateCondition;
195185
+ if (info.switchDiscriminant) {
195186
+ const discRewritten = rewriteBarePropRefs2(getText(info.switchDiscriminant), info.switchDiscriminant, ctx);
195187
+ const discPart = discRewritten ?? getText(info.switchDiscriminant);
195188
+ templateCondition = caseConds.map((c) => {
195189
+ const casePart = rewriteBarePropRefs2(getText(c), c, ctx) ?? getText(c);
195190
+ return `(${discPart}) === (${casePart})`;
195191
+ }).join(" || ");
195192
+ } else {
195193
+ templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
195194
+ }
195195
+ const conditional = {
195196
+ type: "conditional",
195197
+ condition: conditionText,
195198
+ templateCondition,
195199
+ conditionType: null,
195200
+ reactive,
195201
+ whenTrue,
195202
+ whenFalse: result,
195203
+ slotId,
195204
+ callsReactiveGetters: callsReactive || undefined,
195205
+ hasFunctionCalls: hasCalls || undefined,
195206
+ loc,
195207
+ origin: conditionOrigin
195208
+ };
195209
+ result = conditional;
195210
+ }
195211
+ return result;
195212
+ }
195098
195213
  function transformConditional(node, ctx) {
195099
195214
  const condition = ctx.getJS(node.condition);
195100
195215
  const conditionOrigin = {
@@ -195891,6 +196006,87 @@ function checkLoopKey(callback, ctx, isNested) {
195891
196006
  return;
195892
196007
  }
195893
196008
  }
196009
+ function flatMapProjectionCall(body) {
196010
+ let expr;
196011
+ if (import_typescript11.default.isBlock(body)) {
196012
+ const real = body.statements;
196013
+ if (real.length !== 1 || !import_typescript11.default.isReturnStatement(real[0]) || !real[0].expression)
196014
+ return null;
196015
+ expr = real[0].expression;
196016
+ } else {
196017
+ expr = body;
196018
+ }
196019
+ while (import_typescript11.default.isParenthesizedExpression(expr))
196020
+ expr = expr.expression;
196021
+ if (!import_typescript11.default.isCallExpression(expr))
196022
+ return null;
196023
+ if (!getMapLikeMethod(expr))
196024
+ return null;
196025
+ const cb = expr.arguments[0];
196026
+ if (!cb || !import_typescript11.default.isArrowFunction(cb) && !import_typescript11.default.isFunctionExpression(cb))
196027
+ return null;
196028
+ for (const p of cb.parameters) {
196029
+ if (!import_typescript11.default.isIdentifier(p.name))
196030
+ return null;
196031
+ }
196032
+ let innerBody = cb.body;
196033
+ if (import_typescript11.default.isBlock(innerBody)) {
196034
+ const ret = innerBody.statements.find((s) => import_typescript11.default.isReturnStatement(s) && s.expression != null);
196035
+ if (innerBody.statements.length !== 1 || !ret?.expression)
196036
+ return null;
196037
+ innerBody = ret.expression;
196038
+ }
196039
+ while (import_typescript11.default.isParenthesizedExpression(innerBody))
196040
+ innerBody = innerBody.expression;
196041
+ const isElementish = (n) => {
196042
+ let m = n;
196043
+ while (import_typescript11.default.isParenthesizedExpression(m))
196044
+ m = m.expression;
196045
+ if (import_typescript11.default.isJsxElement(m) || import_typescript11.default.isJsxSelfClosingElement(m))
196046
+ return leafIsWirelessElement(m);
196047
+ if (import_typescript11.default.isConditionalExpression(m))
196048
+ return isElementish(m.whenTrue) && isElementish(m.whenFalse);
196049
+ return false;
196050
+ };
196051
+ if (!isElementish(innerBody))
196052
+ return null;
196053
+ return expr;
196054
+ }
196055
+ function leafIsWirelessElement(el) {
196056
+ let ok = true;
196057
+ const visit2 = (n) => {
196058
+ if (!ok)
196059
+ return;
196060
+ if (import_typescript11.default.isJsxOpeningElement(n) || import_typescript11.default.isJsxSelfClosingElement(n)) {
196061
+ const tagNode = n.tagName;
196062
+ const isIntrinsic = import_typescript11.default.isIdentifier(tagNode) ? !/^[A-Z]/.test(tagNode.text) : import_typescript11.default.isJsxNamespacedName(tagNode);
196063
+ if (!isIntrinsic) {
196064
+ ok = false;
196065
+ return;
196066
+ }
196067
+ for (const attr of n.attributes.properties) {
196068
+ if (import_typescript11.default.isJsxSpreadAttribute(attr)) {
196069
+ ok = false;
196070
+ return;
196071
+ }
196072
+ if (import_typescript11.default.isJsxAttribute(attr)) {
196073
+ const name = attr.name.getText();
196074
+ if (/^on[A-Z]/.test(name)) {
196075
+ ok = false;
196076
+ return;
196077
+ }
196078
+ }
196079
+ }
196080
+ }
196081
+ if (import_typescript11.default.isCallExpression(n) && getMapLikeMethod(n) && containsJsxInExpression(n)) {
196082
+ ok = false;
196083
+ return;
196084
+ }
196085
+ import_typescript11.default.forEachChild(n, visit2);
196086
+ };
196087
+ visit2(el);
196088
+ return ok;
196089
+ }
195894
196090
  function loopBodyIsMultiRoot(children) {
195895
196091
  const real = children.filter((c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim()));
195896
196092
  if (real.length === 0)
@@ -195936,6 +196132,7 @@ function extractItemConditionalKey(cond) {
195936
196132
  }
195937
196133
  function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
195938
196134
  const isNested = ctx.loopParams.size > 0;
196135
+ const diagCountAtEntry = ctx.analyzer.errors.length;
195939
196136
  const depth = ctx.loopDepth;
195940
196137
  const propAccess = node.expression;
195941
196138
  const mapSource = propAccess.expression;
@@ -195945,9 +196142,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
195945
196142
  let filterPredicate;
195946
196143
  let sortComparator;
195947
196144
  let chainOrder;
195948
- let mapPreamble;
195949
- let templateMapPreamble;
195950
- let typedMapPreamble;
196145
+ let preamble;
195951
196146
  let iterationShape;
195952
196147
  let objectIteration;
195953
196148
  const setArray = (node2) => {
@@ -195977,7 +196172,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
195977
196172
  const innerFilter = isFilterCall(sortInfo.array);
195978
196173
  const sortExtraction = extractSortComparator(sortInfo.callback, sortInfo.method, ctx);
195979
196174
  if (isClientOnly || !sortExtraction.result) {
195980
- if (!isClientOnly && sortExtraction.unsupportedReason) {
196175
+ if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
195981
196176
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(sortInfo.callback, ctx.sourceFile, ctx.filePath), {
195982
196177
  message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
195983
196178
  suggestion: {
@@ -195992,7 +196187,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
195992
196187
  chainOrder = "filter-sort";
195993
196188
  const filterExtraction = extractFilterPredicate(innerFilter.callback, ctx);
195994
196189
  if (isClientOnly || !filterExtraction.result) {
195995
- if (!isClientOnly && filterExtraction.unsupportedReason) {
196190
+ if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
195996
196191
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerFilter.callback, ctx.sourceFile, ctx.filePath), {
195997
196192
  message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
195998
196193
  suggestion: {
@@ -196015,7 +196210,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196015
196210
  const innerSort = isSortCall(filterInfo.array);
196016
196211
  const filterExtraction = extractFilterPredicate(filterInfo.callback, ctx);
196017
196212
  if (isClientOnly || !filterExtraction.result) {
196018
- if (!isClientOnly && filterExtraction.unsupportedReason) {
196213
+ if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
196019
196214
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(filterInfo.callback, ctx.sourceFile, ctx.filePath), {
196020
196215
  message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
196021
196216
  suggestion: {
@@ -196030,7 +196225,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196030
196225
  chainOrder = "sort-filter";
196031
196226
  const sortExtraction = extractSortComparator(innerSort.callback, innerSort.method, ctx);
196032
196227
  if (isClientOnly || !sortExtraction.result) {
196033
- if (!isClientOnly && sortExtraction.unsupportedReason) {
196228
+ if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
196034
196229
  ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerSort.callback, ctx.sourceFile, ctx.filePath), {
196035
196230
  message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
196036
196231
  suggestion: {
@@ -196145,7 +196340,24 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196145
196340
  } else if (method === "flatMap" && import_typescript11.default.isArrayLiteralExpression(body)) {
196146
196341
  children = transformArrayLiteralChildren(body, ctx);
196147
196342
  } else if (import_typescript11.default.isBlock(body)) {
196148
- const returnStmt = body.statements.find((s) => import_typescript11.default.isReturnStatement(s) && s.expression != null);
196343
+ const multiReturn = method !== "flatMap" ? extractMultiReturnJsxBranches(body, true) : null;
196344
+ if (multiReturn && multiReturn.branches.length > 0) {
196345
+ const loc = getSourceLocation(body, ctx.sourceFile, ctx.filePath);
196346
+ children = [foldMultiReturnBranches(multiReturn, ctx, loc, ctx.getJS)];
196347
+ const pre = multiReturn.preamble ?? [];
196348
+ if (pre.length > 0) {
196349
+ preamble = preambleFromValueStatements(pre, ctx);
196350
+ if (!isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("map") ?? false)) {
196351
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
196352
+ message: "A .map() callback body with a `const`/`let` preamble before its " + "branches cannot be lowered to a template: the loop-local binding " + "cannot be carried into a conditional branch on this backend.",
196353
+ suggestion: {
196354
+ message: "Add /* @client */ to evaluate this expression on the client only"
196355
+ }
196356
+ }));
196357
+ }
196358
+ }
196359
+ }
196360
+ const returnStmt = children.length === 0 ? body.statements.find((s) => import_typescript11.default.isReturnStatement(s) && s.expression != null) : undefined;
196149
196361
  if (returnStmt && returnStmt.expression) {
196150
196362
  let returnExpr = returnStmt.expression;
196151
196363
  while (import_typescript11.default.isParenthesizedExpression(returnExpr)) {
@@ -196157,41 +196369,84 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196157
196369
  children = [transformed];
196158
196370
  }
196159
196371
  }
196160
- const preambleStmts = [];
196161
- const templatePreambleStmts = [];
196162
- const typedPreambleStmts = [];
196163
- let hasTypeDiff = false;
196164
- let hasTemplateDiff = false;
196372
+ let jsxPreambleStmt;
196165
196373
  for (const stmt of body.statements) {
196166
196374
  if (stmt === returnStmt)
196167
196375
  break;
196168
- const js = ctx.getJS(stmt);
196169
- const tjs = ctx.getTemplateJS(stmt);
196170
- const ts12 = stmt.getText(ctx.sourceFile);
196171
- preambleStmts.push(js.endsWith(";") ? js : js + ";");
196172
- templatePreambleStmts.push(tjs.endsWith(";") ? tjs : tjs + ";");
196173
- typedPreambleStmts.push(ts12.endsWith(";") ? ts12 : ts12 + ";");
196174
- if (js !== ts12)
196175
- hasTypeDiff = true;
196176
- if (js !== tjs)
196177
- hasTemplateDiff = true;
196376
+ if (containsJsxInExpression(stmt)) {
196377
+ jsxPreambleStmt = stmt;
196378
+ break;
196379
+ }
196178
196380
  }
196179
- if (preambleStmts.length > 0) {
196180
- mapPreamble = preambleStmts.join(" ");
196181
- if (hasTemplateDiff) {
196182
- templateMapPreamble = templatePreambleStmts.join(" ");
196381
+ if (jsxPreambleStmt) {
196382
+ const jsRuntime = isClientOnly || (ctx.analyzer.acceptsCallbackBody?.("map") ?? false);
196383
+ if (children.length === 0) {
196384
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
196385
+ message: "A .map() callback that builds JSX in a statement before its " + "`return` must return a single JSX element that embeds the " + "built array — this return shape has no element root to host it.",
196386
+ suggestion: {
196387
+ message: "Wrap the result in one element root, e.g. `return <tr key={item.id}>{out}</tr>`."
196388
+ }
196389
+ }));
196390
+ } else if (!jsRuntime) {
196391
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
196392
+ message: "A .map() callback that builds JSX in a statement before its " + "`return` (for example pushing elements into an array in a loop) " + "cannot be lowered to a template on this backend.",
196393
+ suggestion: {
196394
+ message: "Add /* @client */ to render this loop on the client only"
196395
+ }
196396
+ }));
196397
+ } else {
196398
+ const collected = buildPreambleSegments(body.statements, returnStmt, ctx);
196399
+ if (collected.refusalNode) {
196400
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(collected.refusalNode, ctx.sourceFile, ctx.filePath), {
196401
+ message: "A JSX element built in a .map() callback preamble cannot carry " + "event handlers, components, nested loops, or reactive expressions, " + "and cannot sit inside a template literal — the verbatim render " + "path builds it once, with no reactive wiring.",
196402
+ suggestion: {
196403
+ message: "Return the element directly (not through a preamble variable), " + "or add /* @client */ to render the loop on the client only."
196404
+ }
196405
+ }));
196406
+ } else {
196407
+ preamble = collected.preamble;
196408
+ }
196409
+ }
196410
+ } else {
196411
+ const valueStmts = [];
196412
+ for (const stmt of body.statements) {
196413
+ if (stmt === returnStmt)
196414
+ break;
196415
+ valueStmts.push(stmt);
196183
196416
  }
196184
- if (hasTypeDiff) {
196185
- typedMapPreamble = typedPreambleStmts.join(" ");
196417
+ if (valueStmts.length > 0) {
196418
+ preamble = preambleFromValueStatements(valueStmts, ctx);
196186
196419
  }
196187
196420
  }
196188
196421
  }
196189
- if (method === "flatMap" && children.length === 0) {
196422
+ if (method === "flatMap" && children.length === 0 && !flatMapProjectionCall(body)) {
196190
196423
  flatMapCallback = buildFlatMapCallback(callback, body, ctx);
196191
196424
  }
196192
196425
  } else {
196193
196426
  tryTransformRenderableBody(body);
196194
196427
  }
196428
+ if (method === "flatMap" && children.length === 0 && !flatMapCallback) {
196429
+ const projection = flatMapProjectionCall(body);
196430
+ if (projection) {
196431
+ const transformed = transformJsxExpression(projection, ctx, isClientOnly);
196432
+ if (transformed && transformed.type === "loop") {
196433
+ children = [transformed];
196434
+ }
196435
+ }
196436
+ }
196437
+ if (method === "flatMap" && children.length === 0 && !flatMapCallback && !import_typescript11.default.isBlock(body)) {
196438
+ flatMapCallback = buildFlatMapCallback(callback, body, ctx);
196439
+ }
196440
+ if (flatMapCallback)
196441
+ preamble = undefined;
196442
+ if (flatMapCallback && !isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("flatMap") ?? false)) {
196443
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(body, ctx.sourceFile, ctx.filePath), {
196444
+ message: "A .flatMap() callback body with statements or a nested projection " + "cannot be lowered to a template on this backend.",
196445
+ suggestion: {
196446
+ message: "Add /* @client */ to render this loop on the client only"
196447
+ }
196448
+ }));
196449
+ }
196195
196450
  if (paramBindings) {
196196
196451
  for (const b of paramBindings)
196197
196452
  ctx.loopParams.delete(b.name);
@@ -196203,6 +196458,16 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196203
196458
  ctx.loopDepth--;
196204
196459
  }
196205
196460
  if (children.length === 0 && !flatMapCallback) {
196461
+ const cb = node.arguments[0];
196462
+ const cbBody = cb && (import_typescript11.default.isArrowFunction(cb) || import_typescript11.default.isFunctionExpression(cb)) ? cb.body : undefined;
196463
+ if (cbBody && containsJsxInExpression(cbBody) && ctx.analyzer.errors.length === diagCountAtEntry) {
196464
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(cbBody, ctx.sourceFile, ctx.filePath), {
196465
+ message: `A .${method}() callback that builds JSX in this shape cannot be ` + "compiled — the JSX would leak verbatim into the client bundle. " + "Recognized bodies: a JSX element/fragment, a ternary or " + "&& / || / ?? expression, an array literal (flatMap), or a block " + "body whose return the compiler can lower.",
196466
+ suggestion: {
196467
+ message: "Restructure the callback to return the JSX element directly " + "(or via a block body with a plain `return`)."
196468
+ }
196469
+ }));
196470
+ }
196206
196471
  return null;
196207
196472
  }
196208
196473
  if (import_typescript11.default.isArrowFunction(node.arguments[0]) && children.length > 0) {
@@ -196211,6 +196476,31 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196211
196476
  const itemConditional = children.length > 0 ? loopBodyItemConditional(children) : null;
196212
196477
  const bodyIsItemConditional = itemConditional !== null;
196213
196478
  const key = bodyIsItemConditional ? extractItemConditionalKey(itemConditional) : children.length > 0 ? extractLoopKey(children[0]) : null;
196479
+ const declaredNameSet = preamble && preamble.declaredNames.length > 0 ? new Set(preamble.declaredNames) : undefined;
196480
+ if (key && declaredNameSet) {
196481
+ const keyRefs = extractFreeIdentifiersFromText(key);
196482
+ const usesLocal = [...keyRefs].some((r) => declaredNameSet.has(r));
196483
+ if (usesLocal) {
196484
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
196485
+ message: "A .map() loop key must be derivable from the loop item — it is " + "evaluated before the callback body runs, so it cannot reference a " + "value computed in the callback preamble.",
196486
+ suggestion: {
196487
+ message: "Derive the key directly from the loop item (e.g. key={item.id})."
196488
+ }
196489
+ }));
196490
+ }
196491
+ }
196492
+ if (preamble && preamble.builderNames.length > 0) {
196493
+ flagArrayChildExpressions(children, new Set(preamble.builderNames));
196494
+ }
196495
+ if (preamble && preamble.builderNames.length > 0 && children.length === 1 && children[0].type === "component") {
196496
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
196497
+ message: "A .map() callback that builds JSX in its preamble cannot return a " + "component: the built elements would reach the component as raw HTML " + "strings on the client but as JSX elements at SSR (a silent divergence).",
196498
+ suggestion: {
196499
+ message: "Return a plain element root that embeds the array, or move the " + "building logic inside the component."
196500
+ }
196501
+ }));
196502
+ preamble = undefined;
196503
+ }
196214
196504
  let childComponent;
196215
196505
  if (children.length === 1 && children[0].type === "component") {
196216
196506
  const comp = children[0];
@@ -196230,6 +196520,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196230
196520
  const hasCalls = exprHasFunctionCalls(arrayExpr);
196231
196521
  const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
196232
196522
  const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
196523
+ const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children, new Set(preamble.declaredNames), ctx) : undefined;
196233
196524
  const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
196234
196525
  return {
196235
196526
  type: "loop",
@@ -196259,11 +196550,10 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196259
196550
  objectIteration,
196260
196551
  depth,
196261
196552
  clientOnly: isClientOnly || undefined,
196262
- mapPreamble,
196263
- templateMapPreamble,
196553
+ preamble,
196554
+ preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : undefined,
196264
196555
  paramType,
196265
196556
  indexType,
196266
- typedMapPreamble,
196267
196557
  paramBindings,
196268
196558
  arrayFreeIdentifiers: extractFreeIdentifiersFromNode(arrayExpr),
196269
196559
  flatMapCallback,
@@ -196299,47 +196589,281 @@ function containsJsx(node) {
196299
196589
  function buildFlatMapCallback(callback, body, ctx) {
196300
196590
  if (!containsJsx(body))
196301
196591
  return;
196302
- const fragments = [];
196303
- const sourceText = ctx.sourceFile.text;
196304
- const bodyStart = body.getStart(ctx.sourceFile);
196305
- const bodyEnd = body.getEnd();
196306
- const bodyText = sourceText.slice(bodyStart, bodyEnd);
196307
- const jsxNodes = [];
196308
- function collectJsx(n) {
196592
+ const leafSpans = [];
196593
+ const leafIrs = [];
196594
+ let refusalNode;
196595
+ const collectJsx = (n, underTemplate) => {
196309
196596
  if (import_typescript11.default.isJsxElement(n) || import_typescript11.default.isJsxSelfClosingElement(n) || import_typescript11.default.isJsxFragment(n)) {
196310
- jsxNodes.push({
196311
- node: n,
196312
- start: n.getStart(ctx.sourceFile) - bodyStart,
196313
- end: n.getEnd() - bodyStart
196314
- });
196597
+ if (underTemplate)
196598
+ refusalNode ??= n;
196599
+ leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
196600
+ const ir = transformNode(n, ctx);
196601
+ leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
196315
196602
  return;
196316
196603
  }
196317
- n.forEachChild(collectJsx);
196318
- }
196319
- collectJsx(body);
196320
- if (jsxNodes.length === 0)
196604
+ const inTemplate = underTemplate || import_typescript11.default.isTemplateExpression(n) || import_typescript11.default.isTaggedTemplateExpression(n);
196605
+ n.forEachChild((c) => collectJsx(c, inTemplate));
196606
+ };
196607
+ collectJsx(body, false);
196608
+ if (leafSpans.length === 0)
196321
196609
  return;
196322
- let compiledBody = "";
196323
- let lastEnd = 0;
196324
- for (let i2 = 0;i2 < jsxNodes.length; i2++) {
196325
- const { node, start, end } = jsxNodes[i2];
196326
- const placeholder = `__BF_JSX_${i2}__`;
196327
- compiledBody += bodyText.slice(lastEnd, start) + placeholder;
196328
- lastEnd = end;
196329
- const ir = transformNode(node, ctx);
196330
- fragments.push({
196331
- placeholder,
196332
- ir: ir ?? { type: "text", value: "", loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath) }
196333
- });
196610
+ if (refusalNode) {
196611
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(refusalNode, ctx.sourceFile, ctx.filePath), {
196612
+ message: "A JSX element inside a template literal in a .flatMap() callback " + "body cannot be compiled.",
196613
+ suggestion: { message: "Build the element outside the template literal." }
196614
+ }));
196615
+ return;
196616
+ }
196617
+ for (const leafIr of leafIrs) {
196618
+ if (leafIr.type !== "element") {
196619
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
196620
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
196621
+ message: "A JSX leaf produced by a .flatMap() callback must be a single " + "element — a fragment or non-element root cannot ride the keyed " + "descriptor path (each leaf hydrates and patches as one element).",
196622
+ suggestion: {
196623
+ message: "Wrap the leaf content in a single keyed element."
196624
+ }
196625
+ }));
196626
+ return;
196627
+ }
196628
+ if (flatMapLeafNeedsWiring(leafIr)) {
196629
+ const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
196630
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
196631
+ message: "A JSX element produced by a .flatMap() callback cannot carry " + "event handlers, components, nested loops, or spreads — the " + "leaf renders as a keyed HTML string with no per-element wiring.",
196632
+ suggestion: {
196633
+ message: "Restructure so the interactive element lives in a .map() body — " + "the descriptor path has no per-element wiring on any backend, " + "so /* @client */ does not lift this."
196634
+ }
196635
+ }));
196636
+ return;
196637
+ }
196334
196638
  }
196335
- compiledBody += bodyText.slice(lastEnd);
196639
+ const pieces = reconstructAsSegments(body, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
196640
+ const segments = pieces.map((piece) => {
196641
+ if ("marker" in piece)
196642
+ return { kind: "jsx", ir: leafIrs[piece.marker] };
196643
+ const tpl = rewriteBarePropRefs2(piece.js, body, ctx);
196644
+ return tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js };
196645
+ });
196336
196646
  const paramsText = callback.parameters.map((p) => p.getText(ctx.sourceFile)).join(", ");
196337
196647
  return {
196338
196648
  params: `(${paramsText})`,
196339
- body: compiledBody,
196340
- templateBody: compiledBody,
196341
- rawBody: bodyText,
196342
- fragments
196649
+ segments,
196650
+ rawBody: tsxSourceText(body.getText(ctx.sourceFile))
196651
+ };
196652
+ }
196653
+ function flatMapLeafNeedsWiring(ir) {
196654
+ switch (ir.type) {
196655
+ case "component":
196656
+ case "loop":
196657
+ return true;
196658
+ case "element":
196659
+ if (ir.events.length > 0)
196660
+ return true;
196661
+ if (ir.attrs.some((a) => a.name.startsWith("...")))
196662
+ return true;
196663
+ return ir.children.some(flatMapLeafNeedsWiring);
196664
+ case "conditional":
196665
+ return flatMapLeafNeedsWiring(ir.whenTrue) || (ir.whenFalse ? flatMapLeafNeedsWiring(ir.whenFalse) : false);
196666
+ case "fragment":
196667
+ return ir.children.some(flatMapLeafNeedsWiring);
196668
+ default:
196669
+ return false;
196670
+ }
196671
+ }
196672
+ function preambleFragmentNeedsWiring(ir) {
196673
+ switch (ir.type) {
196674
+ case "component":
196675
+ case "loop":
196676
+ return true;
196677
+ case "element":
196678
+ if (ir.events.length > 0)
196679
+ return true;
196680
+ if (ir.attrs.some((a) => a.name.startsWith("...")))
196681
+ return true;
196682
+ return ir.children.some(preambleFragmentNeedsWiring);
196683
+ case "expression":
196684
+ return ir.reactive === true;
196685
+ case "conditional":
196686
+ return preambleFragmentNeedsWiring(ir.whenTrue) || (ir.whenFalse ? preambleFragmentNeedsWiring(ir.whenFalse) : false);
196687
+ case "fragment":
196688
+ return ir.children.some(preambleFragmentNeedsWiring);
196689
+ default:
196690
+ return false;
196691
+ }
196692
+ }
196693
+ function flagArrayChildExpressions(nodes, declared) {
196694
+ for (const node of nodes) {
196695
+ switch (node.type) {
196696
+ case "expression": {
196697
+ const name = node.expr.trim();
196698
+ const refs = extractFreeIdentifiersFromText(node.expr);
196699
+ if (refs.size === 1 && refs.has(name) && declared.has(name)) {
196700
+ node.joinArrayChild = true;
196701
+ }
196702
+ break;
196703
+ }
196704
+ case "element":
196705
+ case "fragment":
196706
+ flagArrayChildExpressions(node.children, declared);
196707
+ break;
196708
+ case "conditional":
196709
+ flagArrayChildExpressions([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []], declared);
196710
+ break;
196711
+ }
196712
+ }
196713
+ }
196714
+ function collectPreambleRegions(nodes, declared, ctx) {
196715
+ const regions = [];
196716
+ const visit2 = (list) => {
196717
+ for (const node of list) {
196718
+ switch (node.type) {
196719
+ case "expression": {
196720
+ const refs = extractFreeIdentifiersFromText(node.expr);
196721
+ const usesPreambleLocal = [...refs].some((r) => declared.has(r));
196722
+ if (usesPreambleLocal) {
196723
+ if (!node.slotId)
196724
+ node.slotId = generateSlotId(ctx);
196725
+ node.preambleRegion = true;
196726
+ node.reactive = true;
196727
+ regions.push({
196728
+ slotId: node.slotId,
196729
+ expr: node.expr,
196730
+ joinArrayChild: node.joinArrayChild || undefined
196731
+ });
196732
+ }
196733
+ break;
196734
+ }
196735
+ case "element":
196736
+ case "fragment":
196737
+ visit2(node.children);
196738
+ break;
196739
+ case "conditional":
196740
+ visit2([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []]);
196741
+ break;
196742
+ }
196743
+ }
196744
+ };
196745
+ visit2(nodes);
196746
+ return regions;
196747
+ }
196748
+ function collectBindingNames(name, out) {
196749
+ if (import_typescript11.default.isIdentifier(name)) {
196750
+ out.add(name.text);
196751
+ return;
196752
+ }
196753
+ for (const el of name.elements) {
196754
+ if (import_typescript11.default.isBindingElement(el))
196755
+ collectBindingNames(el.name, out);
196756
+ }
196757
+ }
196758
+ function collectPreambleDeclaredNames(stmt, out) {
196759
+ if (import_typescript11.default.isVariableStatement(stmt)) {
196760
+ for (const decl of stmt.declarationList.declarations) {
196761
+ collectBindingNames(decl.name, out);
196762
+ }
196763
+ } else if (import_typescript11.default.isFunctionDeclaration(stmt) && stmt.name) {
196764
+ out.add(stmt.name.text);
196765
+ }
196766
+ }
196767
+ function preambleFromValueStatements(statements, ctx) {
196768
+ const segments = [];
196769
+ const typedParts = [];
196770
+ const declared = new Set;
196771
+ for (const stmt of statements) {
196772
+ collectPreambleDeclaredNames(stmt, declared);
196773
+ const js0 = ctx.getJS(stmt);
196774
+ const tjs0 = ctx.getTemplateJS(stmt);
196775
+ const raw0 = stmt.getText(ctx.sourceFile);
196776
+ const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
196777
+ const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
196778
+ typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
196779
+ segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
196780
+ }
196781
+ return {
196782
+ segments: trimPreambleSegments(segments),
196783
+ ssrText: tsxSourceText(typedParts.join(" ")),
196784
+ declaredNames: [...declared],
196785
+ builderNames: []
196786
+ };
196787
+ }
196788
+ function trimPreambleSegments(segments) {
196789
+ const last = segments[segments.length - 1];
196790
+ if (last?.kind === "js") {
196791
+ const text = last.text.trimEnd();
196792
+ const templateText = last.templateText?.trimEnd();
196793
+ segments[segments.length - 1] = templateText !== undefined ? { kind: "js", text, templateText } : { kind: "js", text };
196794
+ }
196795
+ return segments;
196796
+ }
196797
+ function buildPreambleSegments(statements, returnStmt, ctx) {
196798
+ const segments = [];
196799
+ const typedParts = [];
196800
+ const declared = new Set;
196801
+ const builders = new Set;
196802
+ let refusalNode;
196803
+ const recordBuilderTarget = (leaf, stmt) => {
196804
+ for (let n = leaf.parent;n && n !== stmt.parent; n = n.parent) {
196805
+ if (import_typescript11.default.isCallExpression(n) && import_typescript11.default.isPropertyAccessExpression(n.expression) && (n.expression.name.text === "push" || n.expression.name.text === "unshift") && import_typescript11.default.isIdentifier(n.expression.expression)) {
196806
+ builders.add(n.expression.expression.text);
196807
+ return;
196808
+ }
196809
+ if (import_typescript11.default.isVariableDeclaration(n) && import_typescript11.default.isIdentifier(n.name)) {
196810
+ builders.add(n.name.text);
196811
+ return;
196812
+ }
196813
+ }
196814
+ };
196815
+ for (const stmt of statements) {
196816
+ if (stmt === returnStmt)
196817
+ break;
196818
+ collectPreambleDeclaredNames(stmt, declared);
196819
+ const leafSpans = [];
196820
+ const leafIrs = [];
196821
+ const collect = (n, underTemplate) => {
196822
+ if (import_typescript11.default.isJsxElement(n) || import_typescript11.default.isJsxSelfClosingElement(n) || import_typescript11.default.isJsxFragment(n)) {
196823
+ if (underTemplate)
196824
+ refusalNode ??= n;
196825
+ recordBuilderTarget(n, stmt);
196826
+ leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
196827
+ const ir = transformNode(n, ctx);
196828
+ if (ir && preambleFragmentNeedsWiring(ir))
196829
+ refusalNode ??= n;
196830
+ leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
196831
+ return;
196832
+ }
196833
+ const inTemplate = underTemplate || import_typescript11.default.isTemplateExpression(n) || import_typescript11.default.isTaggedTemplateExpression(n);
196834
+ n.forEachChild((c) => collect(c, inTemplate));
196835
+ };
196836
+ collect(stmt, false);
196837
+ const raw0 = stmt.getText(ctx.sourceFile);
196838
+ typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
196839
+ if (leafSpans.length === 0) {
196840
+ const js0 = ctx.getJS(stmt);
196841
+ const tjs0 = ctx.getTemplateJS(stmt);
196842
+ const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
196843
+ const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
196844
+ segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
196845
+ continue;
196846
+ }
196847
+ const pieces = reconstructAsSegments(stmt, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
196848
+ for (const piece of pieces) {
196849
+ if ("marker" in piece) {
196850
+ segments.push({ kind: "jsx", ir: leafIrs[piece.marker] });
196851
+ } else {
196852
+ const tpl = rewriteBarePropRefs2(piece.js, stmt, ctx);
196853
+ segments.push(tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js });
196854
+ }
196855
+ }
196856
+ const sep2 = raw0.endsWith(";") ? " " : "; ";
196857
+ segments.push({ kind: "js", text: sep2 });
196858
+ }
196859
+ return {
196860
+ preamble: {
196861
+ segments: trimPreambleSegments(segments),
196862
+ ssrText: tsxSourceText(typedParts.join(" ")),
196863
+ declaredNames: [...declared],
196864
+ builderNames: [...builders]
196865
+ },
196866
+ refusalNode
196343
196867
  };
196344
196868
  }
196345
196869
  function collectNestedComponents(nodes) {
@@ -197726,6 +198250,7 @@ class BaseAdapter {
197726
198250
  // ../jsx/src/adapters/jsx-adapter.ts
197727
198251
  class JsxAdapter extends BaseAdapter {
197728
198252
  componentName = "";
198253
+ acceptsCallbackBody = () => true;
197729
198254
  formatImportSpecifiers(specifiers) {
197730
198255
  const defaultSpec = specifiers.find((s) => s.isDefault);
197731
198256
  const namespaceSpec = specifiers.find((s) => s.isNamespace);
@@ -198071,6 +198596,10 @@ export default ${this.componentName}` : "";
198071
198596
  const indexParam = loop.index ? `, ${loop.index}` : "";
198072
198597
  const children = this.renderChildren(loop.children);
198073
198598
  const safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
198599
+ const preamble = loop.preamble?.ssrText;
198600
+ if (preamble) {
198601
+ return `{${loop.array}.map((${loop.param}${indexParam}) => { ${preamble} return ${safeChildren} })}`;
198602
+ }
198074
198603
  return `{${loop.array}.map((${loop.param}${indexParam}) => ${safeChildren})}`;
198075
198604
  }
198076
198605
  renderComponent(comp) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "Test utilities for BarefootJS - IR-based component testing without a browser",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "directory": "packages/test"
40
40
  },
41
41
  "dependencies": {
42
- "@barefootjs/jsx": "0.26.2"
42
+ "@barefootjs/jsx": "0.26.4"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"