@barefootjs/jsx 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.
package/dist/index.js CHANGED
@@ -5243,7 +5243,8 @@ var ErrorCodes = {
5243
5243
  INLINE_JSX_CALLBACK_CAPTURE: "BF080",
5244
5244
  UNRECOGNIZED_REACTIVE_FACTORY: "BF110",
5245
5245
  REACTIVE_FACTORY_RENAME_UNSUPPORTED: "BF111",
5246
- REACTIVE_FACTORY_MODULE_CAPTURE: "BF112"
5246
+ REACTIVE_FACTORY_MODULE_CAPTURE: "BF112",
5247
+ REACTIVE_FACTORY_IMPORT_COLLISION: "BF113"
5247
5248
  };
5248
5249
  var errorMessages = {
5249
5250
  [ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
@@ -5269,7 +5270,8 @@ var errorMessages = {
5269
5270
  [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.",
5270
5271
  [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.",
5271
5272
  [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.",
5272
- [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."
5273
+ [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.",
5274
+ [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."
5273
5275
  };
5274
5276
  function createError(code, loc, options) {
5275
5277
  if (code === undefined || !(code in errorMessages)) {
@@ -7780,6 +7782,74 @@ function prescanReactiveFactoriesInSource(source, filePath) {
7780
7782
  prescanImportedReactiveFactories(sourceFile, filePath, result);
7781
7783
  return result;
7782
7784
  }
7785
+ function toComponentRelativeSpecifier(resolvedAbs, componentFilePath) {
7786
+ let rel = path_default.relative(path_default.dirname(componentFilePath), resolvedAbs).split(path_default.sep).join("/");
7787
+ rel = rel.replace(/\.(tsx|ts|jsx|js)$/, "");
7788
+ if (rel === "")
7789
+ rel = ".";
7790
+ if (!rel.startsWith("."))
7791
+ rel = "./" + rel;
7792
+ return rel;
7793
+ }
7794
+ function buildEntryImportIndex(sf, filePath) {
7795
+ const index = new Map;
7796
+ for (const stmt of sf.statements) {
7797
+ if (!ts8.isImportDeclaration(stmt))
7798
+ continue;
7799
+ if (!ts8.isStringLiteral(stmt.moduleSpecifier))
7800
+ continue;
7801
+ if (stmt.importClause?.isTypeOnly)
7802
+ continue;
7803
+ const src = stmt.moduleSpecifier.text;
7804
+ const targetKey = src.startsWith("./") || src.startsWith("../") ? resolveRelativeImportToFile(src, filePath) ?? "unresolved:" + src : src;
7805
+ const namedBindings = stmt.importClause?.namedBindings;
7806
+ if (namedBindings && ts8.isNamedImports(namedBindings)) {
7807
+ for (const el of namedBindings.elements) {
7808
+ if (el.isTypeOnly)
7809
+ continue;
7810
+ index.set(el.name.text, { targetKey, exportedName: (el.propertyName ?? el.name).text });
7811
+ }
7812
+ }
7813
+ }
7814
+ return index;
7815
+ }
7816
+ function collectEntryBindingNames(sf) {
7817
+ const names = new Set;
7818
+ function visit2(node) {
7819
+ if (ts8.isImportDeclaration(node) && node.importClause) {
7820
+ if (node.importClause.name)
7821
+ names.add(node.importClause.name.text);
7822
+ const namedBindings = node.importClause.namedBindings;
7823
+ if (namedBindings && ts8.isNamedImports(namedBindings)) {
7824
+ for (const el of namedBindings.elements)
7825
+ names.add(el.name.text);
7826
+ }
7827
+ if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
7828
+ names.add(namedBindings.name.text);
7829
+ }
7830
+ }
7831
+ if (ts8.isVariableDeclaration(node)) {
7832
+ const out = [];
7833
+ addBindingNames(node.name, out);
7834
+ for (const n of out)
7835
+ names.add(n);
7836
+ }
7837
+ if ((ts8.isFunctionDeclaration(node) || ts8.isClassDeclaration(node) || ts8.isEnumDeclaration(node)) && node.name) {
7838
+ names.add(node.name.text);
7839
+ }
7840
+ if (ts8.isFunctionLike(node)) {
7841
+ for (const p of node.parameters) {
7842
+ const out = [];
7843
+ addBindingNames(p.name, out);
7844
+ for (const n of out)
7845
+ names.add(n);
7846
+ }
7847
+ }
7848
+ ts8.forEachChild(node, visit2);
7849
+ }
7850
+ visit2(sf);
7851
+ return names;
7852
+ }
7783
7853
  function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7784
7854
  const candidateCallees = new Set;
7785
7855
  function collectCandidates(node) {
@@ -7820,6 +7890,9 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7820
7890
  }
7821
7891
  if (importsToCheck.length === 0)
7822
7892
  return;
7893
+ const entryBindingNames = collectEntryBindingNames(entrySourceFile);
7894
+ const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
7895
+ const plannedInjections = new Map;
7823
7896
  for (const { src, specs } of importsToCheck) {
7824
7897
  const resolved = resolveRelativeImportToFile(src, filePath);
7825
7898
  if (!resolved)
@@ -7885,17 +7958,64 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7885
7958
  result.declined.set(spec.local, det.declined);
7886
7959
  break;
7887
7960
  case "factory": {
7888
- const offending = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
7889
- if (offending.length > 0) {
7961
+ const capture = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
7962
+ if (capture.captured.length > 0) {
7890
7963
  result.declined.set(spec.local, {
7891
7964
  code: "BF112",
7892
- detail: `'${offending.join("', '")}'`,
7965
+ detail: `'${capture.captured.join("', '")}'`,
7893
7966
  loc: det.info.loc
7894
7967
  });
7895
- } else {
7896
- det.info.sourceFilePath = resolved;
7897
- result.factories.set(spec.local, det.info);
7968
+ break;
7969
+ }
7970
+ const required = [];
7971
+ const pending = [];
7972
+ let declinedEntry = null;
7973
+ for (const ref of capture.importedRefs) {
7974
+ let specifier;
7975
+ let targetKey;
7976
+ if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
7977
+ const abs = resolveRelativeImportToFile(ref.source, resolved);
7978
+ if (!abs) {
7979
+ declinedEntry = {
7980
+ code: "BF112",
7981
+ detail: `'${ref.localName}' (import '${ref.source}' did not resolve from the helper file)`,
7982
+ loc: det.info.loc
7983
+ };
7984
+ break;
7985
+ }
7986
+ specifier = toComponentRelativeSpecifier(abs, filePath);
7987
+ targetKey = abs;
7988
+ } else {
7989
+ specifier = ref.source;
7990
+ targetKey = ref.source;
7991
+ }
7992
+ const existing = entryImportIndex.get(ref.localName);
7993
+ if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName) {
7994
+ continue;
7995
+ }
7996
+ const planned = plannedInjections.get(ref.localName);
7997
+ const collides = existing !== undefined || planned !== undefined && (planned.targetKey !== targetKey || planned.exportedName !== ref.exportedName) || planned === undefined && entryBindingNames.has(ref.localName);
7998
+ if (collides) {
7999
+ declinedEntry = {
8000
+ code: "BF113",
8001
+ detail: `'${ref.localName}' from '${specifier}'`,
8002
+ loc: det.info.loc
8003
+ };
8004
+ break;
8005
+ }
8006
+ pending.push([ref.localName, { targetKey, exportedName: ref.exportedName }]);
8007
+ required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier });
7898
8008
  }
8009
+ if (declinedEntry) {
8010
+ result.declined.set(spec.local, declinedEntry);
8011
+ break;
8012
+ }
8013
+ for (const [name, id] of pending)
8014
+ plannedInjections.set(name, id);
8015
+ det.info.sourceFilePath = resolved;
8016
+ if (required.length > 0)
8017
+ det.info.requiredImports = required;
8018
+ result.factories.set(spec.local, det.info);
7899
8019
  break;
7900
8020
  }
7901
8021
  }
@@ -7903,7 +8023,8 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7903
8023
  }
7904
8024
  }
7905
8025
  function collectHelperModuleValueBindings(sf) {
7906
- const names = new Set;
8026
+ const local = new Set;
8027
+ const imported = new Map;
7907
8028
  for (const stmt of sf.statements) {
7908
8029
  if (ts8.isVariableStatement(stmt)) {
7909
8030
  const out = [];
@@ -7911,11 +8032,11 @@ function collectHelperModuleValueBindings(sf) {
7911
8032
  addBindingNames(decl.name, out);
7912
8033
  }
7913
8034
  for (const n of out)
7914
- names.add(n);
8035
+ local.add(n);
7915
8036
  continue;
7916
8037
  }
7917
8038
  if ((ts8.isFunctionDeclaration(stmt) || ts8.isClassDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name) {
7918
- names.add(stmt.name.text);
8039
+ local.add(stmt.name.text);
7919
8040
  continue;
7920
8041
  }
7921
8042
  if (ts8.isImportDeclaration(stmt)) {
@@ -7927,25 +8048,25 @@ function collectHelperModuleValueBindings(sf) {
7927
8048
  if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
7928
8049
  continue;
7929
8050
  if (stmt.importClause?.name)
7930
- names.add(stmt.importClause.name.text);
8051
+ local.add(stmt.importClause.name.text);
7931
8052
  const namedBindings = stmt.importClause?.namedBindings;
7932
8053
  if (namedBindings && ts8.isNamedImports(namedBindings)) {
7933
8054
  for (const el of namedBindings.elements) {
7934
8055
  if (el.isTypeOnly)
7935
8056
  continue;
7936
- names.add(el.name.text);
8057
+ imported.set(el.name.text, { source: src, exportedName: (el.propertyName ?? el.name).text });
7937
8058
  }
7938
8059
  }
7939
8060
  if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
7940
- names.add(namedBindings.name.text);
8061
+ local.add(namedBindings.name.text);
7941
8062
  }
7942
8063
  }
7943
8064
  }
7944
- return names;
8065
+ return { local, imported };
7945
8066
  }
7946
8067
  function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
7947
8068
  if (!fn.body)
7948
- return [];
8069
+ return { captured: [], importedRefs: [] };
7949
8070
  const free = extractFreeIdentifiersFromNode(fn.body);
7950
8071
  const exclude = new Set(info.params);
7951
8072
  for (const b of info.localBindings)
@@ -7955,14 +8076,22 @@ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
7955
8076
  for (const p of REACTIVE_PRIMITIVES)
7956
8077
  exclude.add(p);
7957
8078
  exclude.add(selfName);
7958
- const offending = [];
8079
+ const captured = [];
8080
+ const importedRefs = [];
7959
8081
  for (const id of free) {
7960
8082
  if (exclude.has(id))
7961
8083
  continue;
7962
- if (moduleBindings.has(id))
7963
- offending.push(id);
8084
+ if (moduleBindings.local.has(id)) {
8085
+ captured.push(id);
8086
+ continue;
8087
+ }
8088
+ const imp = moduleBindings.imported.get(id);
8089
+ if (imp)
8090
+ importedRefs.push({ localName: id, source: imp.source, exportedName: imp.exportedName });
7964
8091
  }
7965
- return offending.sort();
8092
+ captured.sort();
8093
+ importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
8094
+ return { captured, importedRefs };
7966
8095
  }
7967
8096
  function detectReactiveFactory(node, sourceFile, filePath) {
7968
8097
  if (!node.body || !node.name)
@@ -8086,6 +8215,7 @@ function rewriteFactoryCallsInSource(source, prescan) {
8086
8215
  const { factories, sourceFile } = prescan;
8087
8216
  const edits = [];
8088
8217
  let callSiteIndex = 0;
8218
+ const inlinedFactories = new Set;
8089
8219
  function visitStmt(node, inComponent) {
8090
8220
  if (ts8.isVariableStatement(node) && inComponent) {
8091
8221
  for (const decl of node.declarationList.declarations) {
@@ -8187,10 +8317,34 @@ function rewriteFactoryCallsInSource(source, prescan) {
8187
8317
  end: stmt.getEnd(),
8188
8318
  replacement: body
8189
8319
  });
8320
+ inlinedFactories.add(factory);
8190
8321
  }
8191
8322
  visitStmt(sourceFile, false);
8192
8323
  if (edits.length === 0)
8193
8324
  return source;
8325
+ const importsBySpecifier = new Map;
8326
+ for (const f of inlinedFactories) {
8327
+ for (const r of f.requiredImports ?? []) {
8328
+ let names = importsBySpecifier.get(r.specifier);
8329
+ if (!names) {
8330
+ names = new Map;
8331
+ importsBySpecifier.set(r.specifier, names);
8332
+ }
8333
+ names.set(r.localName, r.exportedName);
8334
+ }
8335
+ }
8336
+ if (importsBySpecifier.size > 0) {
8337
+ const lines = [...importsBySpecifier].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([spec, names]) => {
8338
+ const specifiers = [...names].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([local, exported]) => exported === local ? local : `${exported} as ${local}`);
8339
+ return `import { ${specifiers.join(", ")} } from '${spec}'`;
8340
+ });
8341
+ const at = factoryImportInsertionOffset(sourceFile);
8342
+ edits.push({ start: at, end: at, replacement: at === 0 ? lines.join(`
8343
+ `) + `
8344
+ ` : `
8345
+ ` + lines.join(`
8346
+ `) });
8347
+ }
8194
8348
  edits.sort((a, b) => b.start - a.start);
8195
8349
  let out = source;
8196
8350
  for (const e of edits) {
@@ -8198,6 +8352,20 @@ function rewriteFactoryCallsInSource(source, prescan) {
8198
8352
  }
8199
8353
  return out;
8200
8354
  }
8355
+ function factoryImportInsertionOffset(sf) {
8356
+ let lastImportEnd = -1;
8357
+ let directiveEnd = -1;
8358
+ for (const stmt of sf.statements) {
8359
+ if (ts8.isImportDeclaration(stmt)) {
8360
+ lastImportEnd = stmt.getEnd();
8361
+ continue;
8362
+ }
8363
+ if (directiveEnd === -1 && ts8.isExpressionStatement(stmt) && ts8.isStringLiteral(stmt.expression) && stmt.expression.text === "use client") {
8364
+ directiveEnd = stmt.getEnd();
8365
+ }
8366
+ }
8367
+ return lastImportEnd >= 0 ? lastImportEnd : directiveEnd >= 0 ? directiveEnd : 0;
8368
+ }
8201
8369
  function isPascalCaseComponentFn(node) {
8202
8370
  if (ts8.isFunctionDeclaration(node) && node.name) {
8203
8371
  return /^[A-Z]/.test(node.name.text);
@@ -8214,8 +8382,21 @@ function declinedFactoryMessage(callee, d) {
8214
8382
  if (d.code === "BF112") {
8215
8383
  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.`;
8216
8384
  }
8385
+ if (d.code === "BF113") {
8386
+ 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 }).`;
8387
+ }
8217
8388
  return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
8218
8389
  }
8390
+ function declinedFactoryErrorCode(code) {
8391
+ switch (code) {
8392
+ case "BF112":
8393
+ return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
8394
+ case "BF113":
8395
+ return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
8396
+ default:
8397
+ return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
8398
+ }
8399
+ }
8219
8400
  function validateReactiveFactoryCalls(ctx) {
8220
8401
  if (!ctx.componentNode)
8221
8402
  return;
@@ -8239,7 +8420,7 @@ function validateReactiveFactoryCalls(ctx) {
8239
8420
  continue;
8240
8421
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
8241
8422
  if (declinedEntry) {
8242
- ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8423
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8243
8424
  continue;
8244
8425
  }
8245
8426
  const objectFactory = ctx.reactiveFactories.get(callee);
@@ -8296,7 +8477,7 @@ function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
8296
8477
  }
8297
8478
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
8298
8479
  if (declinedEntry) {
8299
- ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8480
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8300
8481
  return;
8301
8482
  }
8302
8483
  if (ctx.reactiveShapedHelpers.has(callee)) {
@@ -8848,19 +9029,83 @@ function resolveFreeRefs(node, env) {
8848
9029
  // src/to-locale-date-lowering.ts
8849
9030
  var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
8850
9031
  var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
8851
- var patternCache = new Map;
8852
- function resolveLocaleDatePattern(locale) {
8853
- const cached = patternCache.get(locale);
9032
+ var formatCache = new Map;
9033
+ var namesCache = new Map;
9034
+ function deriveMonthNames(locale, ctx) {
9035
+ return deriveNamesCached(`${locale}|m|${ctx}`, () => {
9036
+ const months = (width) => Array.from({ length: 12 }, (_, m) => probePart(locale, ctx === "formatting" ? { month: width, day: "numeric" } : { month: width }, Date.UTC(2001, m, 15), "month"));
9037
+ return [...months("long"), ...months("short")];
9038
+ });
9039
+ }
9040
+ function deriveWeekdayNames(locale, ctx) {
9041
+ return deriveNamesCached(`${locale}|w|${ctx}`, () => {
9042
+ 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"));
9043
+ return [...weekdays("long"), ...weekdays("short")];
9044
+ });
9045
+ }
9046
+ function probePart(locale, options, utc, type) {
9047
+ const parts = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).formatToParts(new Date(utc));
9048
+ const found = parts.find((p) => p.type === type);
9049
+ if (!found || !found.value)
9050
+ throw new Error("missing part");
9051
+ return found.value;
9052
+ }
9053
+ function deriveNamesCached(key, derive) {
9054
+ const cached = namesCache.get(key);
9055
+ if (cached !== undefined)
9056
+ return cached;
9057
+ let derived;
9058
+ try {
9059
+ derived = derive();
9060
+ } catch {
9061
+ derived = null;
9062
+ }
9063
+ namesCache.set(key, derived);
9064
+ return derived;
9065
+ }
9066
+ function resolveLocaleDateFormat(locale, probeOptions) {
9067
+ const key = `${locale}|${JSON.stringify(probeOptions, Object.keys(probeOptions).sort())}`;
9068
+ const cached = formatCache.get(key);
8854
9069
  if (cached !== undefined)
8855
9070
  return cached;
8856
- const derived = derivePattern(locale);
8857
- patternCache.set(locale, derived);
9071
+ const derived = deriveFormat(locale, probeOptions);
9072
+ formatCache.set(key, derived);
8858
9073
  return derived;
8859
9074
  }
8860
- function derivePattern(locale) {
9075
+ var VERIFY_UTC = new Date(Date.UTC(2001, 4, 13));
9076
+ function renderPatternAt(pattern, names, y, m, d, wd) {
9077
+ const pad2 = (n) => String(n).padStart(2, "0");
9078
+ return pattern.replace(/YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D/g, (token) => {
9079
+ switch (token) {
9080
+ case "YYYY":
9081
+ return String(y).padStart(4, "0");
9082
+ case "MMMM":
9083
+ return names[m - 1] ?? "";
9084
+ case "MMM":
9085
+ return names[12 + m - 1] ?? "";
9086
+ case "MM":
9087
+ return pad2(m);
9088
+ case "M":
9089
+ return String(m);
9090
+ case "DD":
9091
+ return pad2(d);
9092
+ case "D":
9093
+ return String(d);
9094
+ case "dddd":
9095
+ return names[24 + wd] ?? "";
9096
+ default:
9097
+ return names[31 + wd] ?? "";
9098
+ }
9099
+ });
9100
+ }
9101
+ function deriveFormat(locale, probeOptions) {
9102
+ let dtf;
8861
9103
  let parts;
8862
9104
  try {
8863
- const dtf = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
9105
+ dtf = new Intl.DateTimeFormat(locale, {
9106
+ ...probeOptions,
9107
+ timeZone: "UTC"
9108
+ });
8864
9109
  const resolved = dtf.resolvedOptions();
8865
9110
  if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
8866
9111
  return null;
@@ -8868,7 +9113,18 @@ function derivePattern(locale) {
8868
9113
  } catch {
8869
9114
  return null;
8870
9115
  }
9116
+ const monthTables = [
9117
+ deriveMonthNames(locale, "formatting"),
9118
+ deriveMonthNames(locale, "standalone")
9119
+ ];
9120
+ const weekdayTables = [
9121
+ deriveWeekdayNames(locale, "formatting"),
9122
+ deriveWeekdayNames(locale, "standalone")
9123
+ ];
9124
+ let monthTable = null;
9125
+ let weekdayTable = null;
8871
9126
  let pattern = "";
9127
+ let usesNames = false;
8872
9128
  for (const part of parts) {
8873
9129
  switch (part.type) {
8874
9130
  case "year":
@@ -8876,14 +9132,27 @@ function derivePattern(locale) {
8876
9132
  return null;
8877
9133
  pattern += "YYYY";
8878
9134
  break;
8879
- case "month":
8880
- if (part.value === "2")
9135
+ case "month": {
9136
+ if (part.value === "2") {
8881
9137
  pattern += "M";
8882
- else if (part.value === "02")
9138
+ break;
9139
+ }
9140
+ if (part.value === "02") {
8883
9141
  pattern += "MM";
9142
+ break;
9143
+ }
9144
+ const wide = monthTables.find((t) => t && part.value === t[1]) ?? null;
9145
+ const abbr = wide ? null : monthTables.find((t) => t && part.value === t[12 + 1]) ?? null;
9146
+ if (wide)
9147
+ pattern += "MMMM";
9148
+ else if (abbr)
9149
+ pattern += "MMM";
8884
9150
  else
8885
9151
  return null;
9152
+ monthTable = wide ?? abbr;
9153
+ usesNames = true;
8886
9154
  break;
9155
+ }
8887
9156
  case "day":
8888
9157
  if (part.value === "3")
8889
9158
  pattern += "D";
@@ -8892,8 +9161,21 @@ function derivePattern(locale) {
8892
9161
  else
8893
9162
  return null;
8894
9163
  break;
9164
+ case "weekday": {
9165
+ const wide = weekdayTables.find((t) => t && part.value === t[6]) ?? null;
9166
+ const abbr = wide ? null : weekdayTables.find((t) => t && part.value === t[7 + 6]) ?? null;
9167
+ if (wide)
9168
+ pattern += "dddd";
9169
+ else if (abbr)
9170
+ pattern += "ddd";
9171
+ else
9172
+ return null;
9173
+ weekdayTable = wide ?? abbr;
9174
+ usesNames = true;
9175
+ break;
9176
+ }
8895
9177
  case "literal":
8896
- if (/[YMD]/.test(part.value))
9178
+ if (/[YMD]/.test(part.value) || /ddd/.test(part.value))
8897
9179
  return null;
8898
9180
  pattern += part.value;
8899
9181
  break;
@@ -8901,9 +9183,73 @@ function derivePattern(locale) {
8901
9183
  return null;
8902
9184
  }
8903
9185
  }
8904
- if (!pattern.includes("YYYY") || !/M/.test(pattern) || !/D/.test(pattern))
9186
+ if (!/YYYY|MMMM|MMM|MM|M/.test(pattern) && !/DD|D/.test(pattern))
8905
9187
  return null;
8906
- return pattern;
9188
+ if (!usesNames)
9189
+ return { pattern, names: null };
9190
+ const names = [
9191
+ ...monthTable ?? monthTables[0] ?? monthTables[1] ?? Array(24).fill(""),
9192
+ ...weekdayTable ?? weekdayTables[0] ?? weekdayTables[1] ?? Array(14).fill("")
9193
+ ];
9194
+ if (renderPatternAt(pattern, names, 2001, 5, 13, 0) !== dtf.format(VERIFY_UTC))
9195
+ return null;
9196
+ return { pattern, names };
9197
+ }
9198
+ function unionMemberLiteral(member) {
9199
+ const m = /^'([^'\\]*)'$|^"([^"\\]*)"$/.exec(member.raw.trim());
9200
+ return m ? m[1] ?? m[2] : null;
9201
+ }
9202
+ function resolveLocaleUnionMembers(locale, metadata) {
9203
+ let sourcePropName = null;
9204
+ if (metadata.propsObjectName) {
9205
+ if (locale.kind === "member" && !locale.computed && locale.object.kind === "identifier" && locale.object.name === metadata.propsObjectName) {
9206
+ sourcePropName = locale.property;
9207
+ }
9208
+ } else if (locale.kind === "identifier") {
9209
+ const name = locale.name;
9210
+ const param = metadata.propsParams?.find((pp) => pp.name === name);
9211
+ if (param)
9212
+ sourcePropName = param.sourceName ?? param.name;
9213
+ }
9214
+ if (!sourcePropName)
9215
+ return null;
9216
+ const target = sourcePropName;
9217
+ const prop = metadata.propsType?.properties?.find((p) => p.name === target);
9218
+ if (!prop || prop.optional)
9219
+ return null;
9220
+ const type = prop.type;
9221
+ if (type.kind !== "union" || !type.unionTypes || type.unionTypes.length === 0)
9222
+ return null;
9223
+ const members = [];
9224
+ for (const member of type.unionTypes) {
9225
+ const value = unionMemberLiteral(member);
9226
+ if (value === null)
9227
+ return null;
9228
+ members.push(value);
9229
+ }
9230
+ return members;
9231
+ }
9232
+ var strLit = (value) => ({ kind: "literal", value, literalType: "string" });
9233
+ function strArr(values) {
9234
+ return {
9235
+ kind: "array-literal",
9236
+ elements: values.map((v) => strLit(v)),
9237
+ raw: JSON.stringify(values)
9238
+ };
9239
+ }
9240
+ function foldMembers(locale, members, leaves, allEqual) {
9241
+ let expr = leaves[leaves.length - 1];
9242
+ if (allEqual)
9243
+ return expr;
9244
+ for (let i = leaves.length - 2;i >= 0; i--) {
9245
+ expr = {
9246
+ kind: "conditional",
9247
+ test: { kind: "binary", op: "===", left: locale, right: strLit(members[i]) },
9248
+ consequent: leaves[i],
9249
+ alternate: expr
9250
+ };
9251
+ }
9252
+ return expr;
8907
9253
  }
8908
9254
  function matchToLocaleDateStringCall(callee, args, metadata) {
8909
9255
  if (callee.kind !== "member" || callee.computed)
@@ -8911,17 +9257,23 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
8911
9257
  if (callee.property !== "toLocaleDateString" || args.length !== 2)
8912
9258
  return null;
8913
9259
  const [locale, options] = args;
8914
- if (locale.kind !== "literal" || locale.literalType !== "string")
8915
- return null;
8916
- if (options.kind !== "object-literal" || options.properties.length !== 1)
9260
+ if (options.kind !== "object-literal")
8917
9261
  return null;
8918
- const prop = options.properties[0];
8919
- if (prop.key !== "timeZone")
8920
- return null;
8921
- if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
8922
- return null;
8923
- const tz = String(prop.value.value);
8924
- if (!TO_LOCALE_TZ_RE.test(tz))
9262
+ let tz = null;
9263
+ const probeOptions = {};
9264
+ for (const prop of options.properties) {
9265
+ if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
9266
+ return null;
9267
+ const value = String(prop.value.value);
9268
+ if (prop.key === "timeZone") {
9269
+ if (!TO_LOCALE_TZ_RE.test(value))
9270
+ return null;
9271
+ tz = value;
9272
+ } else {
9273
+ probeOptions[prop.key] = value;
9274
+ }
9275
+ }
9276
+ if (tz === null)
8925
9277
  return null;
8926
9278
  const receiverType = resolveReceiverType(callee.object, metadata, new Map);
8927
9279
  if (!receiverType || receiverType.kind !== "interface")
@@ -8931,19 +9283,64 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
8931
9283
  return null;
8932
9284
  if (metadata.typeDefinitions.some((d) => d.name === typeName))
8933
9285
  return null;
8934
- const pattern = resolveLocaleDatePattern(String(locale.value));
8935
- if (pattern === null)
9286
+ if (locale.kind === "literal" && locale.literalType === "string") {
9287
+ const format2 = resolveLocaleDateFormat(String(locale.value), probeOptions);
9288
+ if (format2 === null)
9289
+ return null;
9290
+ return {
9291
+ kind: "helper-call",
9292
+ helper: "format_date",
9293
+ args: [callee.object, strLit(format2.pattern), strLit(tz), strArr(format2.names ?? [])]
9294
+ };
9295
+ }
9296
+ const members = resolveLocaleUnionMembers(locale, metadata);
9297
+ if (!members)
8936
9298
  return null;
9299
+ const formats = [];
9300
+ for (const member of members) {
9301
+ const format2 = resolveLocaleDateFormat(member, probeOptions);
9302
+ if (format2 === null)
9303
+ return null;
9304
+ formats.push(format2);
9305
+ }
9306
+ const patterns = formats.map((f) => f.pattern);
9307
+ const nameTables = formats.map((f) => JSON.stringify(f.names ?? []));
8937
9308
  return {
8938
9309
  kind: "helper-call",
8939
9310
  helper: "format_date",
8940
9311
  args: [
8941
9312
  callee.object,
8942
- { kind: "literal", value: pattern, literalType: "string" },
8943
- { kind: "literal", value: tz, literalType: "string" }
9313
+ foldMembers(locale, members, patterns.map(strLit), new Set(patterns).size === 1),
9314
+ strLit(tz),
9315
+ foldMembers(locale, members, formats.map((f) => strArr(f.names ?? [])), new Set(nameTables).size === 1)
8944
9316
  ]
8945
9317
  };
8946
9318
  }
9319
+ function foldedArgToClientJs(arg, localeText) {
9320
+ if (arg.kind === "literal")
9321
+ return JSON.stringify(arg.value);
9322
+ if (arg.kind === "array-literal") {
9323
+ const values = [];
9324
+ for (const el of arg.elements) {
9325
+ if (el.kind !== "literal")
9326
+ return null;
9327
+ values.push(String(el.value));
9328
+ }
9329
+ return JSON.stringify(values);
9330
+ }
9331
+ if (arg.kind !== "conditional")
9332
+ return null;
9333
+ const t = arg.test;
9334
+ if (t.kind !== "binary" || t.op !== "===" || t.right.kind !== "literal")
9335
+ return null;
9336
+ if (arg.consequent.kind !== "literal" && arg.consequent.kind !== "array-literal")
9337
+ return null;
9338
+ const cons = foldedArgToClientJs(arg.consequent, localeText);
9339
+ const rest = foldedArgToClientJs(arg.alternate, localeText);
9340
+ if (cons === null || rest === null)
9341
+ return null;
9342
+ return `${localeText} === ${JSON.stringify(t.right.value)} ? ${cons} : ${rest}`;
9343
+ }
8947
9344
  var toLocaleDatePlugin = {
8948
9345
  name: "toLocaleDateString",
8949
9346
  prepare(metadata) {
@@ -9110,12 +9507,22 @@ function lowerToLocaleDateCalls(text, expr, ctx) {
9110
9507
  const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
9111
9508
  if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
9112
9509
  continue;
9113
- const [, patternArg, tzArg] = node.args;
9114
- if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
9510
+ const [, patternArg, tzArg, namesArg] = node.args;
9511
+ if (!patternArg || tzArg?.kind !== "literal")
9115
9512
  continue;
9513
+ const localeText = ctx.getJS(call.arguments[0]);
9514
+ const patternJs = foldedArgToClientJs(patternArg, localeText);
9515
+ if (patternJs === null)
9516
+ continue;
9517
+ let namesJs = null;
9518
+ if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
9519
+ namesJs = foldedArgToClientJs(namesArg, localeText);
9520
+ if (namesJs === null)
9521
+ continue;
9522
+ }
9116
9523
  const receiverText = ctx.getJS(propAccess.expression);
9117
9524
  const matchText = ctx.getJS(call);
9118
- result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
9525
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
9119
9526
  }
9120
9527
  return restore(result);
9121
9528
  }
@@ -17194,12 +17601,22 @@ function lowerToLocaleCallsInReactiveExpr(expr, matcher) {
17194
17601
  const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
17195
17602
  if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
17196
17603
  continue;
17197
- const [, patternArg, tzArg] = node.args;
17198
- if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
17604
+ const [, patternArg, tzArg, namesArg] = node.args;
17605
+ if (!patternArg || tzArg?.kind !== "literal")
17606
+ continue;
17607
+ const localeText = call.arguments[0].getText(sourceFile);
17608
+ const patternJs = foldedArgToClientJs(patternArg, localeText);
17609
+ if (patternJs === null)
17199
17610
  continue;
17611
+ let namesJs = null;
17612
+ if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
17613
+ namesJs = foldedArgToClientJs(namesArg, localeText);
17614
+ if (namesJs === null)
17615
+ continue;
17616
+ }
17200
17617
  const receiverText = propAccess.expression.getText(sourceFile);
17201
17618
  const matchText = call.getText(sourceFile);
17202
- result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
17619
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
17203
17620
  }
17204
17621
  return restore(result);
17205
17622
  }
@@ -22002,15 +22419,16 @@ function isOmitBranch(node) {
22002
22419
  }
22003
22420
  // src/format-date-lowering.ts
22004
22421
  var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
22422
+ var EMPTY_NAMES = { kind: "array-literal", elements: [], raw: "[]" };
22005
22423
  function matchFormatDateCall(callee, args, locals) {
22006
22424
  if (callee.kind !== "identifier" || !locals.has(callee.name))
22007
22425
  return null;
22008
- if (args.length < 2 || args.length > 3)
22426
+ if (args.length < 2 || args.length > 4)
22009
22427
  return null;
22010
22428
  return {
22011
22429
  kind: "helper-call",
22012
22430
  helper: "format_date",
22013
- args: [args[0], args[1], args[2] ?? UTC_LITERAL]
22431
+ args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES]
22014
22432
  };
22015
22433
  }
22016
22434
  var formatDatePlugin = {