@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.
- package/dist/index.js +106 -6
- 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
|
-
|
|
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.
|
|
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.
|
|
42
|
+
"@barefootjs/jsx": "0.33.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.0.0"
|