@barefootjs/test 0.33.0 → 0.33.1
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 +76 -5
- 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) {
|
|
@@ -198063,6 +198104,35 @@ function getStringValue(node) {
|
|
|
198063
198104
|
}
|
|
198064
198105
|
return null;
|
|
198065
198106
|
}
|
|
198107
|
+
function unwrapTransparentTsWrappers(node) {
|
|
198108
|
+
let n = node;
|
|
198109
|
+
while (import_typescript13.default.isParenthesizedExpression(n) || import_typescript13.default.isAsExpression(n) || import_typescript13.default.isSatisfiesExpression(n) || import_typescript13.default.isNonNullExpression(n)) {
|
|
198110
|
+
n = n.expression;
|
|
198111
|
+
}
|
|
198112
|
+
return n;
|
|
198113
|
+
}
|
|
198114
|
+
function expressionWrapsJsx(node) {
|
|
198115
|
+
const n = unwrapTransparentTsWrappers(node);
|
|
198116
|
+
if (import_typescript13.default.isJsxElement(n) || import_typescript13.default.isJsxSelfClosingElement(n) || import_typescript13.default.isJsxFragment(n))
|
|
198117
|
+
return true;
|
|
198118
|
+
if (import_typescript13.default.isConditionalExpression(n)) {
|
|
198119
|
+
return expressionWrapsJsx(n.whenTrue) || expressionWrapsJsx(n.whenFalse);
|
|
198120
|
+
}
|
|
198121
|
+
if (import_typescript13.default.isArrayLiteralExpression(n)) {
|
|
198122
|
+
return n.elements.some((el) => expressionWrapsJsx(import_typescript13.default.isSpreadElement(el) ? el.expression : el));
|
|
198123
|
+
}
|
|
198124
|
+
return false;
|
|
198125
|
+
}
|
|
198126
|
+
function reportNakedJsxWrapperProp(ctx, attr, propName, jsxExpr) {
|
|
198127
|
+
const shape = import_typescript13.default.isConditionalExpression(jsxExpr) ? "a ternary" : "an array literal";
|
|
198128
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(attr, ctx.sourceFile, ctx.filePath), {
|
|
198129
|
+
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.`,
|
|
198130
|
+
suggestion: {
|
|
198131
|
+
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).`,
|
|
198132
|
+
escape: [{ kind: "rewrite" }]
|
|
198133
|
+
}
|
|
198134
|
+
}));
|
|
198135
|
+
}
|
|
198066
198136
|
function processComponentProps(attributes, ctx) {
|
|
198067
198137
|
const props = [];
|
|
198068
198138
|
for (const attr of attributes.properties) {
|
|
@@ -198074,10 +198144,7 @@ function processComponentProps(attributes, ctx) {
|
|
|
198074
198144
|
continue;
|
|
198075
198145
|
const name = attr.name.getText(ctx.sourceFile);
|
|
198076
198146
|
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
|
-
}
|
|
198147
|
+
const jsxExpr = unwrapTransparentTsWrappers(attr.initializer.expression);
|
|
198081
198148
|
if (import_typescript13.default.isJsxElement(jsxExpr) || import_typescript13.default.isJsxSelfClosingElement(jsxExpr) || import_typescript13.default.isJsxFragment(jsxExpr)) {
|
|
198082
198149
|
const prevInsideComponentChildren = ctx.insideComponentChildren;
|
|
198083
198150
|
ctx.insideComponentChildren = true;
|
|
@@ -198092,6 +198159,10 @@ function processComponentProps(attributes, ctx) {
|
|
|
198092
198159
|
continue;
|
|
198093
198160
|
}
|
|
198094
198161
|
}
|
|
198162
|
+
if ((import_typescript13.default.isConditionalExpression(jsxExpr) || import_typescript13.default.isArrayLiteralExpression(jsxExpr)) && expressionWrapsJsx(jsxExpr)) {
|
|
198163
|
+
reportNakedJsxWrapperProp(ctx, attr, name, jsxExpr);
|
|
198164
|
+
continue;
|
|
198165
|
+
}
|
|
198095
198166
|
}
|
|
198096
198167
|
let value = getAttributeValue(attr, ctx);
|
|
198097
198168
|
if (value.kind === "template") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/test",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.1",
|
|
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.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.0.0"
|