@barefootjs/test 0.21.4 → 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 +1057 -71
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -189221,18 +189221,39 @@ function createTemplateAwareStringProtector() {
189221
189221
  stash.push(s);
189222
189222
  return `__STRLIT_${i2}__`;
189223
189223
  };
189224
+ const STRING_LIT_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g;
189224
189225
  const protect = (s) => {
189225
189226
  s = s.replace(/`([^`]*)`/g, (_full, inner) => {
189226
189227
  const parts = splitTemplateInterpolations(inner);
189227
189228
  return "`" + parts.map((p) => p.startsWith("${") ? p : save(p)).join("") + "`";
189228
189229
  });
189229
- s = s.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, (m) => save(m));
189230
+ s = s.replace(STRING_LIT_RE, (m) => save(m));
189230
189231
  return s;
189231
189232
  };
189232
189233
  const restore = (s) => {
189233
189234
  return s.replace(/__STRLIT_(\d+)__/g, (_, i2) => stash[Number(i2)]);
189234
189235
  };
189235
- return { protect, restore };
189236
+ const replaceProtectedCall = (haystack, needle, replacement) => {
189237
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
189238
+ const litValues = [];
189239
+ let pattern = "";
189240
+ let last = 0;
189241
+ for (const m of needle.matchAll(STRING_LIT_RE)) {
189242
+ pattern += escape(needle.slice(last, m.index));
189243
+ pattern += "__STRLIT_(\\d+)__";
189244
+ litValues.push(m[0]);
189245
+ last = m.index + m[0].length;
189246
+ }
189247
+ pattern += escape(needle.slice(last));
189248
+ for (const m of haystack.matchAll(new RegExp(pattern, "g"))) {
189249
+ const verified = m.slice(1).every((idx, i2) => stash[Number(idx)] === litValues[i2]);
189250
+ if (!verified)
189251
+ continue;
189252
+ return haystack.slice(0, m.index) + replacement() + haystack.slice(m.index + m[0].length);
189253
+ }
189254
+ return haystack;
189255
+ };
189256
+ return { protect, restore, replaceProtectedCall };
189236
189257
  }
189237
189258
  var VOID_ELEMENTS = new Set([
189238
189259
  "area",
@@ -189551,6 +189572,9 @@ function createAnalyzerContext(sourceFile, filePath) {
189551
189572
  jsxFunctions: new Map,
189552
189573
  jsxMultiReturnFunctions: new Map,
189553
189574
  reactiveFactories: new Map,
189575
+ declinedReactiveFactories: new Map,
189576
+ reactiveShapedHelpers: new Set,
189577
+ cleanFactoryImports: new Set,
189554
189578
  signalTupleRefs: new Map,
189555
189579
  propsType: null,
189556
189580
  propsParams: [],
@@ -189739,7 +189763,10 @@ var ErrorCodes = {
189739
189763
  STAGE_INIT_LOCAL_IN_TEMPLATE: "BF061",
189740
189764
  STAGE_AWAIT_IN_TEMPLATE: "BF062",
189741
189765
  INLINE_JSX_CALLBACK_CAPTURE: "BF080",
189742
- UNRECOGNIZED_REACTIVE_FACTORY: "BF110"
189766
+ UNRECOGNIZED_REACTIVE_FACTORY: "BF110",
189767
+ REACTIVE_FACTORY_RENAME_UNSUPPORTED: "BF111",
189768
+ REACTIVE_FACTORY_MODULE_CAPTURE: "BF112",
189769
+ REACTIVE_FACTORY_IMPORT_COLLISION: "BF113"
189743
189770
  };
189744
189771
  var errorMessages = {
189745
189772
  [ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
@@ -189763,7 +189790,10 @@ var errorMessages = {
189763
189790
  [ErrorCodes.STAGE_INIT_LOCAL_IN_TEMPLATE]: "Init-scope local referenced from template scope. The template lambda runs at module scope (via render() / renderChild()) and cannot reach init-body locals. Wrap the JSX expression in /* @client */, or lift the value to a prop or module-scope const.",
189764
189791
  [ErrorCodes.STAGE_AWAIT_IN_TEMPLATE]: "AwaitExpression in template scope. The generated template and init functions are synchronous — a bare `await` produces a SyntaxError at parse time. Move the await into the component body (before the return) or into an onMount/effect callback, and pass the resolved value to JSX.",
189765
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.",
189766
- [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
+ [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
+ [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
+ [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."
189767
189797
  };
189768
189798
  function createError(code2, loc, options) {
189769
189799
  if (code2 === undefined || !(code2 in errorMessages)) {
@@ -190032,6 +190062,10 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
190032
190062
  }
190033
190063
  const ctx = createAnalyzerContext(sourceFile, filePath);
190034
190064
  ctx.checker = checker;
190065
+ ctx.reactiveFactories = prescan.factories;
190066
+ ctx.declinedReactiveFactories = prescan.declined;
190067
+ ctx.reactiveShapedHelpers = prescan.reactiveShaped;
190068
+ ctx.cleanFactoryImports = prescan.cleanFactoryImports;
190035
190069
  const brandImportLoc = findBrandPackageImportLoc(sourceFile, filePath);
190036
190070
  if (!hadSharedProgram && brandImportLoc !== null) {
190037
190071
  ctx.errors.push(createError(ErrorCodes.SHARED_PROGRAM_REQUIRED, brandImportLoc));
@@ -190937,6 +190971,7 @@ var CLIENT_EXPORTS = new Set([
190937
190971
  "cleanupPortalPlaceholder",
190938
190972
  "createSearchParams",
190939
190973
  "queryHref",
190974
+ "formatDate",
190940
190975
  "Async",
190941
190976
  "Region"
190942
190977
  ]);
@@ -192164,27 +192199,368 @@ var REACTIVE_PRIMITIVES = new Set([
192164
192199
  function prescanReactiveFactoriesInSource(source, filePath) {
192165
192200
  const sourceFile = import_typescript8.default.createSourceFile(filePath + ".prescan", source, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192166
192201
  const factories = new Map;
192202
+ const declined = new Map;
192203
+ const reactiveShaped = new Set;
192204
+ const cleanFactoryImports = new Set;
192167
192205
  function visitTop(node) {
192168
192206
  if (import_typescript8.default.isFunctionDeclaration(node) && node.name && node.body) {
192169
- const info = detectReactiveFactory(node, sourceFile, filePath);
192170
- if (info)
192171
- factories.set(node.name.text, info);
192207
+ const det = detectReactiveFactory(node, sourceFile, filePath);
192208
+ if (!det)
192209
+ return;
192210
+ switch (det.kind) {
192211
+ case "factory":
192212
+ factories.set(node.name.text, det.info);
192213
+ break;
192214
+ case "declined":
192215
+ declined.set(node.name.text, det.declined);
192216
+ break;
192217
+ case "reactive-shaped":
192218
+ reactiveShaped.add(node.name.text);
192219
+ break;
192220
+ }
192172
192221
  }
192173
192222
  }
192174
192223
  import_typescript8.default.forEachChild(sourceFile, visitTop);
192175
- return { factories, sourceFile };
192224
+ const result = { factories, declined, reactiveShaped, cleanFactoryImports, sourceFile };
192225
+ prescanImportedReactiveFactories(sourceFile, filePath, result);
192226
+ return result;
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
+ }
192296
+ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
192297
+ const candidateCallees = new Set;
192298
+ function collectCandidates(node) {
192299
+ if (import_typescript8.default.isVariableDeclaration(node) && (import_typescript8.default.isArrayBindingPattern(node.name) || import_typescript8.default.isObjectBindingPattern(node.name)) && node.initializer && import_typescript8.default.isCallExpression(node.initializer) && import_typescript8.default.isIdentifier(node.initializer.expression)) {
192300
+ candidateCallees.add(node.initializer.expression.text);
192301
+ }
192302
+ import_typescript8.default.forEachChild(node, collectCandidates);
192303
+ }
192304
+ collectCandidates(entrySourceFile);
192305
+ if (candidateCallees.size === 0)
192306
+ return;
192307
+ const importsToCheck = [];
192308
+ for (const stmt of entrySourceFile.statements) {
192309
+ if (!import_typescript8.default.isImportDeclaration(stmt))
192310
+ continue;
192311
+ if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192312
+ continue;
192313
+ const src = stmt.moduleSpecifier.text;
192314
+ if (!src.startsWith("./") && !src.startsWith("../"))
192315
+ continue;
192316
+ if (stmt.importClause?.isTypeOnly)
192317
+ continue;
192318
+ const namedBindings = stmt.importClause?.namedBindings;
192319
+ if (!namedBindings || !import_typescript8.default.isNamedImports(namedBindings))
192320
+ continue;
192321
+ const specs = [];
192322
+ for (const el of namedBindings.elements) {
192323
+ if (el.isTypeOnly)
192324
+ continue;
192325
+ const local = el.name.text;
192326
+ if (!candidateCallees.has(local))
192327
+ continue;
192328
+ specs.push({ exported: (el.propertyName ?? el.name).text, local });
192329
+ }
192330
+ if (specs.length === 0)
192331
+ continue;
192332
+ importsToCheck.push({ src, specs });
192333
+ }
192334
+ if (importsToCheck.length === 0)
192335
+ return;
192336
+ const entryBindingNames = collectEntryBindingNames(entrySourceFile);
192337
+ const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
192338
+ const plannedInjections = new Map;
192339
+ for (const { src, specs } of importsToCheck) {
192340
+ const resolved = resolveRelativeImportToFile(src, filePath);
192341
+ if (!resolved)
192342
+ continue;
192343
+ let content;
192344
+ try {
192345
+ content = fs.readFileSync(resolved, "utf8");
192346
+ } catch {
192347
+ continue;
192348
+ }
192349
+ const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
192350
+ 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;
192357
+ }
192358
+ const helperSf = import_typescript8.default.createSourceFile(resolved + ".prescan", content, import_typescript8.default.ScriptTarget.Latest, true, import_typescript8.default.ScriptKind.TSX);
192359
+ const localFns = new Map;
192360
+ const exportedFns = new Map;
192361
+ for (const stmt of helperSf.statements) {
192362
+ if (import_typescript8.default.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
192363
+ localFns.set(stmt.name.text, stmt);
192364
+ const hasExportModifier = stmt.modifiers?.some((m) => m.kind === import_typescript8.default.SyntaxKind.ExportKeyword) ?? false;
192365
+ const hasDefaultModifier = stmt.modifiers?.some((m) => m.kind === import_typescript8.default.SyntaxKind.DefaultKeyword) ?? false;
192366
+ if (hasExportModifier && !hasDefaultModifier) {
192367
+ exportedFns.set(stmt.name.text, stmt);
192368
+ }
192369
+ }
192370
+ }
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
+ for (const el of stmt.exportClause.elements) {
192374
+ if (el.isTypeOnly)
192375
+ continue;
192376
+ const fn = localFns.get((el.propertyName ?? el.name).text);
192377
+ if (fn)
192378
+ exportedFns.set(el.name.text, fn);
192379
+ }
192380
+ }
192381
+ }
192382
+ const moduleBindings = collectHelperModuleValueBindings(helperSf);
192383
+ for (const spec of specs) {
192384
+ if (alreadyKnown(spec.local))
192385
+ continue;
192386
+ const fn = exportedFns.get(spec.exported);
192387
+ if (!fn) {
192388
+ result.cleanFactoryImports.add(spec.local);
192389
+ continue;
192390
+ }
192391
+ const det = detectReactiveFactory(fn, helperSf, resolved);
192392
+ if (!det) {
192393
+ result.cleanFactoryImports.add(spec.local);
192394
+ continue;
192395
+ }
192396
+ switch (det.kind) {
192397
+ case "reactive-shaped":
192398
+ result.reactiveShaped.add(spec.local);
192399
+ break;
192400
+ case "declined":
192401
+ result.declined.set(spec.local, det.declined);
192402
+ break;
192403
+ case "factory": {
192404
+ const capture = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
192405
+ if (capture.captured.length > 0) {
192406
+ result.declined.set(spec.local, {
192407
+ code: "BF112",
192408
+ detail: `'${capture.captured.join("', '")}'`,
192409
+ loc: det.info.loc
192410
+ });
192411
+ break;
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);
192462
+ break;
192463
+ }
192464
+ }
192465
+ }
192466
+ }
192467
+ }
192468
+ function collectHelperModuleValueBindings(sf) {
192469
+ const local = new Set;
192470
+ const imported = new Map;
192471
+ for (const stmt of sf.statements) {
192472
+ if (import_typescript8.default.isVariableStatement(stmt)) {
192473
+ const out = [];
192474
+ for (const decl of stmt.declarationList.declarations) {
192475
+ addBindingNames(decl.name, out);
192476
+ }
192477
+ for (const n of out)
192478
+ local.add(n);
192479
+ continue;
192480
+ }
192481
+ if ((import_typescript8.default.isFunctionDeclaration(stmt) || import_typescript8.default.isClassDeclaration(stmt) || import_typescript8.default.isEnumDeclaration(stmt)) && stmt.name) {
192482
+ local.add(stmt.name.text);
192483
+ continue;
192484
+ }
192485
+ if (import_typescript8.default.isImportDeclaration(stmt)) {
192486
+ if (stmt.importClause?.isTypeOnly)
192487
+ continue;
192488
+ if (!import_typescript8.default.isStringLiteral(stmt.moduleSpecifier))
192489
+ continue;
192490
+ const src = stmt.moduleSpecifier.text;
192491
+ if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
192492
+ continue;
192493
+ if (stmt.importClause?.name)
192494
+ local.add(stmt.importClause.name.text);
192495
+ const namedBindings = stmt.importClause?.namedBindings;
192496
+ if (namedBindings && import_typescript8.default.isNamedImports(namedBindings)) {
192497
+ 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 });
192501
+ }
192502
+ }
192503
+ if (namedBindings && import_typescript8.default.isNamespaceImport(namedBindings)) {
192504
+ local.add(namedBindings.name.text);
192505
+ }
192506
+ }
192507
+ }
192508
+ return { local, imported };
192509
+ }
192510
+ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
192511
+ if (!fn.body)
192512
+ return { captured: [], importedRefs: [] };
192513
+ const free = extractFreeIdentifiersFromNode(fn.body);
192514
+ const exclude = new Set(info.params);
192515
+ for (const b of info.localBindings)
192516
+ exclude.add(b);
192517
+ for (const r of info.returnTupleIdentifiers)
192518
+ exclude.add(r);
192519
+ for (const p of REACTIVE_PRIMITIVES)
192520
+ exclude.add(p);
192521
+ exclude.add(selfName);
192522
+ const captured = [];
192523
+ const importedRefs = [];
192524
+ for (const id of free) {
192525
+ if (exclude.has(id))
192526
+ continue;
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 });
192534
+ }
192535
+ captured.sort();
192536
+ importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
192537
+ return { captured, importedRefs };
192176
192538
  }
192177
192539
  function detectReactiveFactory(node, sourceFile, filePath) {
192178
192540
  if (!node.body || !node.name)
192179
192541
  return null;
192180
- let tupleReturn = null;
192542
+ let hasReactiveCall = false;
192543
+ function checkForReactive(n) {
192544
+ if (hasReactiveCall)
192545
+ return;
192546
+ if (import_typescript8.default.isCallExpression(n) && import_typescript8.default.isIdentifier(n.expression) && REACTIVE_PRIMITIVES.has(n.expression.text)) {
192547
+ hasReactiveCall = true;
192548
+ return;
192549
+ }
192550
+ import_typescript8.default.forEachChild(n, checkForReactive);
192551
+ }
192552
+ checkForReactive(node.body);
192553
+ if (!hasReactiveCall)
192554
+ return null;
192555
+ const loc = getSourceLocation(node, sourceFile, filePath);
192556
+ let returnExpr = null;
192181
192557
  let returnCount = 0;
192182
192558
  for (const stmt of node.body.statements) {
192183
192559
  if (!import_typescript8.default.isReturnStatement(stmt))
192184
192560
  continue;
192185
192561
  returnCount++;
192186
192562
  if (!stmt.expression)
192187
- return null;
192563
+ return { kind: "reactive-shaped" };
192188
192564
  let expr = stmt.expression;
192189
192565
  while (import_typescript8.default.isParenthesizedExpression(expr))
192190
192566
  expr = expr.expression;
@@ -192192,33 +192568,42 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192192
192568
  expr = expr.expression;
192193
192569
  if (import_typescript8.default.isTypeAssertionExpression(expr))
192194
192570
  expr = expr.expression;
192195
- if (!import_typescript8.default.isArrayLiteralExpression(expr))
192196
- return null;
192197
- tupleReturn = expr;
192571
+ returnExpr = expr;
192198
192572
  }
192199
- if (returnCount !== 1 || !tupleReturn)
192200
- return null;
192573
+ if (returnCount !== 1 || !returnExpr)
192574
+ return { kind: "reactive-shaped" };
192201
192575
  const returnTupleIdentifiers = [];
192202
- for (const el of tupleReturn.elements) {
192203
- if (!import_typescript8.default.isIdentifier(el))
192204
- return null;
192205
- returnTupleIdentifiers.push(el.text);
192206
- }
192207
- if (returnTupleIdentifiers.length === 0)
192208
- return null;
192209
- let hasReactiveCall = false;
192210
- function checkForReactive(n) {
192211
- if (hasReactiveCall)
192212
- return;
192213
- if (import_typescript8.default.isCallExpression(n) && import_typescript8.default.isIdentifier(n.expression) && REACTIVE_PRIMITIVES.has(n.expression.text)) {
192214
- hasReactiveCall = true;
192215
- return;
192576
+ let returnKind;
192577
+ if (import_typescript8.default.isArrayLiteralExpression(returnExpr)) {
192578
+ returnKind = "tuple";
192579
+ for (const el of returnExpr.elements) {
192580
+ if (!import_typescript8.default.isIdentifier(el))
192581
+ return { kind: "reactive-shaped" };
192582
+ returnTupleIdentifiers.push(el.text);
192583
+ }
192584
+ if (returnTupleIdentifiers.length === 0)
192585
+ return { kind: "reactive-shaped" };
192586
+ } else if (import_typescript8.default.isObjectLiteralExpression(returnExpr)) {
192587
+ returnKind = "object";
192588
+ const hasNonShorthand = returnExpr.properties.some((p) => !import_typescript8.default.isShorthandPropertyAssignment(p));
192589
+ if (hasNonShorthand) {
192590
+ return {
192591
+ kind: "declined",
192592
+ declined: {
192593
+ code: "BF111",
192594
+ detail: `return object of '${node.name.text}' uses non-shorthand properties`,
192595
+ loc
192596
+ }
192597
+ };
192216
192598
  }
192217
- import_typescript8.default.forEachChild(n, checkForReactive);
192599
+ for (const p of returnExpr.properties) {
192600
+ returnTupleIdentifiers.push(p.name.text);
192601
+ }
192602
+ if (returnTupleIdentifiers.length === 0)
192603
+ return { kind: "reactive-shaped" };
192604
+ } else {
192605
+ return { kind: "reactive-shaped" };
192218
192606
  }
192219
- checkForReactive(node.body);
192220
- if (!hasReactiveCall)
192221
- return null;
192222
192607
  const localBindings = [];
192223
192608
  for (const stmt of node.body.statements) {
192224
192609
  if (import_typescript8.default.isVariableStatement(stmt)) {
@@ -192231,19 +192616,24 @@ function detectReactiveFactory(node, sourceFile, filePath) {
192231
192616
  }
192232
192617
  const bodyStatements = node.body.statements.filter((s) => !import_typescript8.default.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
192233
192618
  `);
