@barefootjs/test 0.24.1 → 0.26.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 +356 -78
  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)) {
@@ -191419,6 +191421,37 @@ function extractFreeIdentifiersFromNode(node) {
191419
191421
  visit2(node);
191420
191422
  return ids;
191421
191423
  }
191424
+ function extractFreeTypeIdentifiersFromNode(node) {
191425
+ const ids = new Set;
191426
+ const boundTypeParams = new Set;
191427
+ function rootName(name) {
191428
+ return import_typescript8.default.isQualifiedName(name) ? rootName(name.left) : name;
191429
+ }
191430
+ function visit2(n) {
191431
+ if (import_typescript8.default.isTypeReferenceNode(n)) {
191432
+ const name = rootName(n.typeName).text;
191433
+ if (!boundTypeParams.has(name))
191434
+ ids.add(name);
191435
+ }
191436
+ if (import_typescript8.default.isTypeQueryNode(n)) {
191437
+ const name = rootName(n.exprName).text;
191438
+ if (!boundTypeParams.has(name))
191439
+ ids.add(name);
191440
+ }
191441
+ if (import_typescript8.default.isFunctionLike(n) && n.typeParameters && n.typeParameters.length > 0) {
191442
+ const names = n.typeParameters.map((p) => p.name.text);
191443
+ for (const p of names)
191444
+ boundTypeParams.add(p);
191445
+ import_typescript8.default.forEachChild(n, visit2);
191446
+ for (const p of names)
191447
+ boundTypeParams.delete(p);
191448
+ return;
191449
+ }
191450
+ import_typescript8.default.forEachChild(n, visit2);
191451
+ }
191452
+ visit2(node);
191453
+ return ids;
191454
+ }
191422
191455
  function initializerShapeContainsJsx(node) {
191423
191456
  let found = false;
191424
191457
  function visit2(n) {
@@ -192241,16 +192274,17 @@ function buildEntryImportIndex(sf, filePath) {
192241
192274
  continue;
192242
192275
  if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192243
192276
  continue;
192244
- if (stmt.importClause?.isTypeOnly)
192245
- continue;
192246
192277
  const src = stmt.moduleSpecifier.text;
192247
192278
  const targetKey = src.startsWith("./") || src.startsWith("../") ? resolveRelativeImportToFile(src, filePath) ?? "unresolved:" + src : src;
192279
+ const wholeTypeOnly = stmt.importClause?.isTypeOnly === true;
192248
192280
  const namedBindings = stmt.importClause?.namedBindings;
192249
192281
  if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192250
192282
  for (const el of namedBindings.elements) {
192251
- if (el.isTypeOnly)
192252
- continue;
192253
- index.set(el.name.text, { targetKey, exportedName: (el.propertyName ?? el.name).text });
192283
+ index.set(el.name.text, {
192284
+ targetKey,
192285
+ exportedName: (el.propertyName ?? el.name).text,
192286
+ isTypeOnly: wholeTypeOnly || el.isTypeOnly
192287
+ });
192254
192288
  }
192255
192289
  }
192256
192290
  }
@@ -192280,6 +192314,9 @@ function collectEntryBindingNames(sf) {
192280
192314
  if ((import_typescript8.default.isFunctionDeclaration(node) || import_typescript8.default.isClassDeclaration(node) || import_typescript8.default.isEnumDeclaration(node)) && node.name) {
192281
192315
  names.add(node.name.text);
192282
192316
  }
192317
+ if ((import_typescript8.default.isTypeAliasDeclaration(node) || import_typescript8.default.isInterfaceDeclaration(node)) && node.name) {
192318
+ names.add(node.name.text);
192319
+ }
192283
192320
  if (import_typescript8.default.isFunctionLike(node)) {
192284
192321
  for (const p of node.parameters) {
192285
192322
  const out = [];
@@ -192293,6 +192330,7 @@ function collectEntryBindingNames(sf) {
192293
192330
  visit2(sf);
192294
192331
  return names;
192295
192332
  }
192333
+ var MAX_REEXPORT_HOPS = 1;
192296
192334
  function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192297
192335
  const candidateCallees = new Set;
192298
192336
  function collectCandidates(node) {
@@ -192336,29 +192374,28 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192336
192374
  const entryBindingNames = collectEntryBindingNames(entrySourceFile);
192337
192375
  const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
192338
192376
  const plannedInjections = new Map;
192339
- for (const { src, specs } of importsToCheck) {
192340
- const resolved = resolveRelativeImportToFile(src, filePath);
192341
- if (!resolved)
192342
- continue;
192377
+ const helperCache = new Map;
192378
+ function loadHelperFile(abs) {
192379
+ const cached = helperCache.get(abs);
192380
+ if (cached !== undefined)
192381
+ return cached;
192343
192382
  let content;
192344
192383
  try {
192345
- content = fs.readFileSync(resolved, "utf8");
192384
+ content = fs.readFileSync(abs, "utf8");
192346
192385
  } catch {
192347
- continue;
192386
+ helperCache.set(abs, null);
192387
+ return null;
192348
192388
  }
192349
- const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
192350
192389
  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;
192390
+ const hasReexportText = content.includes("export") && content.includes("from");
192391
+ if (!hasAnyPrimitiveText && !hasReexportText) {
192392
+ helperCache.set(abs, "clean");
192393
+ return "clean";
192357
192394
  }
192358
- const helperSf = import_typescript8.default.createSourceFile(resolved + ".prescan", content, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192395
+ const sf = import_typescript8.default.createSourceFile(abs + ".prescan", content, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192359
192396
  const localFns = new Map;
192360
192397
  const exportedFns = new Map;
192361
- for (const stmt of helperSf.statements) {
192398
+ for (const stmt of sf.statements) {
192362
192399
  if (import_typescript8.default.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
192363
192400
  localFns.set(stmt.name.text, stmt);
192364
192401
  const hasExportModifier = stmt.modifiers?.some((m) => m.kind === import_typescript8.default.SyntaxKind.ExportKeyword) ?? false;
@@ -192368,27 +192405,89 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192368
192405
  }
192369
192406
  }
192370
192407
  }
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) {
192408
+ const reexports = new Map;
192409
+ let hasStarReexport = false;
192410
+ for (const stmt of sf.statements) {
192411
+ if (!import_typescript8.default.isExportDeclaration(stmt) || stmt.isTypeOnly)
192412
+ continue;
192413
+ if (!stmt.moduleSpecifier) {
192414
+ if (stmt.exportClause && import_typescript8.default.isNamedExports(stmt.exportClause)) {
192415
+ for (const el of stmt.exportClause.elements) {
192416
+ if (el.isTypeOnly)
192417
+ continue;
192418
+ const fn = localFns.get((el.propertyName ?? el.name).text);
192419
+ if (fn)
192420
+ exportedFns.set(el.name.text, fn);
192421
+ }
192422
+ }
192423
+ continue;
192424
+ }
192425
+ if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192426
+ continue;
192427
+ if (stmt.exportClause && import_typescript8.default.isNamedExports(stmt.exportClause)) {
192373
192428
  for (const el of stmt.exportClause.elements) {
192374
192429
  if (el.isTypeOnly)
192375
192430
  continue;
192376
- const fn = localFns.get((el.propertyName ?? el.name).text);
192377
- if (fn)
192378
- exportedFns.set(el.name.text, fn);
192431
+ reexports.set(el.name.text, {
192432
+ source: stmt.moduleSpecifier.text,
192433
+ innerName: (el.propertyName ?? el.name).text
192434
+ });
192379
192435
  }
192380
- }
192436
+ } else if (!stmt.exportClause) {
192437
+ hasStarReexport = true;
192438
+ }
192439
+ }
192440
+ const moduleBindings = collectHelperModuleValueBindings(sf);
192441
+ const info = { sf, exportedFns, reexports, hasStarReexport, moduleBindings };
192442
+ helperCache.set(abs, info);
192443
+ return info;
192444
+ }
192445
+ function lookupExportedFactory(abs, exportedName, visited, hopsLeft) {
192446
+ if (visited.has(abs))
192447
+ return { kind: "unknown" };
192448
+ visited.add(abs);
192449
+ const file = loadHelperFile(abs);
192450
+ if (file === null)
192451
+ return { kind: "unknown" };
192452
+ if (file === "clean")
192453
+ return { kind: "clean" };
192454
+ const fn = file.exportedFns.get(exportedName);
192455
+ if (fn)
192456
+ return { kind: "fn", fn, file, definingPath: abs };
192457
+ const re = file.reexports.get(exportedName);
192458
+ if (re) {
192459
+ if (hopsLeft <= 0)
192460
+ return { kind: "unknown" };
192461
+ if (!re.source.startsWith("./") && !re.source.startsWith("../"))
192462
+ return { kind: "unknown" };
192463
+ const target = resolveRelativeImportToFile(re.source, abs);
192464
+ if (!target)
192465
+ return { kind: "unknown" };
192466
+ return lookupExportedFactory(target, re.innerName, visited, hopsLeft - 1);
192381
192467
  }
192382
- const moduleBindings = collectHelperModuleValueBindings(helperSf);
192468
+ if (file.hasStarReexport)
192469
+ return { kind: "unknown" };
192470
+ return { kind: "clean" };
192471
+ }
192472
+ for (const { src, specs } of importsToCheck) {
192473
+ const resolved = resolveRelativeImportToFile(src, filePath);
192474
+ if (!resolved)
192475
+ continue;
192476
+ const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
192383
192477
  for (const spec of specs) {
192384
192478
  if (alreadyKnown(spec.local))
192385
192479
  continue;
192386
- const fn = exportedFns.get(spec.exported);
192387
- if (!fn) {
192480
+ const found = lookupExportedFactory(resolved, spec.exported, new Set, MAX_REEXPORT_HOPS);
192481
+ if (found.kind === "clean") {
192388
192482
  result.cleanFactoryImports.add(spec.local);
192389
192483
  continue;
192390
192484
  }
192391
- const det = detectReactiveFactory(fn, helperSf, resolved);
192485
+ if (found.kind === "unknown")
192486
+ continue;
192487
+ const { fn, file, definingPath } = found;
192488
+ const helperSf = file.sf;
192489
+ const moduleBindings = file.moduleBindings;
192490
+ const det = detectReactiveFactory(fn, helperSf, definingPath);
192392
192491
  if (!det) {
192393
192492
  result.cleanFactoryImports.add(spec.local);
192394
192493
  continue;
@@ -192413,11 +192512,15 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192413
192512
  const required = [];
192414
192513
  const pending = [];
192415
192514
  let declinedEntry = null;
192416
- for (const ref of capture.importedRefs) {
192515
+ const allRefs = [
192516
+ ...capture.importedRefs.map((r) => ({ ...r, isTypeOnly: false })),
192517
+ ...capture.importedTypeRefs.map((r) => ({ ...r, isTypeOnly: true }))
192518
+ ];
192519
+ for (const ref of allRefs) {
192417
192520
  let specifier;
192418
192521
  let targetKey;
192419
192522
  if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
192420
- const abs = resolveRelativeImportToFile(ref.source, resolved);
192523
+ const abs = resolveRelativeImportToFile(ref.source, definingPath);
192421
192524
  if (!abs) {
192422
192525
  declinedEntry = {
192423
192526
  code: "BF112",
@@ -192433,7 +192536,7 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192433
192536
  targetKey = ref.source;
192434
192537
  }
192435
192538
  const existing = entryImportIndex.get(ref.localName);
192436
- if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName) {
192539
+ if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName && (ref.isTypeOnly || !existing.isTypeOnly)) {
192437
192540
  continue;
192438
192541
  }
192439
192542
  const planned = plannedInjections.get(ref.localName);
@@ -192447,7 +192550,7 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192447
192550
  break;
192448
192551
  }
192449
192552
  pending.push([ref.localName, { targetKey, exportedName: ref.exportedName }]);
192450
- required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier });
192553
+ required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier, isTypeOnly: ref.isTypeOnly || undefined });
192451
192554
  }
192452
192555
  if (declinedEntry) {
192453
192556
  result.declined.set(spec.local, declinedEntry);
@@ -192455,7 +192558,7 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192455
192558
  }
192456
192559
  for (const [name, id] of pending)
192457
192560
  plannedInjections.set(name, id);
192458
- det.info.sourceFilePath = resolved;
192561
+ det.info.sourceFilePath = definingPath;
192459
192562
  if (required.length > 0)
192460
192563
  det.info.requiredImports = required;
192461
192564
  result.factories.set(spec.local, det.info);
@@ -192467,6 +192570,8 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192467
192570
  }
192468
192571
  function collectHelperModuleValueBindings(sf) {
192469
192572
  const local = new Set;
192573
+ const localTypes = new Set;
192574
+ const importedTypes = new Map;
192470
192575
  const imported = new Map;
192471
192576
  for (const stmt of sf.statements) {
192472
192577
  if (import_typescript8.default.isVariableStatement(stmt)) {
@@ -192482,35 +192587,41 @@ function collectHelperModuleValueBindings(sf) {
192482
192587
  local.add(stmt.name.text);
192483
192588
  continue;
192484
192589
  }
192590
+ if ((import_typescript8.default.isTypeAliasDeclaration(stmt) || import_typescript8.default.isInterfaceDeclaration(stmt)) && stmt.name) {
192591
+ localTypes.add(stmt.name.text);
192592
+ continue;
192593
+ }
192485
192594
  if (import_typescript8.default.isImportDeclaration(stmt)) {
192486
- if (stmt.importClause?.isTypeOnly)
192487
- continue;
192488
192595
  if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192489
192596
  continue;
192490
192597
  const src = stmt.moduleSpecifier.text;
192491
192598
  if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
192492
192599
  continue;
192600
+ const wholeTypeOnly = stmt.importClause?.isTypeOnly === true;
192493
192601
  if (stmt.importClause?.name)
192494
- local.add(stmt.importClause.name.text);
192602
+ (wholeTypeOnly ? localTypes : local).add(stmt.importClause.name.text);
192495
192603
  const namedBindings = stmt.importClause?.namedBindings;
192496
192604
  if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192497
192605
  for (const el of namedBindings.elements) {
192498
- if (el.isTypeOnly)
192499
- continue;
192500
- imported.set(el.name.text, { source: src, exportedName: (el.propertyName ?? el.name).text });
192606
+ const entry = { source: src, exportedName: (el.propertyName ?? el.name).text };
192607
+ if (wholeTypeOnly || el.isTypeOnly)
192608
+ importedTypes.set(el.name.text, entry);
192609
+ else
192610
+ imported.set(el.name.text, entry);
192501
192611
  }
192502
192612
  }
192503
192613
  if (namedBindings && import_typescript8.default.isNamespaceImport(namedBindings)) {
192504
- local.add(namedBindings.name.text);
192614
+ (wholeTypeOnly ? localTypes : local).add(namedBindings.name.text);
192505
192615
  }
192506
192616
  }
192507
192617
  }
192508
- return { local, imported };
192618
+ return { local, imported, localTypes, importedTypes };
192509
192619
  }
192510
192620
  function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192511
192621
  if (!fn.body)
192512
- return { captured: [], importedRefs: [] };
192622
+ return { captured: [], importedRefs: [], importedTypeRefs: [] };
192513
192623
  const free = extractFreeIdentifiersFromNode(fn.body);
192624
+ const freeTypes = extractFreeTypeIdentifiersFromNode(fn.body);
192514
192625
  const exclude = new Set(info.params);
192515
192626
  for (const b of info.localBindings)
192516
192627
  exclude.add(b);
@@ -192519,8 +192630,12 @@ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192519
192630
  for (const p of REACTIVE_PRIMITIVES)
192520
192631
  exclude.add(p);
192521
192632
  exclude.add(selfName);
192633
+ if (fn.typeParameters)
192634
+ for (const p of fn.typeParameters)
192635
+ exclude.add(p.name.text);
192522
192636
  const captured = [];
192523
192637
  const importedRefs = [];
192638
+ const importedTypeRefs = [];
192524
192639
  for (const id of free) {
192525
192640
  if (exclude.has(id))
192526
192641
  continue;
@@ -192532,9 +192647,28 @@ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192532
192647
  if (imp)
192533
192648
  importedRefs.push({ localName: id, source: imp.source, exportedName: imp.exportedName });
192534
192649
  }
192650
+ for (const id of freeTypes) {
192651
+ if (exclude.has(id))
192652
+ continue;
192653
+ if (free.has(id))
192654
+ continue;
192655
+ if (moduleBindings.localTypes.has(id) || moduleBindings.local.has(id)) {
192656
+ captured.push(id);
192657
+ continue;
192658
+ }
192659
+ const typeImp = moduleBindings.importedTypes.get(id);
192660
+ if (typeImp) {
192661
+ importedTypeRefs.push({ localName: id, source: typeImp.source, exportedName: typeImp.exportedName });
192662
+ continue;
192663
+ }
192664
+ const valueImp = moduleBindings.imported.get(id);
192665
+ if (valueImp)
192666
+ importedRefs.push({ localName: id, source: valueImp.source, exportedName: valueImp.exportedName });
192667
+ }
192535
192668
  captured.sort();
192536
192669
  importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
192537
- return { captured, importedRefs };
192670
+ importedTypeRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
192671
+ return { captured, importedRefs, importedTypeRefs };
192538
192672
  }
192539
192673
  function detectReactiveFactory(node, sourceFile, filePath) {
192540
192674
  if (!node.body || !node.name)
@@ -192553,6 +192687,17 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192553
192687
  if (!hasReactiveCall)
192554
192688
  return null;
192555
192689
  const loc = getSourceLocation(node, sourceFile, filePath);
192690
+ let totalReturnCount = 0;
192691
+ function countReturns(n) {
192692
+ if (import_typescript8.default.isFunctionLike(n))
192693
+ return;
192694
+ if (import_typescript8.default.isReturnStatement(n)) {
192695
+ totalReturnCount++;
192696
+ return;
192697
+ }
192698
+ import_typescript8.default.forEachChild(n, countReturns);
192699
+ }
192700
+ import_typescript8.default.forEachChild(node.body, countReturns);
192556
192701
  let returnExpr = null;
192557
192702
  let returnCount = 0;
192558
192703
  for (const stmt of node.body.statements) {
@@ -192570,7 +192715,7 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192570
192715
  expr = expr.expression;
192571
192716
  returnExpr = expr;
192572
192717
  }
192573
- if (returnCount !== 1 || !returnExpr)
192718
+ if (totalReturnCount !== 1 || returnCount !== 1 || !returnExpr)
192574
192719
  return { kind: "reactive-shaped" };
192575
192720
  const returnTupleIdentifiers = [];
192576
192721
  let returnKind;
@@ -192604,6 +192749,14 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192604
192749
  } else {
192605
192750
  return { kind: "reactive-shaped" };
192606
192751
  }
192752
+ const params = [];
192753
+ for (const p of node.parameters) {
192754
+ if (import_typescript8.default.isIdentifier(p.name)) {
192755
+ params.push(p.name.text);
192756
+ continue;
192757
+ }
192758
+ return { kind: "reactive-shaped" };
192759
+ }
192607
192760
  const localBindings = [];
192608
192761
  for (const stmt of node.body.statements) {
192609
192762
  if (import_typescript8.default.isVariableStatement(stmt)) {
@@ -192614,15 +192767,95 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192614
192767
  localBindings.push(stmt.name.text);
192615
192768
  }
192616
192769
  }
192617
- const bodyStatements = node.body.statements.filter((s) => !import_typescript8.default.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
192770
+ const relevantNames = new Set([...params, ...localBindings, ...returnTupleIdentifiers]);
192771
+ const renameSites = [];
192772
+ let shadowedParam = null;
192773
+ function collectRenameSites(root, toBodyOffset) {
192774
+ function push(id, form) {
192775
+ renameSites.push({
192776
+ name: id.text,
192777
+ start: toBodyOffset(id.getStart(sourceFile)),
192778
+ end: toBodyOffset(id.getEnd()),
192779
+ form
192780
+ });
192781
+ }
192782
+ function classify(id) {
192783
+ if (!relevantNames.has(id.text))
192784
+ return;
192785
+ const p = id.parent;
192786
+ if (import_typescript8.default.isPropertyAccessExpression(p) && p.name === id)
192787
+ return;
192788
+ if (import_typescript8.default.isPropertyAssignment(p) && p.name === id)
192789
+ return;
192790
+ if (import_typescript8.default.isBindingElement(p) && p.propertyName === id)
192791
+ return;
192792
+ 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)
192793
+ return;
192794
+ if (import_typescript8.default.isJsxAttribute(p) && p.name === id)
192795
+ return;
192796
+ if (import_typescript8.default.isLabeledStatement(p) && p.label === id || (import_typescript8.default.isBreakStatement(p) || import_typescript8.default.isContinueStatement(p)) && p.label === id)
192797
+ return;
192798
+ if ((import_typescript8.default.isJsxOpeningElement(p) || import_typescript8.default.isJsxSelfClosingElement(p) || import_typescript8.default.isJsxClosingElement(p)) && p.tagName === id && /^[a-z]/.test(id.text))
192799
+ return;
192800
+ if (import_typescript8.default.isShorthandPropertyAssignment(p) && p.name === id) {
192801
+ push(id, "shorthand");
192802
+ return;
192803
+ }
192804
+ if (import_typescript8.default.isBindingElement(p) && p.name === id && !p.propertyName && import_typescript8.default.isObjectBindingPattern(p.parent)) {
192805
+ if (params.includes(id.text))
192806
+ shadowedParam = id.text;
192807
+ push(id, "shorthand");
192808
+ return;
192809
+ }
192810
+ 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;
192811
+ if (isDecl && params.includes(id.text))
192812
+ shadowedParam = id.text;
192813
+ push(id, "plain");
192814
+ }
192815
+ function visit2(n) {
192816
+ if (import_typescript8.default.isTypeNode(n) || import_typescript8.default.isTypeParameterDeclaration(n) || import_typescript8.default.isTypeAliasDeclaration(n) || import_typescript8.default.isInterfaceDeclaration(n))
192817
+ return;
192818
+ if (import_typescript8.default.isIdentifier(n)) {
192819
+ classify(n);
192820
+ return;
192821
+ }
192822
+ import_typescript8.default.forEachChild(n, visit2);
192823
+ }
192824
+ visit2(root);
192825
+ }
192826
+ const keptStatements = node.body.statements.filter((s) => !import_typescript8.default.isReturnStatement(s));
192827
+ const pieces = [];
192828
+ let base = 0;
192829
+ for (const stmt of keptStatements) {
192830
+ const text = stmt.getText(sourceFile);
192831
+ const stmtStart = stmt.getStart(sourceFile);
192832
+ collectRenameSites(stmt, (pos) => pos - stmtStart + base);
192833
+ pieces.push(text);
192834
+ base += text.length + 1;
192835
+ }
192836
+ const bodyStatements = pieces.join(`
192618
192837
  `);
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;
192838
+ if (shadowedParam !== null) {
192839
+ return {
192840
+ kind: "declined",
192841
+ declined: {
192842
+ code: "BF114",
192843
+ detail: `parameter '${shadowedParam}' of '${node.name.text}' is shadowed by a nested declaration inside the factory body`,
192844
+ loc
192845
+ }
192846
+ };
192847
+ }
192848
+ for (const site of renameSites) {
192849
+ if (bodyStatements.slice(site.start, site.end) !== site.name) {
192850
+ return {
192851
+ kind: "declined",
192852
+ declined: {
192853
+ code: "BF111",
192854
+ detail: `internal rename-site offset mismatch for '${site.name}' — this is a compiler bug, ` + `please report it`,
192855
+ loc
192856
+ }
192857
+ };
192624
192858
  }
192625
- return { kind: "reactive-shaped" };
192626
192859
  }
192627
192860
  return {
192628
192861
  kind: "factory",
@@ -192632,7 +192865,8 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192632
192865
  returnTupleIdentifiers,
192633
192866
  returnKind,
192634
192867
  localBindings,
192635
- loc
192868
+ loc,
192869
+ renameSites
192636
192870
  }
192637
192871
  };
192638
192872
  }
@@ -192736,24 +192970,31 @@ function rewriteFactoryCallsInSource(source, prescan) {
192736
192970
  const argTexts = args.map((a) => a.getText(sourceFile));
192737
192971
  const thisCallIndex = callSiteIndex++;
192738
192972
  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);
192973
+ const renames = new Map;
192974
+ for (const name of factory.localBindings) {
192975
+ if (!excludeFromSuffixRename.has(name))
192976
+ renames.set(name, name + suffix);
192977
+ }
192978
+ if (renameReturnToCallerNames) {
192979
+ for (const [n, caller] of renameReturnToCallerNames) {
192980
+ if (caller !== n)
192981
+ renames.set(n, caller);
192982
+ }
192745
192983
  }
192746
192984
  const atomicArg = /^(?:[\w$.]+|'[^'\\]*'|"[^"\\]*"|-?\d+(?:\.\d+)?)$/;
192747
192985
  for (let i2 = 0;i2 < factory.params.length; i2++) {
192748
192986
  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);
192987
+ const a = (argTexts[i2] ?? "undefined").trim();
192988
+ renames.set(p, atomicArg.test(a) ? a : `(${a})`);
192752
192989
  }
192753
- if (renameReturnToCallerNames) {
192754
- for (const [n, caller] of renameReturnToCallerNames) {
192755
- body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
192756
- }
192990
+ let body = factory.bodySource;
192991
+ for (let i2 = factory.renameSites.length - 1;i2 >= 0; i2--) {
192992
+ const site = factory.renameSites[i2];
192993
+ const repl = renames.get(site.name);
192994
+ if (repl === undefined)
192995
+ continue;
192996
+ const text = site.form === "shorthand" ? `${site.name}: ${repl}` : repl;
192997
+ body = body.slice(0, site.start) + text + body.slice(site.end);
192757
192998
  }
192758
192999
  edits.push({
192759
193000
  start: stmt.getStart(sourceFile),
@@ -192766,20 +193007,44 @@ function rewriteFactoryCallsInSource(source, prescan) {
192766
193007
  if (edits.length === 0)
192767
193008
  return source;
192768
193009
  const importsBySpecifier = new Map;
193010
+ const typeImportsBySpecifier = new Map;
192769
193011
  for (const f of inlinedFactories) {
192770
193012
  for (const r of f.requiredImports ?? []) {
192771
- let names = importsBySpecifier.get(r.specifier);
193013
+ const bySpecifier = r.isTypeOnly ? typeImportsBySpecifier : importsBySpecifier;
193014
+ let names = bySpecifier.get(r.specifier);
192772
193015
  if (!names) {
192773
193016
  names = new Map;
192774
- importsBySpecifier.set(r.specifier, names);
193017
+ bySpecifier.set(r.specifier, names);
192775
193018
  }
192776
193019
  names.set(r.localName, r.exportedName);
192777
193020
  }
192778
193021
  }
192779
- if (importsBySpecifier.size > 0) {
192780
- const lines = [...importsBySpecifier].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([spec, names]) => {
193022
+ for (const [spec, typeNames] of typeImportsBySpecifier) {
193023
+ const valueNames = importsBySpecifier.get(spec);
193024
+ if (!valueNames)
193025
+ continue;
193026
+ for (const local of [...typeNames.keys()]) {
193027
+ if (valueNames.has(local))
193028
+ typeNames.delete(local);
193029
+ }
193030
+ if (typeNames.size === 0)
193031
+ typeImportsBySpecifier.delete(spec);
193032
+ }
193033
+ if (importsBySpecifier.size > 0 || typeImportsBySpecifier.size > 0) {
193034
+ const buildLine = (names, keyword, spec) => {
192781
193035
  const specifiers = [...names].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([local, exported]) => exported === local ? local : `${exported} as ${local}`);
192782
- return `import { ${specifiers.join(", ")} } from '${spec}'`;
193036
+ return `import ${keyword}{ ${specifiers.join(", ")} } from '${spec}'`;
193037
+ };
193038
+ const allSpecifiers = new Set([...importsBySpecifier.keys(), ...typeImportsBySpecifier.keys()]);
193039
+ const lines = [...allSpecifiers].sort().flatMap((spec) => {
193040
+ const out2 = [];
193041
+ const valueNames = importsBySpecifier.get(spec);
193042
+ if (valueNames)
193043
+ out2.push(buildLine(valueNames, "", spec));
193044
+ const typeNames = typeImportsBySpecifier.get(spec);
193045
+ if (typeNames)
193046
+ out2.push(buildLine(typeNames, "type ", spec));
193047
+ return out2;
192783
193048
  });
192784
193049
  const at = factoryImportInsertionOffset(sourceFile);
192785
193050
  edits.push({ start: at, end: at, replacement: at === 0 ? lines.join(`
@@ -192818,9 +193083,6 @@ function isPascalCaseComponentFn(node) {
192818
193083
  }
192819
193084
  return false;
192820
193085
  }
192821
- function escapeRegex(s) {
192822
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192823
- }
192824
193086
  function declinedFactoryMessage(callee, d) {
192825
193087
  if (d.code === "BF112") {
192826
193088
  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 +193098,8 @@ function declinedFactoryErrorCode(code2) {
192836
193098
  return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
192837
193099
  case "BF113":
192838
193100
  return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
193101
+ case "BF114":
193102
+ return ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED;
192839
193103
  default:
192840
193104
  return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
192841
193105
  }
@@ -193360,6 +193624,20 @@ function resolveFreeRefs(node, env) {
193360
193624
 
193361
193625
  // ../jsx/src/to-locale-date-lowering.ts
193362
193626
  var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
193627
+ var tzProbeCache = new Map;
193628
+ function isBuildResolvableTimeZone(value) {
193629
+ const cached = tzProbeCache.get(value);
193630
+ if (cached !== undefined)
193631
+ return cached;
193632
+ let verified;
193633
+ try {
193634
+ verified = new Intl.DateTimeFormat("en-US", { timeZone: value }).resolvedOptions().timeZone === value;
193635
+ } catch {
193636
+ verified = false;
193637
+ }
193638
+ tzProbeCache.set(value, verified);
193639
+ return verified;
193640
+ }
193363
193641
  var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
193364
193642
  var formatCache = new Map;
193365
193643
  var namesCache = new Map;
@@ -193598,7 +193876,7 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
193598
193876
  return null;
193599
193877
  const value = String(prop.value.value);
193600
193878
  if (prop.key === "timeZone") {
193601
- if (!TO_LOCALE_TZ_RE.test(value))
193879
+ if (!TO_LOCALE_TZ_RE.test(value) && !isBuildResolvableTimeZone(value))
193602
193880
  return null;
193603
193881
  tz = value;
193604
193882
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.24.1",
3
+ "version": "0.26.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.26.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"