@barefootjs/test 0.23.0 → 0.24.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.
Files changed (2) hide show
  1. package/dist/index.js +462 -54
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -189765,7 +189765,8 @@ var ErrorCodes = {
189765
189765
  INLINE_JSX_CALLBACK_CAPTURE: "BF080",
189766
189766
  UNRECOGNIZED_REACTIVE_FACTORY: "BF110",
189767
189767
  REACTIVE_FACTORY_RENAME_UNSUPPORTED: "BF111",
189768
- REACTIVE_FACTORY_MODULE_CAPTURE: "BF112"
189768
+ REACTIVE_FACTORY_MODULE_CAPTURE: "BF112",
189769
+ REACTIVE_FACTORY_IMPORT_COLLISION: "BF113"
189769
189770
  };
189770
189771
  var errorMessages = {
189771
189772
  [ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
@@ -189791,7 +189792,8 @@ var errorMessages = {
189791
189792
  [ErrorCodes.INLINE_JSX_CALLBACK_CAPTURE]: "Inline JSX-returning arrow function captures a non-module identifier. Extract the callback into a top-level 'use client' component (e.g. `function MyNode(n) { return <div/> }` then `renderNode={MyNode}`) or pass captured values via component props.",
189792
189793
  [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.",
189793
189794
  [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.",
189794
- [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."
189795
+ [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."
189795
189797
  };
189796
189798
  function createError(code2, loc, options) {
189797
189799
  if (code2 === undefined || !(code2 in errorMessages)) {
@@ -192223,6 +192225,74 @@ function prescanReactiveFactoriesInSource(source, filePath) {
192223
192225
  prescanImportedReactiveFactories(sourceFile, filePath, result);
192224
192226
  return result;
192225
192227
  }
192228
+ function toComponentRelativeSpecifier(resolvedAbs, componentFilePath) {
192229
+ let rel = path_default.relative(path_default.dirname(componentFilePath), resolvedAbs).split(path_default.sep).join("/");
192230
+ rel = rel.replace(/\.(tsx|ts|jsx|js)$/, "");
192231
+ if (rel === "")
192232
+ rel = ".";
192233
+ if (!rel.startsWith("."))
192234
+ rel = "./" + rel;
192235
+ return rel;
192236
+ }
192237
+ function buildEntryImportIndex(sf, filePath) {
192238
+ const index = new Map;
192239
+ for (const stmt of sf.statements) {
192240
+ if (!import_typescript8.default.isImportDeclaration(stmt))
192241
+ continue;
192242
+ if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192243
+ continue;
192244
+ if (stmt.importClause?.isTypeOnly)
192245
+ continue;
192246
+ const src = stmt.moduleSpecifier.text;
192247
+ const targetKey = src.startsWith("./") || src.startsWith("../") ? resolveRelativeImportToFile(src, filePath) ?? "unresolved:" + src : src;
192248
+ const namedBindings = stmt.importClause?.namedBindings;
192249
+ if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192250
+ 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 });
192254
+ }
192255
+ }
192256
+ }
192257
+ return index;
192258
+ }
192259
+ function collectEntryBindingNames(sf) {
192260
+ const names = new Set;
192261
+ function visit2(node) {
192262
+ if (import_typescript8.default.isImportDeclaration(node) && node.importClause) {
192263
+ if (node.importClause.name)
192264
+ names.add(node.importClause.name.text);
192265
+ const namedBindings = node.importClause.namedBindings;
192266
+ if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192267
+ for (const el of namedBindings.elements)
192268
+ names.add(el.name.text);
192269
+ }
192270
+ if (namedBindings && import_typescript8.default.isNamespaceImport(namedBindings)) {
192271
+ names.add(namedBindings.name.text);
192272
+ }
192273
+ }
192274
+ if (import_typescript8.default.isVariableDeclaration(node)) {
192275
+ const out = [];
192276
+ addBindingNames(node.name, out);
192277
+ for (const n of out)
192278
+ names.add(n);
192279
+ }
192280
+ if ((import_typescript8.default.isFunctionDeclaration(node) || import_typescript8.default.isClassDeclaration(node) || import_typescript8.default.isEnumDeclaration(node)) && node.name) {
192281
+ names.add(node.name.text);
192282
+ }
192283
+ if (import_typescript8.default.isFunctionLike(node)) {
192284
+ for (const p of node.parameters) {
192285
+ const out = [];
192286
+ addBindingNames(p.name, out);
192287
+ for (const n of out)
192288
+ names.add(n);
192289
+ }
192290
+ }
192291
+ import_typescript8.default.forEachChild(node, visit2);
192292
+ }
192293
+ visit2(sf);
192294
+ return names;
192295
+ }
192226
192296
  function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192227
192297
  const candidateCallees = new Set;
192228
192298
  function collectCandidates(node) {
@@ -192263,6 +192333,9 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192263
192333
  }
192264
192334
  if (importsToCheck.length === 0)
192265
192335
  return;
192336
+ const entryBindingNames = collectEntryBindingNames(entrySourceFile);
192337
+ const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
192338
+ const plannedInjections = new Map;
192266
192339
  for (const { src, specs } of importsToCheck) {
192267
192340
  const resolved = resolveRelativeImportToFile(src, filePath);
192268
192341
  if (!resolved)
@@ -192328,17 +192401,64 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192328
192401
  result.declined.set(spec.local, det.declined);
192329
192402
  break;
192330
192403
  case "factory": {
192331
- const offending = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
192332
- if (offending.length > 0) {
192404
+ const capture = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
192405
+ if (capture.captured.length > 0) {
192333
192406
  result.declined.set(spec.local, {
192334
192407
  code: "BF112",
192335
- detail: `'${offending.join("', '")}'`,
192408
+ detail: `'${capture.captured.join("', '")}'`,
192336
192409
  loc: det.info.loc
192337
192410
  });
192338
- } else {
192339
- det.info.sourceFilePath = resolved;
192340
- result.factories.set(spec.local, det.info);
192411
+ break;
192341
192412
  }
192413
+ const required = [];
192414
+ const pending = [];
192415
+ let declinedEntry = null;
192416
+ for (const ref of capture.importedRefs) {
192417
+ let specifier;
192418
+ let targetKey;
192419
+ if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
192420
+ const abs = resolveRelativeImportToFile(ref.source, resolved);
192421
+ if (!abs) {
192422
+ declinedEntry = {
192423
+ code: "BF112",
192424
+ detail: `'${ref.localName}' (import '${ref.source}' did not resolve from the helper file)`,
192425
+ loc: det.info.loc
192426
+ };
192427
+ break;
192428
+ }
192429
+ specifier = toComponentRelativeSpecifier(abs, filePath);
192430
+ targetKey = abs;
192431
+ } else {
192432
+ specifier = ref.source;
192433
+ targetKey = ref.source;
192434
+ }
192435
+ const existing = entryImportIndex.get(ref.localName);
192436
+ if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName) {
192437
+ continue;
192438
+ }
192439
+ const planned = plannedInjections.get(ref.localName);
192440
+ const collides = existing !== undefined || planned !== undefined && (planned.targetKey !== targetKey || planned.exportedName !== ref.exportedName) || planned === undefined && entryBindingNames.has(ref.localName);
192441
+ if (collides) {
192442
+ declinedEntry = {
192443
+ code: "BF113",
192444
+ detail: `'${ref.localName}' from '${specifier}'`,
192445
+ loc: det.info.loc
192446
+ };
192447
+ break;
192448
+ }
192449
+ pending.push([ref.localName, { targetKey, exportedName: ref.exportedName }]);
192450
+ required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier });
192451
+ }
192452
+ if (declinedEntry) {
192453
+ result.declined.set(spec.local, declinedEntry);
192454
+ break;
192455
+ }
192456
+ for (const [name, id] of pending)
192457
+ plannedInjections.set(name, id);
192458
+ det.info.sourceFilePath = resolved;
192459
+ if (required.length > 0)
192460
+ det.info.requiredImports = required;
192461
+ result.factories.set(spec.local, det.info);
192342
192462
  break;
192343
192463
  }
192344
192464
  }