192234
- const params = node.parameters.map((p) => {
192235
- if (import_typescript8.default.isIdentifier(p.name))
192236
- return p.name.text;
192237
- return "";
192238
- });
192239
- if (params.some((p) => p === ""))
192240
- return null;
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;
192624
+ }
192625
+ return { kind: "reactive-shaped" };
192626
+ }
192241
192627
  return {
192242
- params,
192243
- bodySource: bodyStatements,
192244
- returnTupleIdentifiers,
192245
- localBindings,
192246
- loc: getSourceLocation(node, sourceFile, filePath)
192628
+ kind: "factory",
192629
+ info: {
192630
+ params,
192631
+ bodySource: bodyStatements,
192632
+ returnTupleIdentifiers,
192633
+ returnKind,
192634
+ localBindings,
192635
+ loc
192636
+ }
192247
192637
  };
192248
192638
  }
192249
192639
  function addBindingNames(name, out) {
@@ -192268,6 +192658,7 @@ function rewriteFactoryCallsInSource(source, prescan) {
192268
192658
  const { factories, sourceFile } = prescan;
192269
192659
  const edits = [];
192270
192660
  let callSiteIndex = 0;
192661
+ const inlinedFactories = new Set;
192271
192662
  function visitStmt(node, inComponent) {
192272
192663
  if (import_typescript8.default.isVariableStatement(node) && inComponent) {
192273
192664
  for (const decl of node.declarationList.declarations) {
@@ -192281,8 +192672,6 @@ function rewriteFactoryCallsInSource(source, prescan) {
192281
192672
  });
192282
192673
  }
192283
192674
  function maybeRewriteDecl(stmt, decl) {
192284
- if (!import_typescript8.default.isArrayBindingPattern(decl.name))
192285
- return;
192286
192675
  if (!decl.initializer || !import_typescript8.default.isCallExpression(decl.initializer))
192287
192676
  return;
192288
192677
  if (!import_typescript8.default.isIdentifier(decl.initializer.expression))
@@ -192291,7 +192680,21 @@ function rewriteFactoryCallsInSource(source, prescan) {
192291
192680
  const factory = factories.get(factoryName);
192292
192681
  if (!factory)
192293
192682
  return;
192294
- const elements = decl.name.elements;
192683
+ if (import_typescript8.default.isArrayBindingPattern(decl.name)) {
192684
+ if (factory.returnKind !== "tuple")
192685
+ return;
192686
+ rewriteTupleDecl(stmt, decl.name, decl.initializer, factory);
192687
+ return;
192688
+ }
192689
+ if (import_typescript8.default.isObjectBindingPattern(decl.name)) {
192690
+ if (factory.returnKind !== "object")
192691
+ return;
192692
+ rewriteObjectDecl(stmt, decl.name, decl.initializer, factory);
192693
+ return;
192694
+ }
192695
+ }
192696
+ function rewriteTupleDecl(stmt, pattern, call, factory) {
192697
+ const elements = pattern.elements;
192295
192698
  if (elements.length !== factory.returnTupleIdentifiers.length)
192296
192699
  return;
192297
192700
  const callerNames = [];
@@ -192300,15 +192703,43 @@ function rewriteFactoryCallsInSource(source, prescan) {
192300
192703
  return;
192301
192704
  callerNames.push(el.name.text);
192302
192705
  }
192303
- const argTexts = decl.initializer.arguments.map((a) => a.getText(sourceFile));
192706
+ const excludeFromSuffixRename = new Set(factory.params);
192707
+ for (const r of factory.returnTupleIdentifiers)
192708
+ excludeFromSuffixRename.add(r);
192709
+ const renameReturnToCallerNames = new Map;
192710
+ for (let i2 = 0;i2 < factory.returnTupleIdentifiers.length; i2++) {
192711
+ renameReturnToCallerNames.set(factory.returnTupleIdentifiers[i2], callerNames[i2]);
192712
+ }
192713
+ inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, renameReturnToCallerNames);
192714
+ }
192715
+ function rewriteObjectDecl(stmt, pattern, call, factory) {
192716
+ const destructured = new Set;
192717
+ for (const el of pattern.elements) {
192718
+ if (el.dotDotDotToken)
192719
+ return;
192720
+ if (el.propertyName)
192721
+ return;
192722
+ if (el.initializer)
192723
+ return;
192724
+ if (!import_typescript8.default.isIdentifier(el.name))
192725
+ return;
192726
+ if (!factory.returnTupleIdentifiers.includes(el.name.text))
192727
+ return;
192728
+ destructured.add(el.name.text);
192729
+ }
192730
+ const excludeFromSuffixRename = new Set(factory.params);
192731
+ for (const d of destructured)
192732
+ excludeFromSuffixRename.add(d);
192733
+ inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, null);
192734
+ }
192735
+ function inlineFactoryCallAtSite(stmt, factory, args, excludeFromSuffixRename, renameReturnToCallerNames) {
192736
+ const argTexts = args.map((a) => a.getText(sourceFile));
192304
192737
  const thisCallIndex = callSiteIndex++;
192305
192738
  const suffix = `_bf${thisCallIndex}`;
192306
192739
  let body = factory.bodySource;
192307
192740
  const internalRenames = new Set(factory.localBindings);
192308
- for (const p of factory.params)
192309
- internalRenames.delete(p);
192310
- for (const r of factory.returnTupleIdentifiers)
192311
- internalRenames.delete(r);
192741
+ for (const ex of excludeFromSuffixRename)
192742
+ internalRenames.delete(ex);
192312
192743
  for (const name of internalRenames) {
192313
192744
  body = body.replace(new RegExp(`\\b${escapeRegex(name)}\\b`, "g"), name + suffix);
192314
192745
  }
@@ -192319,20 +192750,44 @@ function rewriteFactoryCallsInSource(source, prescan) {
192319
192750
  const wrapped = atomicArg.test(a.trim()) ? a.trim() : `(${a})`;
192320
192751
  body = body.replace(new RegExp(`\\b${escapeRegex(p)}\\b`, "g"), wrapped);
192321
192752
  }
192322
- for (let i2 = 0;i2 < factory.returnTupleIdentifiers.length; i2++) {
192323
- const n = factory.returnTupleIdentifiers[i2];
192324
- const caller = callerNames[i2];
192325
- body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
192753
+ if (renameReturnToCallerNames) {
192754
+ for (const [n, caller] of renameReturnToCallerNames) {
192755
+ body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
192756
+ }
192326
192757
  }
192327
192758
  edits.push({
192328
192759
  start: stmt.getStart(sourceFile),
192329
192760
  end: stmt.getEnd(),
192330
192761
  replacement: body
192331
192762
  });
192763
+ inlinedFactories.add(factory);
192332
192764
  }
192333
192765
  visitStmt(sourceFile, false);
192334
192766
  if (edits.length === 0)
192335
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
+ }
192336
192791
  edits.sort((a, b) => b.start - a.start);
192337
192792
  let out = source;
192338
192793
  for (const e of edits) {
@@ -192340,6 +192795,20 @@ function rewriteFactoryCallsInSource(source, prescan) {
192340
192795
  }
192341
192796
  return out;
192342
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
+ }
192343
192812
  function isPascalCaseComponentFn(node) {
192344
192813
  if (import_typescript8.default.isFunctionDeclaration(node) && node.name) {
192345
192814
  return /^[A-Z]/.test(node.name.text);
@@ -192352,6 +192821,25 @@ function isPascalCaseComponentFn(node) {
192352
192821
  function escapeRegex(s) {
192353
192822
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192354
192823
  }
192824
+ function declinedFactoryMessage(callee, d) {
192825
+ if (d.code === "BF112") {
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.`;
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
+ }
192831
+ return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
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
+ }
192355
192843
  function validateReactiveFactoryCalls(ctx) {
192356
192844
  if (!ctx.componentNode)
192357
192845
  return;
@@ -192362,26 +192850,104 @@ function validateReactiveFactoryCalls(ctx) {
192362
192850
  if (!import_typescript8.default.isVariableStatement(stmt))
192363
192851
  continue;
192364
192852
  for (const decl of stmt.declarationList.declarations) {
192365
- if (!import_typescript8.default.isArrayBindingPattern(decl.name))
192366
- continue;
192367
192853
  if (!decl.initializer || !import_typescript8.default.isCallExpression(decl.initializer))
192368
192854
  continue;
192369
192855
  if (!import_typescript8.default.isIdentifier(decl.initializer.expression))
192370
192856
  continue;
192371
192857
  const callee = decl.initializer.expression.text;
192372
- if (callee === "createSignal" || callee === "createMemo")
192373
- continue;
192374
- if (resolveEnvSignalKey(decl.initializer, ctx))
192858
+ const loc = getSourceLocation(stmt, ctx.sourceFile, ctx.filePath);
192859
+ if (import_typescript8.default.isArrayBindingPattern(decl.name)) {
192860
+ if (callee === "createSignal" || callee === "createMemo")
192861
+ continue;
192862
+ if (resolveEnvSignalKey(decl.initializer, ctx))
192863
+ continue;
192864
+ const declinedEntry = ctx.declinedReactiveFactories.get(callee);
192865
+ if (declinedEntry) {
192866
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192867
+ continue;
192868
+ }
192869
+ const objectFactory = ctx.reactiveFactories.get(callee);
192870
+ if (objectFactory && objectFactory.returnKind === "object") {
192871
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192872
+ severity: "error",
192873
+ message: `'${callee}' is a reactive factory that returns an object — destructure ` + `it with a matching object pattern: const { ${objectFactory.returnTupleIdentifiers.join(", ")} } = ${callee}(...)`
192874
+ }));
192875
+ continue;
192876
+ }
192877
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192878
+ severity: "error",
192879
+ message: `Tuple destructuring of '${callee}(...)': this helper is not a ` + `recognised reactive factory (createSignal / createMemo / a ` + `same-file helper that wraps them with a single \`return [a, b, ...]\`).`,
192880
+ suggestion: {
192881
+ message: `Inline the createSignal call at the call site, or move the ` + `helper into this file as a function that returns a tuple of ` + `identifiers at its single exit point.`
192882
+ }
192883
+ }));
192375
192884
  continue;
192376
- ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, getSourceLocation(stmt, ctx.sourceFile, ctx.filePath), {
192885
+ }
192886
+ if (import_typescript8.default.isObjectBindingPattern(decl.name)) {
192887
+ validateObjectFactoryDestructure(ctx, decl.name, callee, loc);
192888
+ }
192889
+ }
192890
+ }
192891
+ }
192892
+ function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
192893
+ const factory = ctx.reactiveFactories.get(callee);
192894
+ if (factory) {
192895
+ if (factory.returnKind === "tuple") {
192896
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192377
192897
  severity: "error",
192378
- message: `Tuple destructuring of '${callee}(...)': this helper is not a ` + `recognised reactive factory (createSignal / createMemo / a ` + `same-file helper that wraps them with a single \`return [a, b, ...]\`).`,
192379
- suggestion: {
192380
- message: `Inline the createSignal call at the call site, or move the ` + `helper into this file as a function that returns a tuple of ` + `identifiers at its single exit point.`
192381
- }
192898
+ message: `'${callee}' is a reactive factory that returns a tuple destructure ` + `it positionally: const [${factory.returnTupleIdentifiers.join(", ")}] = ${callee}(...)`
192382
192899
  }));
192900
+ return;
192901
+ }
192902
+ const hasUnsupportedElement = pattern.elements.some((el) => !!el.propertyName || !!el.initializer || !!el.dotDotDotToken || !import_typescript8.default.isIdentifier(el.name));
192903
+ if (hasUnsupportedElement) {
192904
+ ctx.errors.push(createError(ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, {
192905
+ severity: "error",
192906
+ message: `Object destructure of reactive factory '${callee}' uses a property ` + `rename, default, or rest element; only shorthand destructuring of ` + `{ ${factory.returnTupleIdentifiers.join(", ")} } is supported.`
192907
+ }));
192908
+ return;
192909
+ }
192910
+ const unknown = pattern.elements.map((el) => import_typescript8.default.isIdentifier(el.name) ? el.name.text : "").filter((name) => name && !factory.returnTupleIdentifiers.includes(name));
192911
+ if (unknown.length > 0) {
192912
+ const label = unknown.length === 1 ? "property" : "properties";
192913
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192914
+ severity: "error",
192915
+ message: `Object destructure of reactive factory '${callee}' references ${label} ` + `'${unknown.join("', '")}' not present in its return { ${factory.returnTupleIdentifiers.join(", ")} }.`
192916
+ }));
192917
+ return;
192918
+ }
192919
+ return;
192920
+ }
192921
+ const declinedEntry = ctx.declinedReactiveFactories.get(callee);
192922
+ if (declinedEntry) {
192923
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
192924
+ return;
192925
+ }
192926
+ if (ctx.reactiveShapedHelpers.has(callee)) {
192927
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192928
+ severity: "error",
192929
+ message: `Object destructure of '${callee}(...)': this helper wraps a reactive ` + `primitive but does not match the inlinable factory shape (single ` + "`return { a, b }` of shorthand identifiers at its one exit point)."
192930
+ }));
192931
+ return;
192932
+ }
192933
+ if (ctx.cleanFactoryImports.has(callee))
192934
+ return;
192935
+ let matchedImportSource = null;
192936
+ for (const imp of ctx.imports) {
192937
+ if (imp.isTypeOnly)
192938
+ continue;
192939
+ const spec = imp.specifiers.find((s) => !s.isTypeOnly && (s.alias ?? s.name) === callee);
192940
+ if (spec) {
192941
+ matchedImportSource = imp.source;
192942
+ break;
192383
192943
  }
192384
192944
  }
