@barefootjs/test 0.33.0 → 0.33.2

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 +106 -6
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -190096,6 +190096,7 @@ var ErrorCodes = {
190096
190096
  MISSING_KEY_IN_LIST: "BF023",
190097
190097
  MISSING_KEY_IN_NESTED_LIST: "BF024",
190098
190098
  UNSUPPORTED_DESTRUCTURE_REST: "BF025",
190099
+ RETURN_VALUE_NOT_JSX: "BF027",
190099
190100
  PROPS_DESTRUCTURING: "BF043",
190100
190101
  SIGNAL_GETTER_NOT_CALLED: "BF044",
190101
190102
  JSX_IN_LOCAL_FUNCTION: "BF045",
@@ -190127,6 +190128,7 @@ var errorMessages = {
190127
190128
  [ErrorCodes.MISSING_KEY_IN_LIST]: "Missing key attribute in list rendering. Add a key prop for efficient updates",
190128
190129
  [ErrorCodes.MISSING_KEY_IN_NESTED_LIST]: "Nested .map() loop requires key attribute for event delegation. Add a key prop to elements in the inner loop",
190129
190130
  [ErrorCodes.UNSUPPORTED_DESTRUCTURE_REST]: "Computed property key in .map() callback destructure is not supported. Rewrite the callback to destructure explicit bindings (e.g., `({ a, b }) => ...`) so the compiler can rewrite references to per-item signal accessors.",
190131
+ [ErrorCodes.RETURN_VALUE_NOT_JSX]: "Component's return value is not recognized as JSX — return the JSX expression directly instead of binding it to a local variable first.",
190130
190132
  [ErrorCodes.PROPS_DESTRUCTURING]: "Props destructuring in function parameters breaks reactivity. Use props object directly.",
190131
190133
  [ErrorCodes.SIGNAL_GETTER_NOT_CALLED]: "Signal/memo getter passed without calling it. Use getter() to read the value.",
190132
190134
  [ErrorCodes.JSX_IN_LOCAL_FUNCTION]: "Local function returns JSX but cannot be inlined. Extract it as a top-level PascalCase component or use a single return statement.",
@@ -190696,6 +190698,14 @@ function visitComponentBody(node, ctx) {
190696
190698
  }
190697
190699
  }
190698
190700
  if (isTopLevel && (import_typescript9.default.isTryStatement(node) || import_typescript9.default.isSwitchStatement(node) || import_typescript9.default.isForStatement(node) || import_typescript9.default.isForInStatement(node) || import_typescript9.default.isForOfStatement(node) || import_typescript9.default.isWhileStatement(node) || import_typescript9.default.isDoStatement(node) || import_typescript9.default.isThrowStatement(node) || import_typescript9.default.isBlock(node) && node.parent === ctx.componentBodyBlock)) {
190701
+ if (import_typescript9.default.isBlock(node)) {
190702
+ const returnedLocal = findBlockBodyReturnedJsxLocalName(node);
190703
+ if (returnedLocal) {
190704
+ ctx.errors.push(createError(ErrorCodes.RETURN_VALUE_NOT_JSX, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
190705
+ message: `Component '${ctx.componentName ?? "(unknown)"}' return value is not recognized ` + `as JSX — return the JSX expression directly instead of binding it to a local ` + `variable first (\`return ${returnedLocal}\` after \`const ${returnedLocal} = ` + `<jsx/>\` is not resolved at return position).`
190706
+ }));
190707
+ }
190708
+ }
190699
190709
  collectInitStatement(node, ctx);
190700
190710
  return;
190701
190711
  }
@@ -190731,6 +190741,31 @@ function unwrapJsxTransparent(expr) {
190731
190741
  }
190732
190742
  return current;
190733
190743
  }