@@ -192346,7 +192466,8 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192346
192466
  }
192347
192467
  }
192348
192468
  function collectHelperModuleValueBindings(sf) {
192349
- const names = new Set;
192469
+ const local = new Set;
192470
+ const imported = new Map;
192350
192471
  for (const stmt of sf.statements) {
192351
192472
  if (import_typescript8.default.isVariableStatement(stmt)) {
192352
192473
  const out = [];
@@ -192354,11 +192475,11 @@ function collectHelperModuleValueBindings(sf) {
192354
192475
  addBindingNames(decl.name, out);
192355
192476
  }
192356
192477
  for (const n of out)
192357
- names.add(n);
192478
+ local.add(n);
192358
192479
  continue;
192359
192480
  }
192360
192481
  if ((import_typescript8.default.isFunctionDeclaration(stmt) || import_typescript8.default.isClassDeclaration(stmt) || import_typescript8.default.isEnumDeclaration(stmt)) && stmt.name) {
192361
- names.add(stmt.name.text);
192482
+ local.add(stmt.name.text);
192362
192483
  continue;
192363
192484
  }
192364
192485
  if (import_typescript8.default.isImportDeclaration(stmt)) {
@@ -192370,25 +192491,25 @@ function collectHelperModuleValueBindings(sf) {
192370
192491
  if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
192371
192492
  continue;
192372
192493
  if (stmt.importClause?.name)
192373
- names.add(stmt.importClause.name.text);
192494
+ local.add(stmt.importClause.name.text);
192374
192495
  const namedBindings = stmt.importClause?.namedBindings;
192375
192496
  if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192376
192497
  for (const el of namedBindings.elements) {
192377
192498
  if (el.isTypeOnly)
192378
192499
  continue;
192379
- names.add(el.name.text);
192500
+ imported.set(el.name.text, { source: src, exportedName: (el.propertyName ?? el.name).text });
192380
192501
  }
192381
192502
  }
192382
192503
  if (namedBindings && import_typescript8.default.isNamespaceImport(namedBindings)) {
192383
- names.add(namedBindings.name.text);
192504
+ local.add(namedBindings.name.text);
192384
192505
  }
192385
192506
  }
192386
192507
  }
192387
- return names;
192508
+ return { local, imported };
192388
192509
  }