192945
+ if (matchedImportSource !== null && !matchedImportSource.startsWith("@barefootjs/") && /^(use|create)[A-Z]/.test(callee)) {
192946
+ ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
192947
+ severity: "error",
192948
+ message: `Object destructure of imported '${callee}(...)': the compiler cannot ` + `inspect this import (non-relative or unresolvable path), so if it wraps ` + `createSignal/createMemo the destructured bindings will not be reactive. Move ` + `the helper to a relative-imported file or inline its body.`
192949
+ }));
192950
+ }
192385
192951
  }
192386
192952
 
192387
192953
  // ../jsx/src/jsx-to-ir.ts
@@ -192792,6 +193358,330 @@ function resolveFreeRefs(node, env) {
192792
193358
  return resolveFreeRefsInternal(node, env, new Set);
192793
193359
  }
192794
193360
 
193361
+ // ../jsx/src/to-locale-date-lowering.ts
193362
+ var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
193363
+ var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
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);
193387
+ if (cached !== undefined)
193388
+ return cached;
193389
+ let derived;
193390
+ try {
193391
+ derived = derive();
193392
+ } catch {
193393
+ derived = null;
193394
+ }
193395
+ namesCache.set(key, derived);
193396
+ return derived;
193397
+ }
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;
193435
+ let parts;
193436
+ try {
193437
+ dtf = new Intl.DateTimeFormat(locale, {
193438
+ ...probeOptions,
193439
+ timeZone: "UTC"
193440
+ });
193441
+ const resolved = dtf.resolvedOptions();
193442
+ if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
193443
+ return null;
193444
+ parts = dtf.formatToParts(PROBE_UTC);
193445
+ } catch {
193446
+ return null;
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;
193458
+ let pattern = "";
193459
+ let usesNames = false;
193460
+ for (const part of parts) {
193461
+ switch (part.type) {
193462
+ case "year":
193463
+ if (part.value !== "2001")
193464
+ return null;
193465
+ pattern += "YYYY";
193466
+ break;
193467
+ case "month": {
193468
+ if (part.value === "2") {
193469
+ pattern += "M";
193470
+ break;
193471
+ }
193472
+ if (part.value === "02") {
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";
193482
+ else
193483
+ return null;
193484
+ monthTable = wide ?? abbr;
193485
+ usesNames = true;
193486
+ break;
193487
+ }
193488
+ case "day":
193489
+ if (part.value === "3")
193490
+ pattern += "D";
193491
+ else if (part.value === "03")
193492
+ pattern += "DD";
193493
+ else
193494
+ return null;
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
+ }
193509
+ case "literal":
193510
+ if (/[YMD]/.test(part.value) || /ddd/.test(part.value))
193511
+ return null;
193512
+ pattern += part.value;
193513
+ break;
193514
+ default:
193515
+ return null;
193516
+ }
193517
+ }
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))
193527
+ return null;
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;
193585
+ }
193586
+ function matchToLocaleDateStringCall(callee, args, metadata) {
193587
+ if (callee.kind !== "member" || callee.computed)
193588
+ return null;
193589
+ if (callee.property !== "toLocaleDateString" || args.length !== 2)
193590
+ return null;
193591
+ const [locale, options] = args;
193592
+ if (options.kind !== "object-literal")
193593
+ return null;
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)
193609
+ return null;
193610
+ const receiverType = resolveReceiverType(callee.object, metadata, new Map);
193611
+ if (!receiverType || receiverType.kind !== "interface")
193612
+ return null;
193613
+ const typeName = baseTypeName(receiverType.raw);
193614
+ if (typeName !== "Date")
193615
+ return null;
193616
+ if (metadata.typeDefinitions.some((d) => d.name === typeName))
193617
+ return 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)
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 ?? []));
193640
+ return {
193641
+ kind: "helper-call",
193642
+ helper: "format_date",
193643
+ args: [
193644
+ callee.object,
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)
193648
+ ]
193649
+ };
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
+ }
193676
+ var toLocaleDatePlugin = {
193677
+ name: "toLocaleDateString",
193678
+ prepare(metadata) {
193679
+ if (!metadata.propsType || !typeReachesDate(metadata.propsType, metadata, new Set))
193680
+ return null;
193681
+ return (callee, args) => matchToLocaleDateStringCall(callee, args, metadata);
193682
+ }
193683
+ };
193684
+
192795
193685
  // ../jsx/src/jsx-to-ir.ts