190744
+ function findBlockBodyReturnedJsxLocalName(block) {
190745
+ const stmts = block.statements;
190746
+ const last = stmts[stmts.length - 1];
190747
+ if (!last || !import_typescript9.default.isReturnStatement(last) || !last.expression)
190748
+ return null;
190749
+ const returned = unwrapJsxTransparent(last.expression);
190750
+ if (!import_typescript9.default.isIdentifier(returned))
190751
+ return null;
190752
+ const name = returned.text;
190753
+ for (const stmt of stmts) {
190754
+ if (!import_typescript9.default.isVariableStatement(stmt))
190755
+ continue;
190756
+ for (const decl of stmt.declarationList.declarations) {
190757
+ if (!import_typescript9.default.isIdentifier(decl.name) || decl.name.text !== name || !decl.initializer)
190758
+ continue;
190759
+ let init = decl.initializer;
190760
+ while (import_typescript9.default.isParenthesizedExpression(init))
190761
+ init = init.expression;
190762
+ if (import_typescript9.default.isJsxElement(init) || import_typescript9.default.isJsxSelfClosingElement(init) || import_typescript9.default.isJsxFragment(init) || initializerShapeContainsJsx(init) || isMapLikeCallWithJsx(init)) {
190763
+ return name;
190764
+ }
190765
+ }
190766
+ }
190767
+ return null;
190768
+ }
190734
190769
  function extractJsxFromExpression(expr) {
190735
190770
  const inner = unwrapJsxTransparent(expr);
190736
190771
  if (import_typescript9.default.isJsxElement(inner) || import_typescript9.default.isJsxFragment(inner) || import_typescript9.default.isJsxSelfClosingElement(inner)) {
@@ -194947,8 +194982,14 @@ function buildIRRoot(analyzer) {
194947
194982
  }
194948
194983
  ctx.isRoot = false;
194949
194984
  const ir = transformJsxExpression(jsxReturn, ctx);
194950
- if (ir === null)
194985
+ if (ir === null) {
194986
+ if (import_typescript13.default.isIdentifier(jsxReturn) && (analyzer.jsxConstants.has(jsxReturn.text) || analyzer.inlineableJsxConsts.has(jsxReturn.text))) {
194987
+ analyzer.errors.push(createError(ErrorCodes.RETURN_VALUE_NOT_JSX, getSourceLocation(jsxReturn, analyzer.sourceFile, analyzer.filePath), {
194988
+ message: `Component '${analyzer.componentName ?? "(unknown)"}' return value is not recognized ` + `as JSX — return the JSX expression directly instead of binding it to a local variable ` + `first (\`return ${jsxReturn.text}\` after \`const ${jsxReturn.text} = <jsx/>\` is not ` + `resolved at return position).`
194989
+ }));
194990
+ }
194951
194991
  return null;
194992
+ }
194952
194993
  return wrapInScopeElement(ir);
194953
194994
  }
194954
194995
  function needsScopeWrapper(ir) {
@@ -195398,6 +195439,31 @@ function unwrapHoistedFragment(node) {
195398
195439
  return node;
195399
195440
  return { ...only, needsScope: true };
195400
195441
  }
195442
+ function markDataKeyCarrier(children) {
195443
+ for (let i2 = 0;i2 < children.length; i2++) {
195444
+ const marked = markCarrierIn(children[i2]);
195445
+ if (!marked)
195446
+ continue;
195447
+ const out = children.slice();
195448
+ out[i2] = marked;
195449
+ return out;
195450
+ }
195451
+ return children;
195452
+ }
195453
+ function markCarrierIn(node) {
195454
+ if (node.type === "element") {
195455
+ return { ...node, carriesDataKey: true };
195456
+ }
195457
+ if (node.type === "conditional") {
195458
+ const cond = node;
195459
+ const whenTrue = markCarrierIn(cond.whenTrue);
195460
+ const whenFalse = markCarrierIn(cond.whenFalse);
195461
+ if (!whenTrue && !whenFalse)
195462
+ return null;
195463
+ return { ...cond, whenTrue: whenTrue ?? cond.whenTrue, whenFalse: whenFalse ?? cond.whenFalse };
195464
+ }
195465
+ return null;
195466
+ }
195401
195467
  function transformFragment(node, ctx) {
195402
195468
  const isFragmentRoot = ctx.isRoot;
195403
195469
  const isTransparent = isFragmentRoot && isTransparentFragment(node, ctx);
@@ -195408,7 +195474,7 @@ function transformFragment(node, ctx) {
195408
195474
  const needsScopeComment = isFragmentRoot && !isTransparent || undefined;
195409
195475
  return {
195410
195476
  type: "fragment",
195411
- children,
195477
+ children: needsScopeComment ? markDataKeyCarrier(children) : children,
195412
195478
  transparent: isTransparent || undefined,
195413
195479
  needsScopeComment,
195414
195480
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath)
@@ -198063,6 +198129,35 @@ function getStringValue(node) {
198063
198129
  }
198064
198130
  return null;
198065
198131
  }
198132
+ function unwrapTransparentTsWrappers(node) {
198133
+ let n = node;
198134
+ while (import_typescript13.default.isParenthesizedExpression(n) || import_typescript13.default.isAsExpression(n) || import_typescript13.default.isSatisfiesExpression(n) || import_typescript13.default.isNonNullExpression(n)) {
198135
+ n = n.expression;
198136
+ }
198137
+ return n;
198138
+ }
198139
+ function expressionWrapsJsx(node) {
198140
+ const n = unwrapTransparentTsWrappers(node);
198141
+ if (import_typescript13.default.isJsxElement(n) || import_typescript13.default.isJsxSelfClosingElement(n) || import_typescript13.default.isJsxFragment(n))
198142
+ return true;
198143
+ if (import_typescript13.default.isConditionalExpression(n)) {
198144
+ return expressionWrapsJsx(n.whenTrue) || expressionWrapsJsx(n.whenFalse);
198145
+ }
198146
+ if (import_typescript13.default.isArrayLiteralExpression(n)) {
198147
+ return n.elements.some((el) => expressionWrapsJsx(import_typescript13.default.isSpreadElement(el) ? el.expression : el));
198148
+ }
198149
+ return false;
198150
+ }
198151
+ function reportNakedJsxWrapperProp(ctx, attr, propName, jsxExpr) {
198152
+ const shape = import_typescript13.default.isConditionalExpression(jsxExpr) ? "a ternary" : "an array literal";
198153
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(attr, ctx.sourceFile, ctx.filePath), {
198154
+ message: `Prop '${propName}' is ${shape} wrapping JSX (${jsxExpr.getText(ctx.sourceFile)}). ` + `This shape is not compiled — only a JSX element/fragment given DIRECTLY as the prop value is.`,
198155
+ suggestion: {
198156
+ message: `Move the conditional/array out of the prop position: compute it in a local ` + `const and pass it as the component's children instead of a named prop ` + `(e.g. const ${propName} = ${jsxExpr.getText(ctx.sourceFile)}; <Comp>{${propName}}</Comp>). ` + `Wrapping the ternary/array in a fragment at the prop position ` + `(${propName}={<>{${jsxExpr.getText(ctx.sourceFile)}}</>}) is NOT a safe escape here: it compiles, ` + `but the child's own reactive prop getter receives the branch's HTML unbranded and re-escapes it as ` + `text on the child's very next reactive run, corrupting the DOM (a narrower gap #2651's door ` + `inventory left open — tracked separately).`,
198157
+ escape: [{ kind: "rewrite" }]
198158
+ }
198159
+ }));
198160
+ }
198066
198161
  function processComponentProps(attributes, ctx) {
198067
198162
  const props = [];
198068
198163
  for (const attr of attributes.properties) {
@@ -198074,10 +198169,7 @@ function processComponentProps(attributes, ctx) {
198074
198169
  continue;
198075
198170
  const name = attr.name.getText(ctx.sourceFile);
198076
198171
  if (attr.initializer && import_typescript13.default.isJsxExpression(attr.initializer) && attr.initializer.expression) {
198077
- let jsxExpr = attr.initializer.expression;
198078
- while (import_typescript13.default.isParenthesizedExpression(jsxExpr)) {
198079
- jsxExpr = jsxExpr.expression;
198080
- }
198172
+ const jsxExpr = unwrapTransparentTsWrappers(attr.initializer.expression);
198081
198173
  if (import_typescript13.default.isJsxElement(jsxExpr) || import_typescript13.default.isJsxSelfClosingElement(jsxExpr) || import_typescript13.default.isJsxFragment(jsxExpr)) {
198082
198174
  const prevInsideComponentChildren = ctx.insideComponentChildren;
198083
198175
  ctx.insideComponentChildren = true;
@@ -198092,6 +198184,10 @@ function processComponentProps(attributes, ctx) {
198092
198184
  continue;
198093
198185
  }
198094
198186
  }
198187
+ if ((import_typescript13.default.isConditionalExpression(jsxExpr) || import_typescript13.default.isArrayLiteralExpression(jsxExpr)) && expressionWrapsJsx(jsxExpr)) {
198188
+ reportNakedJsxWrapperProp(ctx, attr, name, jsxExpr);
198189
+ continue;
198190
+ }
198095
198191
  }
198096
198192
  let value = getAttributeValue(attr, ctx);
198097
198193
  if (value.kind === "template") {
@@ -198474,6 +198570,10 @@ function buildIfStatementChain(analyzer, ctx, opts) {
198474
198570
  return alternate;
198475
198571
  }
198476
198572
 
198573
+ // ../jsx/src/ir-to-client-js/prop-handling.ts
198574
+ var _localConstantValuesCache = new WeakMap;
198575
+ var _restSpreadNamesCache = new WeakMap;
198576
+
198477
198577
  // ../jsx/src/ir-to-client-js/control-flow/stringify/template-parse.ts
198478
198578
  var SVG_ROOT_TAGS = new Set([
198479
198579
  "svg",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.33.0",
3
+ "version": "0.33.2",
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.33.0"
42
+ "@barefootjs/jsx": "0.33.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"