192389
192510
  function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192390
192511
  if (!fn.body)
192391
- return [];
192512
+ return { captured: [], importedRefs: [] };
192392
192513
  const free = extractFreeIdentifiersFromNode(fn.body);
192393
192514
  const exclude = new Set(info.params);
192394
192515
  for (const b of info.localBindings)
@@ -192398,14 +192519,22 @@ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192398
192519
  for (const p of REACTIVE_PRIMITIVES)
192399
192520
  exclude.add(p);
192400
192521
  exclude.add(selfName);
192401
- const offending = [];
192522
+ const captured = [];
192523
+ const importedRefs = [];
192402
192524
  for (const id of free) {
192403
192525
  if (exclude.has(id))
192404
192526
  continue;
192405
- if (moduleBindings.has(id))
192406
- offending.push(id);
192527
+ if (moduleBindings.local.has(id)) {
192528
+ captured.push(id);
192529
+ continue;
192530
+ }
192531
+ const imp = moduleBindings.imported.get(id);
192532
+ if (imp)
192533
+ importedRefs.push({ localName: id, source: imp.source, exportedName: imp.exportedName });
192407
192534
  }
192408
- return offending.sort();
192535
+ captured.sort();
192536
+ importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
192537
+ return { captured, importedRefs };
192409
192538
  }
