@barefootjs/test 0.26.3 → 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 +192 -1
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -196006,6 +196006,87 @@ function checkLoopKey(callback, ctx, isNested) {
196006
196006
  return;
196007
196007
  }
196008
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
+ }
196009
196090
  function loopBodyIsMultiRoot(children) {
196010
196091
  const real = children.filter((c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim()));
196011
196092
  if (real.length === 0)
@@ -196051,6 +196132,7 @@ function extractItemConditionalKey(cond) {
196051
196132
  }
196052
196133
  function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196053
196134
  const isNested = ctx.loopParams.size > 0;
196135
+ const diagCountAtEntry = ctx.analyzer.errors.length;
196054
196136
  const depth = ctx.loopDepth;
196055
196137
  const propAccess = node.expression;
196056
196138
  const mapSource = propAccess.expression;
@@ -196337,12 +196419,34 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196337
196419
  }
196338
196420
  }
196339
196421
  }
196340
- if (method === "flatMap" && children.length === 0) {
196422
+ if (method === "flatMap" && children.length === 0 && !flatMapProjectionCall(body)) {
196341
196423
  flatMapCallback = buildFlatMapCallback(callback, body, ctx);
196342
196424
  }
196343
196425
  } else {
196344
196426
  tryTransformRenderableBody(body);
196345
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
+ }
196346
196450
  if (paramBindings) {
196347
196451
  for (const b of paramBindings)
196348
196452
  ctx.loopParams.delete(b.name);
@@ -196354,6 +196458,16 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196354
196458
  ctx.loopDepth--;
196355
196459
  }
196356
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
+ }
196357
196471
  return null;
196358
196472
  }
196359
196473
  if (import_typescript11.default.isArrowFunction(node.arguments[0]) && children.length > 0) {
@@ -196406,6 +196520,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196406
196520
  const hasCalls = exprHasFunctionCalls(arrayExpr);
196407
196521
  const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
196408
196522
  const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
196523
+ const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children, new Set(preamble.declaredNames), ctx) : undefined;
196409
196524
  const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
196410
196525
  return {
196411
196526
  type: "loop",
@@ -196436,6 +196551,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
196436
196551
  depth,
196437
196552
  clientOnly: isClientOnly || undefined,
196438
196553
  preamble,
196554
+ preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : undefined,
196439
196555
  paramType,
196440
196556
  indexType,
196441
196557
  paramBindings,
@@ -196498,6 +196614,28 @@ function buildFlatMapCallback(callback, body, ctx) {
196498
196614
  }));
196499
196615
  return;
196500
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
+ }
196638
+ }
196501
196639
  const pieces = reconstructAsSegments(body, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
196502
196640
  const segments = pieces.map((piece) => {
196503
196641
  if ("marker" in piece)
@@ -196512,6 +196650,25 @@ function buildFlatMapCallback(callback, body, ctx) {
196512
196650
  rawBody: tsxSourceText(body.getText(ctx.sourceFile))
196513
196651
  };
196514
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
+ }
196515
196672
  function preambleFragmentNeedsWiring(ir) {
196516
196673
  switch (ir.type) {
196517
196674
  case "component":
@@ -196554,6 +196711,40 @@ function flagArrayChildExpressions(nodes, declared) {
196554
196711
  }
196555
196712
  }
196556
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
+ }
196557
196748
  function collectBindingNames(name, out) {
196558
196749
  if (import_typescript11.default.isIdentifier(name)) {
196559
196750
  out.add(name.text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.26.3",
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.3"
42
+ "@barefootjs/jsx": "0.26.4"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"