@barefootjs/jsx 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5243,7 +5243,9 @@ 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",
5248
+ REACTIVE_FACTORY_PARAM_SHADOWED: "BF114"
5247
5249
  };
5248
5250
  var errorMessages = {
5249
5251
  [ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
@@ -5269,7 +5271,9 @@ var errorMessages = {
5269
5271
  [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
5272
  [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
5273
  [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."
5274
+ [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.",
5275
+ [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.",
5276
+ [ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED]: "Reactive factory parameter is shadowed by a nested declaration inside the factory body, so argument substitution at the inline site would be ambiguous. Rename the inner binding so it does not collide with the parameter."
5273
5277
  };
5274
5278
  function createError(code, loc, options) {
5275
5279
  if (code === undefined || !(code in errorMessages)) {
@@ -7780,6 +7784,75 @@ function prescanReactiveFactoriesInSource(source, filePath) {
7780
7784
  prescanImportedReactiveFactories(sourceFile, filePath, result);
7781
7785
  return result;
7782
7786
  }
7787
+ function toComponentRelativeSpecifier(resolvedAbs, componentFilePath) {
7788
+ let rel = path_default.relative(path_default.dirname(componentFilePath), resolvedAbs).split(path_default.sep).join("/");
7789
+ rel = rel.replace(/\.(tsx|ts|jsx|js)$/, "");
7790
+ if (rel === "")
7791
+ rel = ".";
7792
+ if (!rel.startsWith("."))
7793
+ rel = "./" + rel;
7794
+ return rel;
7795
+ }
7796
+ function buildEntryImportIndex(sf, filePath) {
7797
+ const index = new Map;
7798
+ for (const stmt of sf.statements) {
7799
+ if (!ts8.isImportDeclaration(stmt))
7800
+ continue;
7801
+ if (!ts8.isStringLiteral(stmt.moduleSpecifier))
7802
+ continue;
7803
+ if (stmt.importClause?.isTypeOnly)
7804
+ continue;
7805
+ const src = stmt.moduleSpecifier.text;
7806
+ const targetKey = src.startsWith("./") || src.startsWith("../") ? resolveRelativeImportToFile(src, filePath) ?? "unresolved:" + src : src;
7807
+ const namedBindings = stmt.importClause?.namedBindings;
7808
+ if (namedBindings && ts8.isNamedImports(namedBindings)) {
7809
+ for (const el of namedBindings.elements) {
7810
+ if (el.isTypeOnly)
7811
+ continue;
7812
+ index.set(el.name.text, { targetKey, exportedName: (el.propertyName ?? el.name).text });
7813
+ }
7814
+ }
7815
+ }
7816
+ return index;
7817
+ }
7818
+ function collectEntryBindingNames(sf) {
7819
+ const names = new Set;
7820
+ function visit2(node) {
7821
+ if (ts8.isImportDeclaration(node) && node.importClause) {
7822
+ if (node.importClause.name)
7823
+ names.add(node.importClause.name.text);
7824
+ const namedBindings = node.importClause.namedBindings;
7825
+ if (namedBindings && ts8.isNamedImports(namedBindings)) {
7826
+ for (const el of namedBindings.elements)
7827
+ names.add(el.name.text);
7828
+ }
7829
+ if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
7830
+ names.add(namedBindings.name.text);
7831
+ }
7832
+ }
7833
+ if (ts8.isVariableDeclaration(node)) {
7834
+ const out = [];
7835
+ addBindingNames(node.name, out);
7836
+ for (const n of out)
7837
+ names.add(n);
7838
+ }
7839
+ if ((ts8.isFunctionDeclaration(node) || ts8.isClassDeclaration(node) || ts8.isEnumDeclaration(node)) && node.name) {
7840
+ names.add(node.name.text);
7841
+ }
7842
+ if (ts8.isFunctionLike(node)) {
7843
+ for (const p of node.parameters) {
7844
+ const out = [];
7845
+ addBindingNames(p.name, out);
7846
+ for (const n of out)
7847
+ names.add(n);
7848
+ }
7849
+ }
7850
+ ts8.forEachChild(node, visit2);
7851
+ }
7852
+ visit2(sf);
7853
+ return names;
7854
+ }
7855
+ var MAX_REEXPORT_HOPS = 1;
7783
7856
  function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7784
7857
  const candidateCallees = new Set;
7785
7858
  function collectCandidates(node) {
@@ -7820,29 +7893,31 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7820
7893
  }
7821
7894
  if (importsToCheck.length === 0)
7822
7895
  return;
7823
- for (const { src, specs } of importsToCheck) {
7824
- const resolved = resolveRelativeImportToFile(src, filePath);
7825
- if (!resolved)
7826
- continue;
7896
+ const entryBindingNames = collectEntryBindingNames(entrySourceFile);
7897
+ const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
7898
+ const plannedInjections = new Map;
7899
+ const helperCache = new Map;
7900
+ function loadHelperFile(abs) {
7901
+ const cached = helperCache.get(abs);
7902
+ if (cached !== undefined)
7903
+ return cached;
7827
7904
  let content;
7828
7905
  try {
7829
- content = fs.readFileSync(resolved, "utf8");
7906
+ content = fs.readFileSync(abs, "utf8");
7830
7907
  } catch {
7831
- continue;
7908
+ helperCache.set(abs, null);
7909
+ return null;
7832
7910
  }
7833
- const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
7834
7911
  const hasAnyPrimitiveText = [...REACTIVE_PRIMITIVES].some((p) => content.includes(p));
7835
- if (!hasAnyPrimitiveText) {
7836
- for (const spec of specs) {
7837
- if (!alreadyKnown(spec.local))
7838
- result.cleanFactoryImports.add(spec.local);
7839
- }
7840
- continue;
7912
+ const hasReexportText = content.includes("export") && content.includes("from");
7913
+ if (!hasAnyPrimitiveText && !hasReexportText) {
7914
+ helperCache.set(abs, "clean");
7915
+ return "clean";
7841
7916
  }
7842
- const helperSf = ts8.createSourceFile(resolved + ".prescan", content, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
7917
+ const sf = ts8.createSourceFile(abs + ".prescan", content, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
7843
7918
  const localFns = new Map;
7844
7919
  const exportedFns = new Map;
7845
- for (const stmt of helperSf.statements) {
7920
+ for (const stmt of sf.statements) {
7846
7921
  if (ts8.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
7847
7922
  localFns.set(stmt.name.text, stmt);
7848
7923
  const hasExportModifier = stmt.modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword) ?? false;
@@ -7852,27 +7927,89 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7852
7927
  }
7853
7928
  }
7854
7929
  }
7855
- for (const stmt of helperSf.statements) {
7856
- if (ts8.isExportDeclaration(stmt) && stmt.exportClause && ts8.isNamedExports(stmt.exportClause) && !stmt.moduleSpecifier && !stmt.isTypeOnly) {
7930
+ const reexports = new Map;
7931
+ let hasStarReexport = false;
7932
+ for (const stmt of sf.statements) {
7933
+ if (!ts8.isExportDeclaration(stmt) || stmt.isTypeOnly)
7934
+ continue;
7935
+ if (!stmt.moduleSpecifier) {
7936
+ if (stmt.exportClause && ts8.isNamedExports(stmt.exportClause)) {
7937
+ for (const el of stmt.exportClause.elements) {
7938
+ if (el.isTypeOnly)
7939
+ continue;
7940
+ const fn = localFns.get((el.propertyName ?? el.name).text);
7941
+ if (fn)
7942
+ exportedFns.set(el.name.text, fn);
7943
+ }
7944
+ }
7945
+ continue;
7946
+ }
7947
+ if (!ts8.isStringLiteral(stmt.moduleSpecifier))
7948
+ continue;
7949
+ if (stmt.exportClause && ts8.isNamedExports(stmt.exportClause)) {
7857
7950
  for (const el of stmt.exportClause.elements) {
7858
7951
  if (el.isTypeOnly)
7859
7952
  continue;
7860
- const fn = localFns.get((el.propertyName ?? el.name).text);
7861
- if (fn)
7862
- exportedFns.set(el.name.text, fn);
7953
+ reexports.set(el.name.text, {
7954
+ source: stmt.moduleSpecifier.text,
7955
+ innerName: (el.propertyName ?? el.name).text
7956
+ });
7863
7957
  }
7864
- }
7865
- }
7866
- const moduleBindings = collectHelperModuleValueBindings(helperSf);
7958
+ } else if (!stmt.exportClause) {
7959
+ hasStarReexport = true;
7960
+ }
7961
+ }
7962
+ const moduleBindings = collectHelperModuleValueBindings(sf);
7963
+ const info = { sf, exportedFns, reexports, hasStarReexport, moduleBindings };
7964
+ helperCache.set(abs, info);
7965
+ return info;
7966
+ }
7967
+ function lookupExportedFactory(abs, exportedName, visited, hopsLeft) {
7968
+ if (visited.has(abs))
7969
+ return { kind: "unknown" };
7970
+ visited.add(abs);
7971
+ const file = loadHelperFile(abs);
7972
+ if (file === null)
7973
+ return { kind: "unknown" };
7974
+ if (file === "clean")
7975
+ return { kind: "clean" };
7976
+ const fn = file.exportedFns.get(exportedName);
7977
+ if (fn)
7978
+ return { kind: "fn", fn, file, definingPath: abs };
7979
+ const re = file.reexports.get(exportedName);
7980
+ if (re) {
7981
+ if (hopsLeft <= 0)
7982
+ return { kind: "unknown" };
7983
+ if (!re.source.startsWith("./") && !re.source.startsWith("../"))
7984
+ return { kind: "unknown" };
7985
+ const target = resolveRelativeImportToFile(re.source, abs);
7986
+ if (!target)
7987
+ return { kind: "unknown" };
7988
+ return lookupExportedFactory(target, re.innerName, visited, hopsLeft - 1);
7989
+ }
7990
+ if (file.hasStarReexport)
7991
+ return { kind: "unknown" };
7992
+ return { kind: "clean" };
7993
+ }
7994
+ for (const { src, specs } of importsToCheck) {
7995
+ const resolved = resolveRelativeImportToFile(src, filePath);
7996
+ if (!resolved)
7997
+ continue;
7998
+ const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
7867
7999
  for (const spec of specs) {
7868
8000
  if (alreadyKnown(spec.local))
7869
8001
  continue;
7870
- const fn = exportedFns.get(spec.exported);
7871
- if (!fn) {
8002
+ const found = lookupExportedFactory(resolved, spec.exported, new Set, MAX_REEXPORT_HOPS);
8003
+ if (found.kind === "clean") {
7872
8004
  result.cleanFactoryImports.add(spec.local);
7873
8005
  continue;
7874
8006
  }
7875
- const det = detectReactiveFactory(fn, helperSf, resolved);
8007
+ if (found.kind === "unknown")
8008
+ continue;
8009
+ const { fn, file, definingPath } = found;
8010
+ const helperSf = file.sf;
8011
+ const moduleBindings = file.moduleBindings;
8012
+ const det = detectReactiveFactory(fn, helperSf, definingPath);
7876
8013
  if (!det) {
7877
8014
  result.cleanFactoryImports.add(spec.local);
7878
8015
  continue;
@@ -7885,17 +8022,64 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7885
8022
  result.declined.set(spec.local, det.declined);
7886
8023
  break;
7887
8024
  case "factory": {
7888
- const offending = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
7889
- if (offending.length > 0) {
8025
+ const capture = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
8026
+ if (capture.captured.length > 0) {
7890
8027
  result.declined.set(spec.local, {
7891
8028
  code: "BF112",
7892
- detail: `'${offending.join("', '")}'`,
8029
+ detail: `'${capture.captured.join("', '")}'`,
7893
8030
  loc: det.info.loc
7894
8031
  });
7895
- } else {
7896
- det.info.sourceFilePath = resolved;
7897
- result.factories.set(spec.local, det.info);
8032
+ break;
8033
+ }
8034
+ const required = [];
8035
+ const pending = [];
8036
+ let declinedEntry = null;
8037
+ for (const ref of capture.importedRefs) {
8038
+ let specifier;
8039
+ let targetKey;
8040
+ if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
8041
+ const abs = resolveRelativeImportToFile(ref.source, definingPath);
8042
+ if (!abs) {
8043
+ declinedEntry = {
8044
+ code: "BF112",
8045
+ detail: `'${ref.localName}' (import '${ref.source}' did not resolve from the helper file)`,
8046
+ loc: det.info.loc
8047
+ };
8048
+ break;
8049
+ }
8050
+ specifier = toComponentRelativeSpecifier(abs, filePath);
8051
+ targetKey = abs;
8052
+ } else {
8053
+ specifier = ref.source;
8054
+ targetKey = ref.source;
8055
+ }
8056
+ const existing = entryImportIndex.get(ref.localName);
8057
+ if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName) {
8058
+ continue;
8059
+ }
8060
+ const planned = plannedInjections.get(ref.localName);
8061
+ const collides = existing !== undefined || planned !== undefined && (planned.targetKey !== targetKey || planned.exportedName !== ref.exportedName) || planned === undefined && entryBindingNames.has(ref.localName);
8062
+ if (collides) {
8063
+ declinedEntry = {
8064
+ code: "BF113",
8065
+ detail: `'${ref.localName}' from '${specifier}'`,
8066
+ loc: det.info.loc
8067
+ };
8068
+ break;
8069
+ }
8070
+ pending.push([ref.localName, { targetKey, exportedName: ref.exportedName }]);
8071
+ required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier });
8072
+ }
8073
+ if (declinedEntry) {
8074
+ result.declined.set(spec.local, declinedEntry);
8075
+ break;
7898
8076
  }
8077
+ for (const [name, id] of pending)
8078
+ plannedInjections.set(name, id);
8079
+ det.info.sourceFilePath = definingPath;
8080
+ if (required.length > 0)
8081
+ det.info.requiredImports = required;
8082
+ result.factories.set(spec.local, det.info);
7899
8083
  break;
7900
8084
  }
7901
8085
  }
@@ -7903,7 +8087,8 @@ function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
7903
8087
  }
7904
8088
  }
7905
8089
  function collectHelperModuleValueBindings(sf) {
7906
- const names = new Set;
8090
+ const local = new Set;
8091
+ const imported = new Map;
7907
8092
  for (const stmt of sf.statements) {
7908
8093
  if (ts8.isVariableStatement(stmt)) {
7909
8094
  const out = [];
@@ -7911,11 +8096,11 @@ function collectHelperModuleValueBindings(sf) {
7911
8096
  addBindingNames(decl.name, out);
7912
8097
  }
7913
8098
  for (const n of out)
7914
- names.add(n);
8099
+ local.add(n);
7915
8100
  continue;
7916
8101
  }
7917
8102
  if ((ts8.isFunctionDeclaration(stmt) || ts8.isClassDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name) {
7918
- names.add(stmt.name.text);
8103
+ local.add(stmt.name.text);
7919
8104
  continue;
7920
8105
  }
7921
8106
  if (ts8.isImportDeclaration(stmt)) {
@@ -7927,25 +8112,25 @@ function collectHelperModuleValueBindings(sf) {
7927
8112
  if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
7928
8113
  continue;
7929
8114
  if (stmt.importClause?.name)
7930
- names.add(stmt.importClause.name.text);
8115
+ local.add(stmt.importClause.name.text);
7931
8116
  const namedBindings = stmt.importClause?.namedBindings;
7932
8117
  if (namedBindings && ts8.isNamedImports(namedBindings)) {
7933
8118
  for (const el of namedBindings.elements) {
7934
8119
  if (el.isTypeOnly)
7935
8120
  continue;
7936
- names.add(el.name.text);
8121
+ imported.set(el.name.text, { source: src, exportedName: (el.propertyName ?? el.name).text });
7937
8122
  }
7938
8123
  }
7939
8124
  if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
7940
- names.add(namedBindings.name.text);
8125
+ local.add(namedBindings.name.text);
7941
8126
  }
7942
8127
  }
7943
8128
  }
7944
- return names;
8129
+ return { local, imported };
7945
8130
  }
7946
8131
  function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
7947
8132
  if (!fn.body)
7948
- return [];
8133
+ return { captured: [], importedRefs: [] };
7949
8134
  const free = extractFreeIdentifiersFromNode(fn.body);
7950
8135
  const exclude = new Set(info.params);
7951
8136
  for (const b of info.localBindings)
@@ -7955,14 +8140,22 @@ function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
7955
8140
  for (const p of REACTIVE_PRIMITIVES)
7956
8141
  exclude.add(p);
7957
8142
  exclude.add(selfName);
7958
- const offending = [];
8143
+ const captured = [];
8144
+ const importedRefs = [];
7959
8145
  for (const id of free) {
7960
8146
  if (exclude.has(id))
7961
8147
  continue;
7962
- if (moduleBindings.has(id))
7963
- offending.push(id);
8148
+ if (moduleBindings.local.has(id)) {
8149
+ captured.push(id);
8150
+ continue;
8151
+ }
8152
+ const imp = moduleBindings.imported.get(id);
8153
+ if (imp)
8154
+ importedRefs.push({ localName: id, source: imp.source, exportedName: imp.exportedName });
7964
8155
  }
7965
- return offending.sort();
8156
+ captured.sort();
8157
+ importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
8158
+ return { captured, importedRefs };
7966
8159
  }
7967
8160
  function detectReactiveFactory(node, sourceFile, filePath) {
7968
8161
  if (!node.body || !node.name)
@@ -7981,6 +8174,17 @@ function detectReactiveFactory(node, sourceFile, filePath) {
7981
8174
  if (!hasReactiveCall)
7982
8175
  return null;
7983
8176
  const loc = getSourceLocation(node, sourceFile, filePath);
8177
+ let totalReturnCount = 0;
8178
+ function countReturns(n) {
8179
+ if (ts8.isFunctionLike(n))
8180
+ return;
8181
+ if (ts8.isReturnStatement(n)) {
8182
+ totalReturnCount++;
8183
+ return;
8184
+ }
8185
+ ts8.forEachChild(n, countReturns);
8186
+ }
8187
+ ts8.forEachChild(node.body, countReturns);
7984
8188
  let returnExpr = null;
7985
8189
  let returnCount = 0;
7986
8190
  for (const stmt of node.body.statements) {
@@ -7998,7 +8202,7 @@ function detectReactiveFactory(node, sourceFile, filePath) {
7998
8202
  expr = expr.expression;
7999
8203
  returnExpr = expr;
8000
8204
  }
8001
- if (returnCount !== 1 || !returnExpr)
8205
+ if (totalReturnCount !== 1 || returnCount !== 1 || !returnExpr)
8002
8206
  return { kind: "reactive-shaped" };
8003
8207
  const returnTupleIdentifiers = [];
8004
8208
  let returnKind;
@@ -8032,6 +8236,14 @@ function detectReactiveFactory(node, sourceFile, filePath) {
8032
8236
  } else {
8033
8237
  return { kind: "reactive-shaped" };
8034
8238
  }
8239
+ const params = [];
8240
+ for (const p of node.parameters) {
8241
+ if (ts8.isIdentifier(p.name)) {
8242
+ params.push(p.name.text);
8243
+ continue;
8244
+ }
8245
+ return { kind: "reactive-shaped" };
8246
+ }
8035
8247
  const localBindings = [];
8036
8248
  for (const stmt of node.body.statements) {
8037
8249
  if (ts8.isVariableStatement(stmt)) {
@@ -8042,15 +8254,95 @@ function detectReactiveFactory(node, sourceFile, filePath) {
8042
8254
  localBindings.push(stmt.name.text);
8043
8255
  }
8044
8256
  }
8045
- const bodyStatements = node.body.statements.filter((s) => !ts8.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
8257
+ const relevantNames = new Set([...params, ...localBindings, ...returnTupleIdentifiers]);
8258
+ const renameSites = [];
8259
+ let shadowedParam = null;
8260
+ function collectRenameSites(root, toBodyOffset) {
8261
+ function push(id, form) {
8262
+ renameSites.push({
8263
+ name: id.text,
8264
+ start: toBodyOffset(id.getStart(sourceFile)),
8265
+ end: toBodyOffset(id.getEnd()),
8266
+ form
8267
+ });
8268
+ }
8269
+ function classify(id) {
8270
+ if (!relevantNames.has(id.text))
8271
+ return;
8272
+ const p = id.parent;
8273
+ if (ts8.isPropertyAccessExpression(p) && p.name === id)
8274
+ return;
8275
+ if (ts8.isPropertyAssignment(p) && p.name === id)
8276
+ return;
8277
+ if (ts8.isBindingElement(p) && p.propertyName === id)
8278
+ return;
8279
+ if ((ts8.isMethodDeclaration(p) || ts8.isGetAccessorDeclaration(p) || ts8.isSetAccessorDeclaration(p) || ts8.isPropertyDeclaration(p) || ts8.isEnumMember(p)) && p.name === id)
8280
+ return;
8281
+ if (ts8.isJsxAttribute(p) && p.name === id)
8282
+ return;
8283
+ if (ts8.isLabeledStatement(p) && p.label === id || (ts8.isBreakStatement(p) || ts8.isContinueStatement(p)) && p.label === id)
8284
+ return;
8285
+ if ((ts8.isJsxOpeningElement(p) || ts8.isJsxSelfClosingElement(p) || ts8.isJsxClosingElement(p)) && p.tagName === id && /^[a-z]/.test(id.text))
8286
+ return;
8287
+ if (ts8.isShorthandPropertyAssignment(p) && p.name === id) {
8288
+ push(id, "shorthand");
8289
+ return;
8290
+ }
8291
+ if (ts8.isBindingElement(p) && p.name === id && !p.propertyName && ts8.isObjectBindingPattern(p.parent)) {
8292
+ if (params.includes(id.text))
8293
+ shadowedParam = id.text;
8294
+ push(id, "shorthand");
8295
+ return;
8296
+ }
8297
+ const isDecl = (ts8.isVariableDeclaration(p) || ts8.isParameter(p) || ts8.isBindingElement(p) || ts8.isFunctionDeclaration(p) || ts8.isFunctionExpression(p) || ts8.isClassDeclaration(p) || ts8.isClassExpression(p)) && p.name === id;
8298
+ if (isDecl && params.includes(id.text))
8299
+ shadowedParam = id.text;
8300
+ push(id, "plain");
8301
+ }
8302
+ function visit2(n) {
8303
+ if (ts8.isTypeNode(n) || ts8.isTypeParameterDeclaration(n) || ts8.isTypeAliasDeclaration(n) || ts8.isInterfaceDeclaration(n))
8304
+ return;
8305
+ if (ts8.isIdentifier(n)) {
8306
+ classify(n);
8307
+ return;
8308
+ }
8309
+ ts8.forEachChild(n, visit2);
8310
+ }
8311
+ visit2(root);
8312
+ }
8313
+ const keptStatements = node.body.statements.filter((s) => !ts8.isReturnStatement(s));
8314
+ const pieces = [];
8315
+ let base = 0;
8316
+ for (const stmt of keptStatements) {
8317
+ const text = stmt.getText(sourceFile);
8318
+ const stmtStart = stmt.getStart(sourceFile);
8319
+ collectRenameSites(stmt, (pos) => pos - stmtStart + base);
8320
+ pieces.push(text);
8321
+ base += text.length + 1;
8322
+ }
8323
+ const bodyStatements = pieces.join(`
8046
8324
  `);
8047
- const params = [];
8048
- for (const p of node.parameters) {
8049
- if (ts8.isIdentifier(p.name)) {
8050
- params.push(p.name.text);
8051
- continue;
8325
+ if (shadowedParam !== null) {
8326
+ return {
8327
+ kind: "declined",
8328
+ declined: {
8329
+ code: "BF114",
8330
+ detail: `parameter '${shadowedParam}' of '${node.name.text}' is shadowed by a nested declaration inside the factory body`,
8331
+ loc
8332
+ }
8333
+ };
8334
+ }
8335
+ for (const site of renameSites) {
8336
+ if (bodyStatements.slice(site.start, site.end) !== site.name) {
8337
+ return {
8338
+ kind: "declined",
8339
+ declined: {
8340
+ code: "BF111",
8341
+ detail: `internal rename-site offset mismatch for '${site.name}' — this is a compiler bug, ` + `please report it`,
8342
+ loc
8343
+ }
8344
+ };
8052
8345
  }
8053
- return { kind: "reactive-shaped" };
8054
8346
  }
8055
8347
  return {
8056
8348
  kind: "factory",
@@ -8060,7 +8352,8 @@ function detectReactiveFactory(node, sourceFile, filePath) {
8060
8352
  returnTupleIdentifiers,
8061
8353
  returnKind,
8062
8354
  localBindings,
8063
- loc
8355
+ loc,
8356
+ renameSites
8064
8357
  }
8065
8358
  };
8066
8359
  }
@@ -8086,6 +8379,7 @@ function rewriteFactoryCallsInSource(source, prescan) {
8086
8379
  const { factories, sourceFile } = prescan;
8087
8380
  const edits = [];
8088
8381
  let callSiteIndex = 0;
8382
+ const inlinedFactories = new Set;
8089
8383
  function visitStmt(node, inComponent) {
8090
8384
  if (ts8.isVariableStatement(node) && inComponent) {
8091
8385
  for (const decl of node.declarationList.declarations) {
@@ -8163,34 +8457,65 @@ function rewriteFactoryCallsInSource(source, prescan) {
8163
8457
  const argTexts = args.map((a) => a.getText(sourceFile));
8164
8458
  const thisCallIndex = callSiteIndex++;
8165
8459
  const suffix = `_bf${thisCallIndex}`;
8166
- let body = factory.bodySource;
8167
- const internalRenames = new Set(factory.localBindings);
8168
- for (const ex of excludeFromSuffixRename)
8169
- internalRenames.delete(ex);
8170
- for (const name of internalRenames) {
8171
- body = body.replace(new RegExp(`\\b${escapeRegex(name)}\\b`, "g"), name + suffix);
8460
+ const renames = new Map;
8461
+ for (const name of factory.localBindings) {
8462
+ if (!excludeFromSuffixRename.has(name))
8463
+ renames.set(name, name + suffix);
8464
+ }
8465
+ if (renameReturnToCallerNames) {
8466
+ for (const [n, caller] of renameReturnToCallerNames) {
8467
+ if (caller !== n)
8468
+ renames.set(n, caller);
8469
+ }
8172
8470
  }
8173
8471
  const atomicArg = /^(?:[\w$.]+|'[^'\\]*'|"[^"\\]*"|-?\d+(?:\.\d+)?)$/;
8174
8472
  for (let i = 0;i < factory.params.length; i++) {
8175
8473
  const p = factory.params[i];
8176
- const a = argTexts[i] ?? "undefined";
8177
- const wrapped = atomicArg.test(a.trim()) ? a.trim() : `(${a})`;
8178
- body = body.replace(new RegExp(`\\b${escapeRegex(p)}\\b`, "g"), wrapped);
8474
+ const a = (argTexts[i] ?? "undefined").trim();
8475
+ renames.set(p, atomicArg.test(a) ? a : `(${a})`);
8179
8476
  }
8180
- if (renameReturnToCallerNames) {
8181
- for (const [n, caller] of renameReturnToCallerNames) {
8182
- body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
8183
- }
8477
+ let body = factory.bodySource;
8478
+ for (let i = factory.renameSites.length - 1;i >= 0; i--) {
8479
+ const site = factory.renameSites[i];
8480
+ const repl = renames.get(site.name);
8481
+ if (repl === undefined)
8482
+ continue;
8483
+ const text = site.form === "shorthand" ? `${site.name}: ${repl}` : repl;
8484
+ body = body.slice(0, site.start) + text + body.slice(site.end);
8184
8485
  }
8185
8486
  edits.push({
8186
8487
  start: stmt.getStart(sourceFile),
8187
8488
  end: stmt.getEnd(),
8188
8489
  replacement: body
8189
8490
  });
8491
+ inlinedFactories.add(factory);
8190
8492
  }
8191
8493
  visitStmt(sourceFile, false);
8192
8494
  if (edits.length === 0)
8193
8495
  return source;
8496
+ const importsBySpecifier = new Map;
8497
+ for (const f of inlinedFactories) {
8498
+ for (const r of f.requiredImports ?? []) {
8499
+ let names = importsBySpecifier.get(r.specifier);
8500
+ if (!names) {
8501
+ names = new Map;
8502
+ importsBySpecifier.set(r.specifier, names);
8503
+ }
8504
+ names.set(r.localName, r.exportedName);
8505
+ }
8506
+ }
8507
+ if (importsBySpecifier.size > 0) {
8508
+ const lines = [...importsBySpecifier].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([spec, names]) => {
8509
+ const specifiers = [...names].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([local, exported]) => exported === local ? local : `${exported} as ${local}`);
8510
+ return `import { ${specifiers.join(", ")} } from '${spec}'`;
8511
+ });
8512
+ const at = factoryImportInsertionOffset(sourceFile);
8513
+ edits.push({ start: at, end: at, replacement: at === 0 ? lines.join(`
8514
+ `) + `
8515
+ ` : `
8516
+ ` + lines.join(`
8517
+ `) });
8518
+ }
8194
8519
  edits.sort((a, b) => b.start - a.start);
8195
8520
  let out = source;
8196
8521
  for (const e of edits) {
@@ -8198,6 +8523,20 @@ function rewriteFactoryCallsInSource(source, prescan) {
8198
8523
  }
8199
8524
  return out;
8200
8525
  }
8526
+ function factoryImportInsertionOffset(sf) {
8527
+ let lastImportEnd = -1;
8528
+ let directiveEnd = -1;
8529
+ for (const stmt of sf.statements) {
8530
+ if (ts8.isImportDeclaration(stmt)) {
8531
+ lastImportEnd = stmt.getEnd();
8532
+ continue;
8533
+ }
8534
+ if (directiveEnd === -1 && ts8.isExpressionStatement(stmt) && ts8.isStringLiteral(stmt.expression) && stmt.expression.text === "use client") {
8535
+ directiveEnd = stmt.getEnd();
8536
+ }
8537
+ }
8538
+ return lastImportEnd >= 0 ? lastImportEnd : directiveEnd >= 0 ? directiveEnd : 0;
8539
+ }
8201
8540
  function isPascalCaseComponentFn(node) {
8202
8541
  if (ts8.isFunctionDeclaration(node) && node.name) {
8203
8542
  return /^[A-Z]/.test(node.name.text);
@@ -8207,15 +8546,27 @@ function isPascalCaseComponentFn(node) {
8207
8546
  }
8208
8547
  return false;
8209
8548
  }
8210
- function escapeRegex(s) {
8211
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8212
- }
8213
8549
  function declinedFactoryMessage(callee, d) {
8214
8550
  if (d.code === "BF112") {
8215
8551
  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
8552
  }
8553
+ if (d.code === "BF113") {
8554
+ 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 }).`;
8555
+ }
8217
8556
  return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
8218
8557
  }
8558
+ function declinedFactoryErrorCode(code) {
8559
+ switch (code) {
8560
+ case "BF112":
8561
+ return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
8562
+ case "BF113":
8563
+ return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
8564
+ case "BF114":
8565
+ return ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED;
8566
+ default:
8567
+ return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
8568
+ }
8569
+ }
8219
8570
  function validateReactiveFactoryCalls(ctx) {
8220
8571
  if (!ctx.componentNode)
8221
8572
  return;
@@ -8239,7 +8590,7 @@ function validateReactiveFactoryCalls(ctx) {
8239
8590
  continue;
8240
8591
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
8241
8592
  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) }));
8593
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8243
8594
  continue;
8244
8595
  }
8245
8596
  const objectFactory = ctx.reactiveFactories.get(callee);
@@ -8296,7 +8647,7 @@ function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
8296
8647
  }
8297
8648
  const declinedEntry = ctx.declinedReactiveFactories.get(callee);
8298
8649
  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) }));
8650
+ ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
8300
8651
  return;
8301
8652
  }
8302
8653
  if (ctx.reactiveShapedHelpers.has(callee)) {
@@ -8848,19 +9199,83 @@ function resolveFreeRefs(node, env) {
8848
9199
  // src/to-locale-date-lowering.ts
8849
9200
  var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
8850
9201
  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);
9202
+ var formatCache = new Map;
9203
+ var namesCache = new Map;
9204
+ function deriveMonthNames(locale, ctx) {
9205
+ return deriveNamesCached(`${locale}|m|${ctx}`, () => {
9206
+ const months = (width) => Array.from({ length: 12 }, (_, m) => probePart(locale, ctx === "formatting" ? { month: width, day: "numeric" } : { month: width }, Date.UTC(2001, m, 15), "month"));
9207
+ return [...months("long"), ...months("short")];
9208
+ });
9209
+ }
9210
+ function deriveWeekdayNames(locale, ctx) {
9211
+ return deriveNamesCached(`${locale}|w|${ctx}`, () => {
9212
+ 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"));
9213
+ return [...weekdays("long"), ...weekdays("short")];
9214
+ });
9215
+ }
9216
+ function probePart(locale, options, utc, type) {
9217
+ const parts = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).formatToParts(new Date(utc));
9218
+ const found = parts.find((p) => p.type === type);
9219
+ if (!found || !found.value)
9220
+ throw new Error("missing part");
9221
+ return found.value;
9222
+ }
9223
+ function deriveNamesCached(key, derive) {
9224
+ const cached = namesCache.get(key);
8854
9225
  if (cached !== undefined)
8855
9226
  return cached;
8856
- const derived = derivePattern(locale);
8857
- patternCache.set(locale, derived);
9227
+ let derived;
9228
+ try {
9229
+ derived = derive();
9230
+ } catch {
9231
+ derived = null;
9232
+ }
9233
+ namesCache.set(key, derived);
8858
9234
  return derived;
8859
9235
  }
8860
- function derivePattern(locale) {
9236
+ function resolveLocaleDateFormat(locale, probeOptions) {
9237
+ const key = `${locale}|${JSON.stringify(probeOptions, Object.keys(probeOptions).sort())}`;
9238
+ const cached = formatCache.get(key);
9239
+ if (cached !== undefined)
9240
+ return cached;
9241
+ const derived = deriveFormat(locale, probeOptions);
9242
+ formatCache.set(key, derived);
9243
+ return derived;
9244
+ }
9245
+ var VERIFY_UTC = new Date(Date.UTC(2001, 4, 13));
9246
+ function renderPatternAt(pattern, names, y, m, d, wd) {
9247
+ const pad2 = (n) => String(n).padStart(2, "0");
9248
+ return pattern.replace(/YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D/g, (token) => {
9249
+ switch (token) {
9250
+ case "YYYY":
9251
+ return String(y).padStart(4, "0");
9252
+ case "MMMM":
9253
+ return names[m - 1] ?? "";
9254
+ case "MMM":
9255
+ return names[12 + m - 1] ?? "";
9256
+ case "MM":
9257
+ return pad2(m);
9258
+ case "M":
9259
+ return String(m);
9260
+ case "DD":
9261
+ return pad2(d);
9262
+ case "D":
9263
+ return String(d);
9264
+ case "dddd":
9265
+ return names[24 + wd] ?? "";
9266
+ default:
9267
+ return names[31 + wd] ?? "";
9268
+ }
9269
+ });
9270
+ }
9271
+ function deriveFormat(locale, probeOptions) {
9272
+ let dtf;
8861
9273
  let parts;
8862
9274
  try {
8863
- const dtf = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
9275
+ dtf = new Intl.DateTimeFormat(locale, {
9276
+ ...probeOptions,
9277
+ timeZone: "UTC"
9278
+ });
8864
9279
  const resolved = dtf.resolvedOptions();
8865
9280
  if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
8866
9281
  return null;
@@ -8868,7 +9283,18 @@ function derivePattern(locale) {
8868
9283
  } catch {
8869
9284
  return null;
8870
9285
  }
9286
+ const monthTables = [
9287
+ deriveMonthNames(locale, "formatting"),
9288
+ deriveMonthNames(locale, "standalone")
9289
+ ];
9290
+ const weekdayTables = [
9291
+ deriveWeekdayNames(locale, "formatting"),
9292
+ deriveWeekdayNames(locale, "standalone")
9293
+ ];
9294
+ let monthTable = null;
9295
+ let weekdayTable = null;
8871
9296
  let pattern = "";
9297
+ let usesNames = false;
8872
9298
  for (const part of parts) {
8873
9299
  switch (part.type) {
8874
9300
  case "year":
@@ -8876,14 +9302,27 @@ function derivePattern(locale) {
8876
9302
  return null;
8877
9303
  pattern += "YYYY";
8878
9304
  break;
8879
- case "month":
8880
- if (part.value === "2")
9305
+ case "month": {
9306
+ if (part.value === "2") {
8881
9307
  pattern += "M";
8882
- else if (part.value === "02")
9308
+ break;
9309
+ }
9310
+ if (part.value === "02") {
8883
9311
  pattern += "MM";
9312
+ break;
9313
+ }
9314
+ const wide = monthTables.find((t) => t && part.value === t[1]) ?? null;
9315
+ const abbr = wide ? null : monthTables.find((t) => t && part.value === t[12 + 1]) ?? null;
9316
+ if (wide)
9317
+ pattern += "MMMM";
9318
+ else if (abbr)
9319
+ pattern += "MMM";
8884
9320
  else
8885
9321
  return null;
9322
+ monthTable = wide ?? abbr;
9323
+ usesNames = true;
8886
9324
  break;
9325
+ }
8887
9326
  case "day":
8888
9327
  if (part.value === "3")
8889
9328
  pattern += "D";
@@ -8892,8 +9331,21 @@ function derivePattern(locale) {
8892
9331
  else
8893
9332
  return null;
8894
9333
  break;
9334
+ case "weekday": {
9335
+ const wide = weekdayTables.find((t) => t && part.value === t[6]) ?? null;
9336
+ const abbr = wide ? null : weekdayTables.find((t) => t && part.value === t[7 + 6]) ?? null;
9337
+ if (wide)
9338
+ pattern += "dddd";
9339
+ else if (abbr)
9340
+ pattern += "ddd";
9341
+ else
9342
+ return null;
9343
+ weekdayTable = wide ?? abbr;
9344
+ usesNames = true;
9345
+ break;
9346
+ }
8895
9347
  case "literal":
8896
- if (/[YMD]/.test(part.value))
9348
+ if (/[YMD]/.test(part.value) || /ddd/.test(part.value))
8897
9349
  return null;
8898
9350
  pattern += part.value;
8899
9351
  break;
@@ -8901,9 +9353,73 @@ function derivePattern(locale) {
8901
9353
  return null;
8902
9354
  }
8903
9355
  }
8904
- if (!pattern.includes("YYYY") || !/M/.test(pattern) || !/D/.test(pattern))
9356
+ if (!/YYYY|MMMM|MMM|MM|M/.test(pattern) && !/DD|D/.test(pattern))
9357
+ return null;
9358
+ if (!usesNames)
9359
+ return { pattern, names: null };
9360
+ const names = [
9361
+ ...monthTable ?? monthTables[0] ?? monthTables[1] ?? Array(24).fill(""),
9362
+ ...weekdayTable ?? weekdayTables[0] ?? weekdayTables[1] ?? Array(14).fill("")
9363
+ ];
9364
+ if (renderPatternAt(pattern, names, 2001, 5, 13, 0) !== dtf.format(VERIFY_UTC))
8905
9365
  return null;
8906
- return pattern;
9366
+ return { pattern, names };
9367
+ }
9368
+ function unionMemberLiteral(member) {
9369
+ const m = /^'([^'\\]*)'$|^"([^"\\]*)"$/.exec(member.raw.trim());
9370
+ return m ? m[1] ?? m[2] : null;
9371
+ }
9372
+ function resolveLocaleUnionMembers(locale, metadata) {
9373
+ let sourcePropName = null;
9374
+ if (metadata.propsObjectName) {
9375
+ if (locale.kind === "member" && !locale.computed && locale.object.kind === "identifier" && locale.object.name === metadata.propsObjectName) {
9376
+ sourcePropName = locale.property;
9377
+ }
9378
+ } else if (locale.kind === "identifier") {
9379
+ const name = locale.name;
9380
+ const param = metadata.propsParams?.find((pp) => pp.name === name);
9381
+ if (param)
9382
+ sourcePropName = param.sourceName ?? param.name;
9383
+ }
9384
+ if (!sourcePropName)
9385
+ return null;
9386
+ const target = sourcePropName;
9387
+ const prop = metadata.propsType?.properties?.find((p) => p.name === target);
9388
+ if (!prop || prop.optional)
9389
+ return null;
9390
+ const type = prop.type;
9391
+ if (type.kind !== "union" || !type.unionTypes || type.unionTypes.length === 0)
9392
+ return null;
9393
+ const members = [];
9394
+ for (const member of type.unionTypes) {
9395
+ const value = unionMemberLiteral(member);
9396
+ if (value === null)
9397
+ return null;
9398
+ members.push(value);
9399
+ }
9400
+ return members;
9401
+ }
9402
+ var strLit = (value) => ({ kind: "literal", value, literalType: "string" });
9403
+ function strArr(values) {
9404
+ return {
9405
+ kind: "array-literal",
9406
+ elements: values.map((v) => strLit(v)),
9407
+ raw: JSON.stringify(values)
9408
+ };
9409
+ }
9410
+ function foldMembers(locale, members, leaves, allEqual) {
9411
+ let expr = leaves[leaves.length - 1];
9412
+ if (allEqual)
9413
+ return expr;
9414
+ for (let i = leaves.length - 2;i >= 0; i--) {
9415
+ expr = {
9416
+ kind: "conditional",
9417
+ test: { kind: "binary", op: "===", left: locale, right: strLit(members[i]) },
9418
+ consequent: leaves[i],
9419
+ alternate: expr
9420
+ };
9421
+ }
9422
+ return expr;
8907
9423
  }
8908
9424
  function matchToLocaleDateStringCall(callee, args, metadata) {
8909
9425
  if (callee.kind !== "member" || callee.computed)
@@ -8911,17 +9427,23 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
8911
9427
  if (callee.property !== "toLocaleDateString" || args.length !== 2)
8912
9428
  return null;
8913
9429
  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)
8917
- 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")
9430
+ if (options.kind !== "object-literal")
8922
9431
  return null;
8923
- const tz = String(prop.value.value);
8924
- if (!TO_LOCALE_TZ_RE.test(tz))
9432
+ let tz = null;
9433
+ const probeOptions = {};
9434
+ for (const prop of options.properties) {
9435
+ if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
9436
+ return null;
9437
+ const value = String(prop.value.value);
9438
+ if (prop.key === "timeZone") {
9439
+ if (!TO_LOCALE_TZ_RE.test(value))
9440
+ return null;
9441
+ tz = value;
9442
+ } else {
9443
+ probeOptions[prop.key] = value;
9444
+ }
9445
+ }
9446
+ if (tz === null)
8925
9447
  return null;
8926
9448
  const receiverType = resolveReceiverType(callee.object, metadata, new Map);
8927
9449
  if (!receiverType || receiverType.kind !== "interface")
@@ -8931,19 +9453,64 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
8931
9453
  return null;
8932
9454
  if (metadata.typeDefinitions.some((d) => d.name === typeName))
8933
9455
  return null;
8934
- const pattern = resolveLocaleDatePattern(String(locale.value));
8935
- if (pattern === null)
9456
+ if (locale.kind === "literal" && locale.literalType === "string") {
9457
+ const format2 = resolveLocaleDateFormat(String(locale.value), probeOptions);
9458
+ if (format2 === null)
9459
+ return null;
9460
+ return {
9461
+ kind: "helper-call",
9462
+ helper: "format_date",
9463
+ args: [callee.object, strLit(format2.pattern), strLit(tz), strArr(format2.names ?? [])]
9464
+ };
9465
+ }
9466
+ const members = resolveLocaleUnionMembers(locale, metadata);
9467
+ if (!members)
8936
9468
  return null;
9469
+ const formats = [];
9470
+ for (const member of members) {
9471
+ const format2 = resolveLocaleDateFormat(member, probeOptions);
9472
+ if (format2 === null)
9473
+ return null;
9474
+ formats.push(format2);
9475
+ }
9476
+ const patterns = formats.map((f) => f.pattern);
9477
+ const nameTables = formats.map((f) => JSON.stringify(f.names ?? []));
8937
9478
  return {
8938
9479
  kind: "helper-call",
8939
9480
  helper: "format_date",
8940
9481
  args: [
8941
9482
  callee.object,
8942
- { kind: "literal", value: pattern, literalType: "string" },
8943
- { kind: "literal", value: tz, literalType: "string" }
9483
+ foldMembers(locale, members, patterns.map(strLit), new Set(patterns).size === 1),
9484
+ strLit(tz),
9485
+ foldMembers(locale, members, formats.map((f) => strArr(f.names ?? [])), new Set(nameTables).size === 1)
8944
9486
  ]
8945
9487
  };
8946
9488
  }
9489
+ function foldedArgToClientJs(arg, localeText) {
9490
+ if (arg.kind === "literal")
9491
+ return JSON.stringify(arg.value);
9492
+ if (arg.kind === "array-literal") {
9493
+ const values = [];
9494
+ for (const el of arg.elements) {
9495
+ if (el.kind !== "literal")
9496
+ return null;
9497
+ values.push(String(el.value));
9498
+ }
9499
+ return JSON.stringify(values);
9500
+ }
9501
+ if (arg.kind !== "conditional")
9502
+ return null;
9503
+ const t = arg.test;
9504
+ if (t.kind !== "binary" || t.op !== "===" || t.right.kind !== "literal")
9505
+ return null;
9506
+ if (arg.consequent.kind !== "literal" && arg.consequent.kind !== "array-literal")
9507
+ return null;
9508
+ const cons = foldedArgToClientJs(arg.consequent, localeText);
9509
+ const rest = foldedArgToClientJs(arg.alternate, localeText);
9510
+ if (cons === null || rest === null)
9511
+ return null;
9512
+ return `${localeText} === ${JSON.stringify(t.right.value)} ? ${cons} : ${rest}`;
9513
+ }
8947
9514
  var toLocaleDatePlugin = {
8948
9515
  name: "toLocaleDateString",
8949
9516
  prepare(metadata) {
@@ -9110,12 +9677,22 @@ function lowerToLocaleDateCalls(text, expr, ctx) {
9110
9677
  const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
9111
9678
  if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
9112
9679
  continue;
9113
- const [, patternArg, tzArg] = node.args;
9114
- if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
9680
+ const [, patternArg, tzArg, namesArg] = node.args;
9681
+ if (!patternArg || tzArg?.kind !== "literal")
9682
+ continue;
9683
+ const localeText = ctx.getJS(call.arguments[0]);
9684
+ const patternJs = foldedArgToClientJs(patternArg, localeText);
9685
+ if (patternJs === null)
9115
9686
  continue;
9687
+ let namesJs = null;
9688
+ if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
9689
+ namesJs = foldedArgToClientJs(namesArg, localeText);
9690
+ if (namesJs === null)
9691
+ continue;
9692
+ }
9116
9693
  const receiverText = ctx.getJS(propAccess.expression);
9117
9694
  const matchText = ctx.getJS(call);
9118
- result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
9695
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
9119
9696
  }
9120
9697
  return restore(result);
9121
9698
  }
@@ -17194,12 +17771,22 @@ function lowerToLocaleCallsInReactiveExpr(expr, matcher) {
17194
17771
  const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
17195
17772
  if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
17196
17773
  continue;
17197
- const [, patternArg, tzArg] = node.args;
17198
- if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
17774
+ const [, patternArg, tzArg, namesArg] = node.args;
17775
+ if (!patternArg || tzArg?.kind !== "literal")
17199
17776
  continue;
17777
+ const localeText = call.arguments[0].getText(sourceFile);
17778
+ const patternJs = foldedArgToClientJs(patternArg, localeText);
17779
+ if (patternJs === null)
17780
+ continue;
17781
+ let namesJs = null;
17782
+ if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
17783
+ namesJs = foldedArgToClientJs(namesArg, localeText);
17784
+ if (namesJs === null)
17785
+ continue;
17786
+ }
17200
17787
  const receiverText = propAccess.expression.getText(sourceFile);
17201
17788
  const matchText = call.getText(sourceFile);
17202
- result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
17789
+ result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
17203
17790
  }
17204
17791
  return restore(result);
17205
17792
  }
@@ -22002,15 +22589,16 @@ function isOmitBranch(node) {
22002
22589
  }
22003
22590
  // src/format-date-lowering.ts
22004
22591
  var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
22592
+ var EMPTY_NAMES = { kind: "array-literal", elements: [], raw: "[]" };
22005
22593
  function matchFormatDateCall(callee, args, locals) {
22006
22594
  if (callee.kind !== "identifier" || !locals.has(callee.name))
22007
22595
  return null;
22008
- if (args.length < 2 || args.length > 3)
22596
+ if (args.length < 2 || args.length > 4)
22009
22597
  return null;
22010
22598
  return {
22011
22599
  kind: "helper-call",
22012
22600
  helper: "format_date",
22013
- args: [args[0], args[1], args[2] ?? UTC_LITERAL]
22601
+ args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES]
22014
22602
  };
22015
22603
  }
22016
22604
  var formatDatePlugin = {