@barefootjs/test 0.24.1 → 0.25.0

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 +224 -54
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -189766,7 +189766,8 @@ var ErrorCodes = {
189766
189766
  UNRECOGNIZED_REACTIVE_FACTORY: "BF110",
189767
189767
  REACTIVE_FACTORY_RENAME_UNSUPPORTED: "BF111",
189768
189768
  REACTIVE_FACTORY_MODULE_CAPTURE: "BF112",
189769
- REACTIVE_FACTORY_IMPORT_COLLISION: "BF113"
189769
+ REACTIVE_FACTORY_IMPORT_COLLISION: "BF113",
189770
+ REACTIVE_FACTORY_PARAM_SHADOWED: "BF114"
189770
189771
  };
189771
189772
  var errorMessages = {
189772
189773
  [ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
@@ -189793,7 +189794,8 @@ var errorMessages = {
189793
189794
  [ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY]: "Tuple destructuring of a non-reactive factory call. The compiler only recognizes createSignal / createMemo calls and same-file helpers that wrap them with a single `return [a, b]` exit.",
189794
189795
  [ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED]: "Reactive factory object return/destructure must use shorthand properties only. " + "Property renames (`{ lists: myLists }`), defaults, and rest elements are not " + "supported — destructure with the factory's own property names.",
189795
189796
  [ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE]: "Imported reactive factory references bindings from its own module scope, so its " + "body cannot be inlined into the component file. Move those helpers into the " + "component file, pass them to the factory as parameters, or define the factory " + "in the component file.",
189796
- [ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION]: "Inlining an imported reactive factory requires re-importing one of its helper " + "imports into this file, but that name is already bound here to something else. " + "Rename the conflicting binding in this file, or alias the import in the factory's own file."
189797
+ [ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION]: "Inlining an imported reactive factory requires re-importing one of its helper " + "imports into this file, but that name is already bound here to something else. " + "Rename the conflicting binding in this file, or alias the import in the factory's own file.",
189798
+ [ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED]: "Reactive factory parameter is shadowed by a nested declaration inside the factory body, so argument substitution at the inline site would be ambiguous. Rename the inner binding so it does not collide with the parameter."
189797
189799
  };
189798
189800
  function createError(code2, loc, options) {
189799
189801
  if (code2 === undefined || !(code2 in errorMessages)) {
@@ -192293,6 +192295,7 @@ function collectEntryBindingNames(sf) {
192293
192295
  visit2(sf);
192294
192296
  return names;
192295
192297
  }
192298
+ var MAX_REEXPORT_HOPS = 1;
192296
192299
  function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192297
192300
  const candidateCallees = new Set;
192298
192301
  function collectCandidates(node) {
@@ -192336,29 +192339,28 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192336
192339
  const entryBindingNames = collectEntryBindingNames(entrySourceFile);
192337
192340
  const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
192338
192341
  const plannedInjections = new Map;
192339
- for (const { src, specs } of importsToCheck) {
192340
- const resolved = resolveRelativeImportToFile(src, filePath);
192341
- if (!resolved)
192342
- continue;
192342
+ const helperCache = new Map;
192343
+ function loadHelperFile(abs) {
192344
+ const cached = helperCache.get(abs);
192345
+ if (cached !== undefined)
192346
+ return cached;
192343
192347
  let content;
192344
192348
  try {
192345
- content = fs.readFileSync(resolved, "utf8");
192349
+ content = fs.readFileSync(abs, "utf8");
192346
192350
  } catch {
192347
- continue;
192351
+ helperCache.set(abs, null);
192352
+ return null;
192348
192353
  }
192349
- const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
192350
192354
  const hasAnyPrimitiveText = [...REACTIVE_PRIMITIVES].some((p) => content.includes(p));
192351
- if (!hasAnyPrimitiveText) {
192352
- for (const spec of specs) {
192353
- if (!alreadyKnown(spec.local))
192354
- result.cleanFactoryImports.add(spec.local);
192355
- }
192356
- continue;
192355
+ const hasReexportText = content.includes("export") && content.includes("from");
192356
+ if (!hasAnyPrimitiveText && !hasReexportText) {
192357
+ helperCache.set(abs, "clean");
192358
+ return "clean";
192357
192359
  }
192358
- const helperSf = import_typescript8.default.createSourceFile(resolved + ".prescan", content, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192360
+ const sf = import_typescript8.default.createSourceFile(abs + ".prescan", content, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192359
192361
  const localFns = new Map;
192360
192362
  const exportedFns = new Map;
192361
- for (const stmt of helperSf.statements) {
192363
+ for (const stmt of sf.statements) {
192362
192364
  if (import_typescript8.default.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
192363
192365
  localFns.set(stmt.name.text, stmt);
192364
192366
  const hasExportModifier = stmt.modifiers?.some((m) => m.kind === import_typescript8.default.SyntaxKind.ExportKeyword) ?? false;
@@ -192368,27 +192370,89 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192368
192370
  }
192369
192371
  }
192370
192372
  }
192371
- for (const stmt of helperSf.statements) {
192372
- if (import_typescript8.default.isExportDeclaration(stmt) && stmt.exportClause && import_typescript8.default.isNamedExports(stmt.exportClause) && !stmt.moduleSpecifier && !stmt.isTypeOnly) {
192373
+ const reexports = new Map;
192374
+ let hasStarReexport = false;
192375
+ for (const stmt of sf.statements) {
192376
+ if (!import_typescript8.default.isExportDeclaration(stmt) || stmt.isTypeOnly)
192377
+ continue;
192378
+ if (!stmt.moduleSpecifier) {
192379
+ if (stmt.exportClause && import_typescript8.default.isNamedExports(stmt.exportClause)) {
192380
+ for (const el of stmt.exportClause.elements) {
192381
+ if (el.isTypeOnly)
192382
+ continue;
192383
+ const fn = localFns.get((el.propertyName ?? el.name).text);
192384
+ if (fn)
192385
+ exportedFns.set(el.name.text, fn);
192386
+ }
192387
+ }
192388
+ continue;
192389
+ }
192390
+ if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192391
+ continue;
192392
+ if (stmt.exportClause && import_typescript8.default.isNamedExports(stmt.exportClause)) {
192373
192393
  for (const el of stmt.exportClause.elements) {
192374
192394
  if (el.isTypeOnly)
192375
192395
  continue;
192376
- const fn = localFns.get((el.propertyName ?? el.name).text);
192377
- if (fn)
192378
- exportedFns.set(el.name.text, fn);
192396
+ reexports.set(el.name.text, {
192397
+ source: stmt.moduleSpecifier.text,
192398
+ innerName: (el.propertyName ?? el.name).text
192399
+ });
192379
192400
  }
192380
- }
192401
+ } else if (!stmt.exportClause) {
192402
+ hasStarReexport = true;
192403
+ }
192404
+ }
192405
+ const moduleBindings = collectHelperModuleValueBindings(sf);
192406
+ const info = { sf, exportedFns, reexports, hasStarReexport, moduleBindings };
192407
+ helperCache.set(abs, info);
192408
+ return info;
192409
+ }
192410
+ function lookupExportedFactory(abs, exportedName, visited, hopsLeft) {
192411
+ if (visited.has(abs))
192412
+ return { kind: "unknown" };
192413
+ visited.add(abs);
192414
+ const file = loadHelperFile(abs);
192415
+ if (file === null)
192416
+ return { kind: "unknown" };
192417
+ if (file === "clean")
192418
+ return { kind: "clean" };
192419
+ const fn = file.exportedFns.get(exportedName);
192420
+ if (fn)
192421
+ return { kind: "fn", fn, file, definingPath: abs };
192422
+ const re = file.reexports.get(exportedName);
192423
+ if (re) {
192424
+ if (hopsLeft <= 0)
192425
+ return { kind: "unknown" };
192426
+ if (!re.source.startsWith("./") && !re.source.startsWith("../"))
192427
+ return { kind: "unknown" };
192428
+ const target = resolveRelativeImportToFile(re.source, abs);
192429
+ if (!target)
192430
+ return { kind: "unknown" };
192431
+ return lookupExportedFactory(target, re.innerName, visited, hopsLeft - 1);
192381
192432
  }
192382
- const moduleBindings = collectHelperModuleValueBindings(helperSf);
192433
+ if (file.hasStarReexport)
192434
+ return { kind: "unknown" };
192435
+ return { kind: "clean" };
192436
+ }
192437
+ for (const { src, specs } of importsToCheck) {
192438
+ const resolved = resolveRelativeImportToFile(src, filePath);
192439
+ if (!resolved)
192440
+ continue;
192441
+ const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
192383
192442
  for (const spec of specs) {
192384
192443
  if (alreadyKnown(spec.local))
192385
192444
  continue;
192386
- const fn = exportedFns.get(spec.exported);
192387
- if (!fn) {
192445
+ const found = lookupExportedFactory(resolved, spec.exported, new Set, MAX_REEXPORT_HOPS);
192446
+ if (found.kind === "clean") {
192388
192447
  result.cleanFactoryImports.add(spec.local);
192389
192448
  continue;
192390
192449
  }
192391
- const det = detectReactiveFactory(fn, helperSf, resolved);
192450
+ if (found.kind === "unknown")
192451
+ continue;
192452
+ const { fn, file, definingPath } = found;
192453
+ const helperSf = file.sf;
192454
+ const moduleBindings = file.moduleBindings;
192455
+ const det = detectReactiveFactory(fn, helperSf, definingPath);
192392
192456
  if (!det) {
192393
192457
  result.cleanFactoryImports.add(spec.local);
192394
192458
  continue;
@@ -192417,7 +192481,7 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192417
192481
  let specifier;
192418
192482
  let targetKey;
192419
192483
  if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
192420
- const abs = resolveRelativeImportToFile(ref.source, resolved);
192484
+ const abs = resolveRelativeImportToFile(ref.source, definingPath);
192421
192485
  if (!abs) {
192422
192486
  declinedEntry = {
192423
192487
  code: "BF112",
@@ -192455,7 +192519,7 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192455
192519
  }
192456
192520
  for (const [name, id] of pending)
192457
192521
  plannedInjections.set(name, id);
192458
- det.info.sourceFilePath = resolved;
192522
+ det.info.sourceFilePath = definingPath;
192459
192523
  if (required.length > 0)
192460
192524
  det.info.requiredImports = required;
192461
192525
  result.factories.set(spec.local, det.info);
@@ -192553,6 +192617,17 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192553
192617
  if (!hasReactiveCall)
192554
192618
  return null;
192555
192619
  const loc = getSourceLocation(node, sourceFile, filePath);
192620
+ let totalReturnCount = 0;
192621
+ function countReturns(n) {
192622
+ if (import_typescript8.default.isFunctionLike(n))
192623
+ return;
192624
+ if (import_typescript8.default.isReturnStatement(n)) {
192625
+ totalReturnCount++;
192626
+ return;
192627
+ }
192628
+ import_typescript8.default.forEachChild(n, countReturns);
192629
+ }
192630
+ import_typescript8.default.forEachChild(node.body, countReturns);
192556
192631
  let returnExpr = null;
192557
192632
  let returnCount = 0;
192558
192633
  for (const stmt of node.body.statements) {
@@ -192570,7 +192645,7 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192570
192645
  expr = expr.expression;
192571
192646
  returnExpr = expr;
192572
192647
  }
192573
- if (returnCount !== 1 || !returnExpr)
192648
+ if (totalReturnCount !== 1 || returnCount !== 1 || !returnExpr)
192574
192649
  return { kind: "reactive-shaped" };
192575
192650
  const returnTupleIdentifiers = [];
192576
192651
  let returnKind;
@@ -192604,6 +192679,14 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192604
192679
  } else {
192605
192680
  return { kind: "reactive-shaped" };
192606
192681
  }
192682
+ const params = [];
192683
+ for (const p of node.parameters) {
192684
+ if (import_typescript8.default.isIdentifier(p.name)) {
192685
+ params.push(p.name.text);
192686
+ continue;
192687
+ }
192688
+ return { kind: "reactive-shaped" };
192689
+ }
192607
192690
  const localBindings = [];
192608
192691
  for (const stmt of node.body.statements) {
192609
192692
  if (import_typescript8.default.isVariableStatement(stmt)) {
@@ -192614,15 +192697,95 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192614
192697
  localBindings.push(stmt.name.text);
192615
192698
  }
192616
192699
  }
192617
- const bodyStatements = node.body.statements.filter((s) => !import_typescript8.default.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
192700
+ const relevantNames = new Set([...params, ...localBindings, ...returnTupleIdentifiers]);
192701
+ const renameSites = [];
192702
+ let shadowedParam = null;
192703
+ function collectRenameSites(root, toBodyOffset) {
192704
+ function push(id, form) {
192705
+ renameSites.push({
192706
+ name: id.text,
192707
+ start: toBodyOffset(id.getStart(sourceFile)),
192708
+ end: toBodyOffset(id.getEnd()),
192709
+ form
192710
+ });
192711
+ }
192712
+ function classify(id) {
192713
+ if (!relevantNames.has(id.text))
192714
+ return;
192715
+ const p = id.parent;
192716
+ if (import_typescript8.default.isPropertyAccessExpression(p) && p.name === id)
192717
+ return;
192718
+ if (import_typescript8.default.isPropertyAssignment(p) && p.name === id)
192719
+ return;
192720
+ if (import_typescript8.default.isBindingElement(p) && p.propertyName === id)
192721
+ return;
192722
+ if ((import_typescript8.default.isMethodDeclaration(p) || import_typescript8.default.isGetAccessorDeclaration(p) || import_typescript8.default.isSetAccessorDeclaration(p) || import_typescript8.default.isPropertyDeclaration(p) || import_typescript8.default.isEnumMember(p)) && p.name === id)
192723
+ return;
192724
+ if (import_typescript8.default.isJsxAttribute(p) && p.name === id)
192725
+ return;
192726
+ if (import_typescript8.default.isLabeledStatement(p) && p.label === id || (import_typescript8.default.isBreakStatement(p) || import_typescript8.default.isContinueStatement(p)) && p.label === id)
192727
+ return;
192728
+ if ((import_typescript8.default.isJsxOpeningElement(p) || import_typescript8.default.isJsxSelfClosingElement(p) || import_typescript8.default.isJsxClosingElement(p)) && p.tagName === id && /^[a-z]/.test(id.text))
192729
+ return;
192730
+ if (import_typescript8.default.isShorthandPropertyAssignment(p) && p.name === id) {
192731
+ push(id, "shorthand");
192732
+ return;
192733
+ }
192734
+ if (import_typescript8.default.isBindingElement(p) && p.name === id && !p.propertyName && import_typescript8.default.isObjectBindingPattern(p.parent)) {
192735
+ if (params.includes(id.text))
192736
+ shadowedParam = id.text;
192737
+ push(id, "shorthand");
192738
+ return;
192739
+ }
192740
+ const isDecl = (import_typescript8.default.isVariableDeclaration(p) || import_typescript8.default.isParameter(p) || import_typescript8.default.isBindingElement(p) || import_typescript8.default.isFunctionDeclaration(p) || import_typescript8.default.isFunctionExpression(p) || import_typescript8.default.isClassDeclaration(p) || import_typescript8.default.isClassExpression(p)) && p.name === id;
192741
+ if (isDecl && params.includes(id.text))
192742
+ shadowedParam = id.text;
192743
+ push(id, "plain");
192744
+ }
192745
+ function visit2(n) {
192746
+ if (import_typescript8.default.isTypeNode(n) || import_typescript8.default.isTypeParameterDeclaration(n) || import_typescript8.default.isTypeAliasDeclaration(n) || import_typescript8.default.isInterfaceDeclaration(n))
192747
+ return;
192748
+ if (import_typescript8.default.isIdentifier(n)) {
192749
+ classify(n);
192750
+ return;
192751
+ }
192752
+ import_typescript8.default.forEachChild(n, visit2);
192753
+ }
192754
+ visit2(root);
192755
+ }
192756
+ const keptStatements = node.body.statements.filter((s) => !import_typescript8.default.isReturnStatement(s));
192757
+ const pieces = [];
192758
+ let base = 0;
192759
+ for (const stmt of keptStatements) {
192760
+ const text = stmt.getText(sourceFile);
192761
+ const stmtStart = stmt.getStart(sourceFile);
192762
+ collectRenameSites(stmt, (pos) => pos - stmtStart + base);
192763
+ pieces.push(text);
192764
+ base += text.length + 1;
192765
+ }
192766
+ const bodyStatements = pieces.join(`
192618
192767
  `);
192619
- const params = [];
192620
- for (const p of node.parameters) {
192621
- if (import_typescript8.default.isIdentifier(p.name)) {
192622
- params.push(p.name.text);
192623
- continue;
192768
+ if (shadowedParam !== null) {
192769
+ return {
192770
+ kind: "declined",
192771
+ declined: {
192772
+ code: "BF114",
192773
+ detail: `parameter '${shadowedParam}' of '${node.name.text}' is shadowed by a nested declaration inside the factory body`,
192774
+ loc
192775
+ }
192776
+ };
192777
+ }
192778
+ for (const site of renameSites) {
192779
+ if (bodyStatements.slice(site.start, site.end) !== site.name) {
192780
+ return {
192781
+ kind: "declined",
192782
+ declined: {
192783
+ code: "BF111",
192784
+ detail: `internal rename-site offset mismatch for '${site.name}' — this is a compiler bug, ` + `please report it`,
192785
+ loc
192786
+ }
192787
+ };
192624
192788
  }
192625
- return { kind: "reactive-shaped" };
192626
192789
  }
192627
192790
  return {
192628
192791
  kind: "factory",
@@ -192632,7 +192795,8 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192632
192795
  returnTupleIdentifiers,
192633
192796
  returnKind,
192634
192797
  localBindings,
192635
- loc
192798
+ loc,
192799
+ renameSites
192636
192800
  }
192637
192801
  };
192638
192802
  }
@@ -192736,24 +192900,31 @@ function rewriteFactoryCallsInSource(source, prescan) {
192736
192900
  const argTexts = args.map((a) => a.getText(sourceFile));
192737
192901
  const thisCallIndex = callSiteIndex++;
192738
192902
  const suffix = `_bf${thisCallIndex}`;
192739
- let body = factory.bodySource;
192740
- const internalRenames = new Set(factory.localBindings);
192741
- for (const ex of excludeFromSuffixRename)
192742
- internalRenames.delete(ex);
192743
- for (const name of internalRenames) {
192744
- body = body.replace(new RegExp(`\\b${escapeRegex(name)}\\b`, "g"), name + suffix);
192903
+ const renames = new Map;
192904
+ for (const name of factory.localBindings) {
192905
+ if (!excludeFromSuffixRename.has(name))
192906
+ renames.set(name, name + suffix);
192907
+ }
192908
+ if (renameReturnToCallerNames) {
192909
+ for (const [n, caller] of renameReturnToCallerNames) {
192910
+ if (caller !== n)
192911
+ renames.set(n, caller);
192912
+ }
192745
192913
  }
192746
192914
  const atomicArg = /^(?:[\w$.]+|'[^'\\]*'|"[^"\\]*"|-?\d+(?:\.\d+)?)$/;
192747
192915
  for (let i2 = 0;i2 < factory.params.length; i2++) {
192748
192916
  const p = factory.params[i2];
192749
- const a = argTexts[i2] ?? "undefined";
192750
- const wrapped = atomicArg.test(a.trim()) ? a.trim() : `(${a})`;
192751
- body = body.replace(new RegExp(`\\b${escapeRegex(p)}\\b`, "g"), wrapped);
192917
+ const a = (argTexts[i2] ?? "undefined").trim();
192918
+ renames.set(p, atomicArg.test(a) ? a : `(${a})`);
192752
192919
  }
192753
- if (renameReturnToCallerNames) {
192754
- for (const [n, caller] of renameReturnToCallerNames) {
192755
- body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
192756
- }
192920
+ let body = factory.bodySource;
192921
+ for (let i2 = factory.renameSites.length - 1;i2 >= 0; i2--) {
192922
+ const site = factory.renameSites[i2];
192923
+ const repl = renames.get(site.name);
192924
+ if (repl === undefined)
192925
+ continue;
192926
+ const text = site.form === "shorthand" ? `${site.name}: ${repl}` : repl;
192927
+ body = body.slice(0, site.start) + text + body.slice(site.end);
192757
192928
  }
192758
192929
  edits.push({
192759
192930
  start: stmt.getStart(sourceFile),
@@ -192818,9 +192989,6 @@ function isPascalCaseComponentFn(node) {
192818
192989
  }
192819
192990
  return false;
192820
192991
  }
192821
- function escapeRegex(s) {
192822
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192823
- }
192824
192992
  function declinedFactoryMessage(callee, d) {
192825
192993
  if (d.code === "BF112") {
192826
192994
  return `Reactive factory '${callee}' references ${d.detail} from its own module ` + `scope and cannot be inlined. Move the referenced helper(s) into this file, ` + `pass them as factory arguments, or inline the factory here.`;
@@ -192836,6 +193004,8 @@ function declinedFactoryErrorCode(code2) {
192836
193004
  return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
192837
193005
  case "BF113":
192838
193006
  return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
193007
+ case "BF114":
193008
+ return ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED;
192839
193009
  default:
192840
193010
  return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
192841
193011
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
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.24.1"
42
+ "@barefootjs/jsx": "0.25.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"