192796
193686
  var CLIENT_DIRECTIVE_INTERIOR_RE2 = /^\s*@client\s*$/;
192797
193687
  var BLOCK_COMMENT_RE2 = /\/\*([\s\S]*?)\*\//g;
@@ -192886,6 +193776,19 @@ function getDateLoweringMatcher(ctx) {
192886
193776
  }
192887
193777
  return ctx._dateLoweringMatcher;
192888
193778
  }
193779
+ function getToLocaleDateLoweringMatcher(ctx) {
193780
+ if (ctx._toLocaleDateLoweringMatcher === undefined) {
193781
+ const a = ctx.analyzer;
193782
+ const metadataSlice = {
193783
+ propsType: a.propsType,
193784
+ propsObjectName: a.propsObjectName,
193785
+ propsParams: a.propsParams,
193786
+ typeDefinitions: a.typeDefinitions
193787
+ };
193788
+ ctx._toLocaleDateLoweringMatcher = toLocaleDatePlugin.prepare(metadataSlice);
193789
+ }
193790
+ return ctx._toLocaleDateLoweringMatcher;
193791
+ }
192889
193792
  function lowerDateCalls(text, expr, ctx) {
192890
193793
  const matcher = getDateLoweringMatcher(ctx);
192891
193794
  if (!matcher)
@@ -192914,8 +193817,48 @@ function lowerDateCalls(text, expr, ctx) {
192914
193817
  }
192915
193818
  return restore(result);
192916
193819
  }