192410
192539
  function detectReactiveFactory(node, sourceFile, filePath) {
192411
192540
  if (!node.body || !node.name)
@@ -192529,6 +192658,7 @@ function rewriteFactoryCallsInSource(source, prescan) {
192529
192658
  const { factories, sourceFile } = prescan;
192530
192659
  const edits = [];
192531
192660
  let callSiteIndex = 0;
192661
+ const inlinedFactories = new Set;
192532
192662
  function visitStmt(node, inComponent) {
192533
192663
  if (import_typescript8.default.isVariableStatement(node) && inComponent) {
192534
192664
  for (const decl of node.declarationList.declarations) {
@@ -192630,10 +192760,34 @@ function rewriteFactoryCallsInSource(source, prescan) {
192630
192760
  end: stmt.getEnd(),
192631
192761
  replacement: body
192632
192762
  });
192763
+ inlinedFactories.add(factory);
192633
192764
  }
192634
192765
  visitStmt(sourceFile, false);
192635
192766
  if (edits.length === 0)
192636
192767
  return source;
192768
+ const importsBySpecifier = new Map;
192769
+ for (const f of inlinedFactories) {
192770
+ for (const r of f.requiredImports ?? []) {
192771
+ let names = importsBySpecifier.get(r.specifier);
192772
+ if (!names) {
192773
+ names = new Map;
192774
+ importsBySpecifier.set(r.specifier, names);
192775
+ }
192776
+ names.set(r.localName, r.exportedName);
192777
+ }
192778
+ }
192779
+ if (importsBySpecifier.size > 0) {
192780
+ const lines = [...importsBySpecifier].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([spec, names]) => {
192781
+ 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}'`;
192783
+ });
192784
+ const at = factoryImportInsertionOffset(sourceFile);
192785
+ edits.push({ start: at, end: at, replacement: at === 0 ? lines.join(`
192786
+ `) + `
192787
+ ` : `
192788
+ ` + lines.join(`
192789
+ `) });
192790
+ }
192637
192791
  edits.sort((a, b) => b.start - a.start);
192638
192792
  let out = source;
192639
192793
  for (const e of edits) {
@@ -192641,6 +192795,20 @@ function rewriteFactoryCallsInSource(source, prescan) {
192641
192795
  }
192642
192796
  return out;
192643
192797
  }
192798
+ function factoryImportInsertionOffset(sf) {
192799
+ let lastImportEnd = -1;
192800
+ let directiveEnd = -1;
192801
+ for (const stmt of sf.statements) {
192802
+ if (import_typescript8.default.isImportDeclaration(stmt)) {
192803
+ lastImportEnd = stmt.getEnd();
192804
+ continue;
192805
+ }
192806
+ if (directiveEnd === -1 && import_typescript8.default.isExpressionStatement(stmt) && import_typescript8.default.isStringLiteral(stmt.expression) && stmt.expression.text === "use client") {
192807
+ directiveEnd = stmt.getEnd();
192808
+ }
192809
+ }
192810
+ return lastImportEnd >= 0 ? lastImportEnd : directiveEnd >= 0 ? directiveEnd : 0;
192811
+ }
192644
192812
  function isPascalCaseComponentFn(node) {
192645
192813
  if (import_typescript8.default.isFunctionDeclaration(node) && node.name) {
192646
192814
  return /^[A-Z]/.test(node.name.text);
@@ -192657,8 +192825,21 @@ function declinedFactoryMessage(callee, d) {
192657
192825
  if (d.code === "BF112") {
192658
192826
  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.`;
192659
192827
  }
192828
+ if (d.code === "BF113") {
192829
+ return `Reactive factory '${callee}' cannot be inlined: it needs ${d.detail} ` + `imported 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 (import { x as y }).`;
192830
+ }
192660
192831
  return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
192661
192832
  }
192833
+ function declinedFactoryErrorCode(code2) {
192834
+ switch (code2) {
192835
+ case "BF112":
192836
+ return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
192837
+ case "BF113":
192838
+ return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
192839
+ default:
192840
+ return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
192841
+ }
192842
+ }
192662
192843
  function validateReactiveFactoryCalls(ctx) {
192663
192844
  if (!ctx.componentNode)
192664
192845
  return;
@@ -192682,7 +192863,7 @@ function validateReactiveFactoryCalls(ctx) {
192682
192863
  continue;
192683
192864
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
192684
192865
  if (declinedEntry) {
192685
- ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192866
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192686
192867
  continue;
192687
192868
  }
192688
192869
  const objectFactory = ctx.reactiveFactories.get(callee);
@@ -192739,7 +192920,7 @@ function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
192739
192920
  }
192740
192921
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
192741
192922
  if (declinedEntry) {
192742
- ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192923
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192743
192924
  return;
192744
192925
  }
192745
192926
  if (ctx.reactiveShapedHelpers.has(callee)) {
@@ -193180,19 +193361,83 @@ function resolveFreeRefs(node, env) {
193180
193361
  // ../jsx/src/to-locale-date-lowering.ts
193181
193362
  var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
193182
193363
  var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
193183
- var patternCache = new Map;
193184
- function resolveLocaleDatePattern(locale) {
193185
- const cached = patternCache.get(locale);
193364
+ var formatCache = new Map;
193365
+ var namesCache = new Map;
193366
+ function deriveMonthNames(locale, ctx) {
193367
+ return deriveNamesCached(`${locale}|m|${ctx}`, () => {
193368
+ const months2 = (width) => Array.from({ length: 12 }, (_, m) => probePart(locale, ctx === "formatting" ? { month: width, day: "numeric" } : { month: width }, Date.UTC(2001, m, 15), "month"));
193369
+ return [...months2("long"), ...months2("short")];
193370
+ });
193371
+ }
193372
+ function deriveWeekdayNames(locale, ctx) {
193373
+ return deriveNamesCached(`${locale}|w|${ctx}`, () => {
193374
+ const weekdays = (width) => Array.from({ length: 7 }, (_, d) => probePart(locale, ctx === "formatting" ? { weekday: width, month: "numeric", day: "numeric" } : { weekday: width }, Date.UTC(2023, 0, 1 + d), "weekday"));
193375
+ return [...weekdays("long"), ...weekdays("short")];
193376
+ });
193377
+ }
193378
+ function probePart(locale, options, utc, type2) {
193379
+ const parts = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).formatToParts(new Date(utc));
193380
+ const found = parts.find((p) => p.type === type2);
193381
+ if (!found || !found.value)
193382
+ throw new Error("missing part");
193383
+ return found.value;
193384
+ }
193385
+ function deriveNamesCached(key, derive) {
193386
+ const cached = namesCache.get(key);
193186
193387
  if (cached !== undefined)
193187
193388
  return cached;
193188
- const derived = derivePattern(locale);
193189
- patternCache.set(locale, derived);
193389
+ let derived;
193390
+ try {
193391
+ derived = derive();
193392
+ } catch {
193393
+ derived = null;
193394
+ }
193395
+ namesCache.set(key, derived);
193190
193396
  return derived;
193191
193397
  }
193192
- function derivePattern(locale) {
193398
+ function resolveLocaleDateFormat(locale, probeOptions) {
193399
+ const key = `${locale}|${JSON.stringify(probeOptions, Object.keys(probeOptions).sort())}`;
193400
+ const cached = formatCache.get(key);
193401
+ if (cached !== undefined)
193402
+ return cached;
193403
+ const derived = deriveFormat(locale, probeOptions);
193404
+ formatCache.set(key, derived);
193405
+ return derived;
193406
+ }
193407
+ var VERIFY_UTC = new Date(Date.UTC(2001, 4, 13));
193408
+ function renderPatternAt(pattern, names, y, m, d, wd) {
193409
+ const pad2 = (n) => String(n).padStart(2, "0");
193410
+ return pattern.replace(/YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D/g, (token) => {
193411
+ switch (token) {
193412
+ case "YYYY":
193413
+ return String(y).padStart(4, "0");
193414
+ case "MMMM":
193415
+ return names[m - 1] ?? "";
193416
+ case "MMM":
193417
+ return names[12 + m - 1] ?? "";
193418
+ case "MM":
193419
+ return pad2(m);
193420
+ case "M":
193421
+ return String(m);
193422
+ case "DD":
193423
+ return pad2(d);
193424
+ case "D":
193425
+ return String(d);
193426
+ case "dddd":
193427
+ return names[24 + wd] ?? "";
193428
+ default:
193429
+ return names[31 + wd] ?? "";
193430
+ }
193431
+ });
193432
+ }
193433
+ function deriveFormat(locale, probeOptions) {
193434
+ let dtf;
193193
193435
  let parts;
193194
193436
  try {
193195
- const dtf = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
193437
+ dtf = new Intl.DateTimeFormat(locale, {
193438
+ ...probeOptions,
193439
+ timeZone: "UTC"
193440
+ });
193196
193441
  const resolved = dtf.resolvedOptions();
193197
193442
  if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
193198
193443
  return null;
@@ -193200,7 +193445,18 @@ function derivePattern(locale) {
193200
193445
  } catch {
193201
193446
  return null;
193202
193447
  }
193448
+ const monthTables = [
193449
+ deriveMonthNames(locale, "formatting"),
193450
+ deriveMonthNames(locale, "standalone")
193451
+ ];
193452
+ const weekdayTables = [
193453
+ deriveWeekdayNames(locale, "formatting"),
193454
+ deriveWeekdayNames(locale, "standalone")
193455
+ ];
193456
+ let monthTable = null;
193457
+ let weekdayTable = null;
193203
193458
  let pattern = "";
193459
+ let usesNames = false;
193204
193460
  for (const part of parts) {
193205
193461
  switch (part.type) {
193206
193462
  case "year":
@@ -193208,14 +193464,27 @@ function derivePattern(locale) {
193208
193464
  return null;
193209
193465
  pattern += "YYYY";
193210
193466
  break;
193211
- case "month":
193212
- if (part.value === "2")
193467
+ case "month": {
193468
+ if (part.value === "2") {
193213
193469
  pattern += "M";
193214
- else if (part.value === "02")
193470
+ break;
193471
+ }
193472
+ if (part.value === "02") {
193215
193473
  pattern += "MM";
193474
+ break;
193475
+ }
193476
+ const wide = monthTables.find((t) => t && part.value === t[1]) ?? null;
193477
+ const abbr = wide ? null : monthTables.find((t) => t && part.value === t[12 + 1]) ?? null;
193478
+ if (wide)
193479
+ pattern += "MMMM";
193480
+ else if (abbr)
193481
+ pattern += "MMM";
193216
193482
  else
193217
193483
  return null;
193484
+ monthTable = wide ?? abbr;
193485
+ usesNames = true;
193218
193486
  break;
193487
+ }
193219
193488
  case "day":
193220
193489
  if (part.value === "3")
193221
193490
  pattern += "D";
@@ -193224,8 +193493,21 @@ function derivePattern(locale) {
193224
193493
  else
193225
193494
  return null;
193226
193495
  break;
193496
+ case "weekday": {
193497
+ const wide = weekdayTables.find((t) => t && part.value === t[6]) ?? null;
193498
+ const abbr = wide ? null : weekdayTables.find((t) => t && part.value === t[7 + 6]) ?? null;
193499
+ if (wide)
193500
+ pattern += "dddd";
193501
+ else if (abbr)
193502
+ pattern += "ddd";
193503
+ else
193504
+ return null;
193505
+ weekdayTable = wide ?? abbr;
193506
+ usesNames = true;
193507
+ break;
193508
+ }
193227
193509
  case "literal":
193228
- if (/[YMD]/.test(part.value))
193510
+ if (/[YMD]/.test(part.value) || /ddd/.test(part.value))
193229
193511
  return null;
193230
193512
  pattern += part.value;
193231
193513
  break;
@@ -193233,9 +193515,73 @@ function derivePattern(locale) {
193233
193515
  return null;
193234
193516
  }
193235
193517
  }
193236
- if (!pattern.includes("YYYY") || !/M/.test(pattern) || !/D/.test(pattern))
193518
+ if (!/YYYY|MMMM|MMM|MM|M/.test(pattern) && !/DD|D/.test(pattern))
193519
+ return null;
193520
+ if (!usesNames)
193521
+ return { pattern, names: null };
193522
+ const names = [
193523
+ ...monthTable ?? monthTables[0] ?? monthTables[1] ?? Array(24).fill(""),
193524
+ ...weekdayTable ?? weekdayTables[0] ?? weekdayTables[1] ?? Array(14).fill("")
193525
+ ];
193526
+ if (renderPatternAt(pattern, names, 2001, 5, 13, 0) !== dtf.format(VERIFY_UTC))
193237
193527
  return null;
193238
- return pattern;
193528
+ return { pattern, names };
193529
+ }
193530
+ function unionMemberLiteral(member) {
193531
+ const m = /^'([^'\\]*)'$|^"([^"\\]*)"$/.exec(member.raw.trim());
193532
+ return m ? m[1] ?? m[2] : null;
193533
+ }
193534
+ function resolveLocaleUnionMembers(locale, metadata) {
193535
+ let sourcePropName = null;
193536
+ if (metadata.propsObjectName) {
193537
+ if (locale.kind === "member" && !locale.computed && locale.object.kind === "identifier" && locale.object.name === metadata.propsObjectName) {
193538
+ sourcePropName = locale.property;
193539
+ }
193540
+ } else if (locale.kind === "identifier") {
193541
+ const name = locale.name;
193542
+ const param = metadata.propsParams?.find((pp) => pp.name === name);
193543
+ if (param)
193544
+ sourcePropName = param.sourceName ?? param.name;
193545
+ }
193546
+ if (!sourcePropName)
193547
+ return null;
193548
+ const target = sourcePropName;
193549
+ const prop = metadata.propsType?.properties?.find((p) => p.name === target);
193550
+ if (!prop || prop.optional)
193551
+ return null;
193552
+ const type2 = prop.type;
193553
+ if (type2.kind !== "union" || !type2.unionTypes || type2.unionTypes.length === 0)
193554
+ return null;
193555
+ const members = [];
193556
+ for (const member of type2.unionTypes) {
193557
+ const value = unionMemberLiteral(member);
193558
+ if (value === null)
193559
+ return null;
193560
+ members.push(value);
193561
+ }
193562
+ return members;
193563
+ }
193564
+ var strLit = (value) => ({ kind: "literal", value, literalType: "string" });
193565
+ function strArr(values) {
193566
+ return {
193567
+ kind: "array-literal",
193568
+ elements: values.map((v) => strLit(v)),
193569
+ raw: JSON.stringify(values)
193570
+ };
193571
+ }
193572
+ function foldMembers(locale, members, leaves, allEqual) {
193573
+ let expr = leaves[leaves.length - 1];
193574
+ if (allEqual)
193575
+ return expr;
193576
+ for (let i2 = leaves.length - 2;i2 >= 0; i2--) {
193577
+ expr = {
193578
+ kind: "conditional",
193579
+ test: { kind: "binary", op: "===", left: locale, right: strLit(members[i2]) },
193580
+ consequent: leaves[i2],
193581
+ alternate: expr
193582
+ };
193583
+ }
193584
+ return expr;
193239
193585
  }
193240
193586
  function matchToLocaleDateStringCall(callee, args, metadata) {
193241
193587
  if (callee.kind !== "member" || callee.computed)
@@ -193243,17 +193589,23 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
193243
193589
  if (callee.property !== "toLocaleDateString" || args.length !== 2)
193244
193590
  return null;
193245
193591
  const [locale, options] = args;
193246
- if (locale.kind !== "literal" || locale.literalType !== "string")
193247
- return null;
193248
- if (options.kind !== "object-literal" || options.properties.length !== 1)
193249
- return null;
193250
- const prop = options.properties[0];
193251
- if (prop.key !== "timeZone")
193592
+ if (options.kind !== "object-literal")
193252
193593
  return null;
193253
- if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
193254
- return null;
193255
- const tz = String(prop.value.value);
193256
- if (!TO_LOCALE_TZ_RE.test(tz))
193594
+ let tz = null;
193595
+ const probeOptions = {};
193596
+ for (const prop of options.properties) {
193597
+ if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
193598
+ return null;
193599
+ const value = String(prop.value.value);
193600
+ if (prop.key === "timeZone") {
193601
+ if (!TO_LOCALE_TZ_RE.test(value))
193602
+ return null;
193603
+ tz = value;
193604
+ } else {
193605
+ probeOptions[prop.key] = value;
193606
+ }
193607
+ }
193608
+ if (tz === null)
193257
193609
  return null;
193258
193610
  const receiverType = resolveReceiverType(callee.object, metadata, new Map);
193259
193611
  if (!receiverType || receiverType.kind !== "interface")
@@ -193263,19 +193615,64 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
193263
193615
  return null;
193264
193616
  if (metadata.typeDefinitions.some((d) => d.name === typeName))
193265
193617
  return null;
193266
- const pattern = resolveLocaleDatePattern(String(locale.value));
193267
- if (pattern === null)
193618
+ if (locale.kind === "literal" && locale.literalType === "string") {
193619
+ const format3 = resolveLocaleDateFormat(String(locale.value), probeOptions);
193620
+ if (format3 === null)
193621
+ return null;
193622
+ return {
193623
+ kind: "helper-call",
193624
+ helper: "format_date",
193625
+ args: [callee.object, strLit(format3.pattern), strLit(tz), strArr(format3.names ?? [])]
193626
+ };
193627
+ }
193628
+ const members = resolveLocaleUnionMembers(locale, metadata);
193629
+ if (!members)
193268
193630
  return null;
193631
+ const formats = [];
193632
+ for (const member of members) {
193633
+ const format3 = resolveLocaleDateFormat(member, probeOptions);
193634
+ if (format3 === null)
193635
+ return null;
193636
+ formats.push(format3);
193637
+ }
193638
+ const patterns = formats.map((f) => f.pattern);
193639
+ const nameTables = formats.map((f) => JSON.stringify(f.names ?? []));
193269
193640
  return {
193270
193641
  kind: "helper-call",
193271
193642
  helper: "format_date",
193272
193643
  args: [
193273
193644
  callee.object,
193274
- { kind: "literal", value: pattern, literalType: "string" },
193275
- { kind: "literal", value: tz, literalType: "string" }
193645
+ foldMembers(locale, members, patterns.map(strLit), new Set(patterns).size === 1),
193646
+ strLit(tz),
193647
+ foldMembers(locale, members, formats.map((f) => strArr(f.names ?? [])), new Set(nameTables).size === 1)
193276
193648
  ]
193277
193649
  };
193278
193650
  }
193651
+ function foldedArgToClientJs(arg, localeText) {
193652
+ if (arg.kind === "literal")
193653
+ return JSON.stringify(arg.value);
193654
+ if (arg.kind === "array-literal") {
193655
+ const values = [];
193656
+ for (const el of arg.elements) {
193657
+ if (el.kind !== "literal")
193658
+ return null;
193659
+ values.push(String(el.value));
193660
+ }
193661
+ return JSON.stringify(values);
193662
+ }
193663
+ if (arg.kind !== "conditional")
193664
+ return null;
193665
+ const t = arg.test;
193666
+ if (t.kind !== "binary" || t.op !== "===" || t.right.kind !== "literal")
193667
+ return null;
193668
+ if (arg.consequent.kind !== "literal" && arg.consequent.kind !== "array-literal")
193669
+ return null;
193670
+ const cons = foldedArgToClientJs(arg.consequent, localeText);
193671
+ const rest = foldedArgToClientJs(arg.alternate, localeText);
193672
+ if (cons === null || rest === null)
193673
+ return null;
193674
+ return `${localeText} === ${JSON.stringify(t.right.value)} ? ${cons} : ${rest}`;
193675
+ }
193279
193676
  var toLocaleDatePlugin = {
193280
193677
  name: "toLocaleDateString",
193281
193678
  prepare(metadata) {
@@ -193441,12 +193838,22 @@ function lowerToLocaleDateCalls(text, expr, ctx) {
193441
193838
  const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
193442
193839
  if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
193443
193840
  continue;
193444
- const [, patternArg, tzArg] = node.args;
193445
- if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
193841
+ const [, patternArg, tzArg, namesArg] = node.args;
193842
+ if (!patternArg || tzArg?.kind !== "literal")
193843
+ continue;
193844
+ const localeText = ctx.getJS(call.arguments[0]);
193845
+ const patternJs = foldedArgToClientJs(patternArg, localeText);
193846
+ if (patternJs === null)
193446
193847
  continue;
193848
+ let namesJs = null;
193849
+ if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
193850
+ namesJs = foldedArgToClientJs(namesArg, localeText);
193851
+ if (namesJs === null)
193852
+ continue;
193853
+ }
193447
193854
  const receiverText = ctx.getJS(propAccess.expression);
193448
193855
  const matchText = ctx.getJS(call);
193449
- result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
193856
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
193450
193857
  }
193451
193858
  return restore(result);
193452
193859
  }
@@ -197507,15 +197914,16 @@ function isOmitBranch(node) {
197507
197914
  }
197508
197915
  // ../jsx/src/format-date-lowering.ts
197509
197916
  var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
197917
+ var EMPTY_NAMES = { kind: "array-literal", elements: [], raw: "[]" };
197510
197918
  function matchFormatDateCall(callee, args, locals) {
197511
197919
  if (callee.kind !== "identifier" || !locals.has(callee.name))
197512
197920
  return null;
197513
- if (args.length < 2 || args.length > 3)
197921
+ if (args.length < 2 || args.length > 4)
197514
197922
  return null;
197515
197923
  return {
197516
197924
  kind: "helper-call",
197517
197925
  helper: "format_date",
197518
- args: [args[0], args[1], args[2] ?? UTC_LITERAL]
197926
+ args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES]
197519
197927
  };
197520
197928
  }
197521
197929
  var formatDatePlugin = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.23.0",
3
+ "version": "0.24.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.23.0"
42
+ "@barefootjs/jsx": "0.24.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"