193820
+ function lowerToLocaleDateCalls(text, expr, ctx) {
193821
+ const matcher = getToLocaleDateLoweringMatcher(ctx);
193822
+ if (!matcher)
193823
+ return text;
193824
+ const candidates = [];
193825
+ function visit2(n) {
193826
+ if (import_typescript11.default.isCallExpression(n) && n.arguments.length === 2 && import_typescript11.default.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
193827
+ candidates.push(n);
193828
+ }
193829
+ import_typescript11.default.forEachChild(n, visit2);
193830
+ }
193831
+ visit2(expr);
193832
+ if (candidates.length === 0)
193833
+ return text;
193834
+ const { protect, restore, replaceProtectedCall } = createTemplateAwareStringProtector();
193835
+ let result = protect(text);
193836
+ for (const call of candidates) {
193837
+ const propAccess = call.expression;
193838
+ const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
193839
+ if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
193840
+ continue;
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)
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
+ }
193854
+ const receiverText = ctx.getJS(propAccess.expression);
193855
+ const matchText = ctx.getJS(call);
193856
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
193857
+ }
193858
+ return restore(result);
193859
+ }
192917
193860
  function rewriteBarePropRefs2(text, expr, ctx) {
192918
- const dateLowered = lowerDateCalls(text, expr, ctx);
193861
+ const dateLowered = lowerToLocaleDateCalls(lowerDateCalls(text, expr, ctx), expr, ctx);
192919
193862
  let propNames = getDestructuredPropNames(ctx);
192920
193863
  if (!propNames)
192921
193864
  return dateLowered === text ? undefined : dateLowered;
@@ -196332,7 +197275,7 @@ var ENV_SIGNAL_READERS = new Map([
196332
197275
  function queryHrefLocalNames(metadata) {
196333
197276
  const names = new Set;
196334
197277
  for (const imp of metadata.imports) {
196335
- if (!QUERY_HREF_SOURCES.has(imp.source) || imp.isTypeOnly)
197278
+ if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
196336
197279
  continue;
196337
197280
  for (const s of imp.specifiers) {
196338
197281
  if (s.isTypeOnly || s.isNamespace || s.isDefault)
@@ -196343,10 +197286,24 @@ function queryHrefLocalNames(metadata) {
196343
197286
  }
196344
197287
  return names;
196345
197288
  }
196346
- var QUERY_HREF_SOURCES = new Set([
197289
+ var CLIENT_HELPER_SOURCES = new Set([
196347
197290
  "@barefootjs/client",
196348
197291
  "@barefootjs/client/runtime"
196349
197292
  ]);
197293
+ function formatDateLocalNames(metadata) {
197294
+ const names = new Set;
197295
+ for (const imp of metadata.imports) {
197296
+ if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
197297
+ continue;
197298
+ for (const s of imp.specifiers) {
197299
+ if (s.isTypeOnly || s.isNamespace || s.isDefault)
197300
+ continue;
197301
+ if (s.name === "formatDate")
197302
+ names.add(s.alias ?? s.name);
197303
+ }
197304
+ }
197305
+ return names;
197306
+ }
196350
197307
 
196351
197308
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
196352
197309
  var import_typescript14 = __toESM(require_typescript(), 1);
@@ -196955,6 +197912,30 @@ function isOmitBranch(node) {
196955
197912
  }
196956
197913
  return false;
196957
197914
  }
197915
+ // ../jsx/src/format-date-lowering.ts
197916
+ var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
197917
+ var EMPTY_NAMES = { kind: "array-literal", elements: [], raw: "[]" };
197918
+ function matchFormatDateCall(callee, args, locals) {
197919
+ if (callee.kind !== "identifier" || !locals.has(callee.name))
197920
+ return null;
197921
+ if (args.length < 2 || args.length > 4)
197922
+ return null;
197923
+ return {
197924
+ kind: "helper-call",
197925
+ helper: "format_date",
197926
+ args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES]
197927
+ };
197928
+ }
197929
+ var formatDatePlugin = {
197930
+ name: "formatDate",
197931
+ prepare(metadata) {
197932
+ const locals = formatDateLocalNames(metadata);
197933
+ if (locals.size === 0)
197934
+ return null;
197935
+ return (callee, args) => matchFormatDateCall(callee, args, locals);
197936
+ }
197937
+ };
197938
+
196958
197939
  // ../jsx/src/builtin-lowering-plugins.ts
196959
197940
  var queryHrefPlugin = {
196960
197941
  name: "queryHref",
@@ -196968,7 +197949,12 @@ var queryHrefPlugin = {
196968
197949
  };
196969
197950
  }
196970
197951
  };
196971
- var BUILTIN_LOWERING_PLUGINS = [queryHrefPlugin, datePlugin];
197952
+ var BUILTIN_LOWERING_PLUGINS = [
197953
+ queryHrefPlugin,
197954
+ datePlugin,
197955
+ formatDatePlugin,
197956
+ toLocaleDatePlugin
197957
+ ];
196972
197958
  function registerBuiltinLoweringPlugins() {
196973
197959
  for (const plugin of BUILTIN_LOWERING_PLUGINS)
196974
197960
  registerLoweringPlugin(plugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/test",
3
- "version": "0.21.4",
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.21.4"
42
+ "@barefootjs/jsx": "0.24.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.0.0"