@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/analyzer.d.ts.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/format-date-lowering.d.ts +6 -3
- package/dist/format-date-lowering.d.ts.map +1 -1
- package/dist/index.js +696 -108
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/to-locale-date-lowering.d.ts +62 -23
- package/dist/to-locale-date-lowering.d.ts.map +1 -1
- package/dist/types.d.ts +47 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/format-date-lowering.test.ts +22 -6
- package/src/__tests__/reactive-factory-cross-file.test.ts +845 -0
- package/src/__tests__/reactive-factory-inlining.test.ts +527 -1
- package/src/__tests__/reactive-factory-rename-fidelity.test.ts +318 -0
- package/src/__tests__/to-locale-date-lowering.test.ts +203 -2
- package/src/analyzer.ts +730 -131
- package/src/errors.ts +10 -0
- package/src/format-date-lowering.ts +9 -5
- package/src/ir-to-client-js/emit-reactive.ts +14 -4
- package/src/jsx-to-ir.ts +15 -4
- package/src/to-locale-date-lowering.ts +437 -49
- package/src/types.ts +49 -3
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
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
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(
|
|
7906
|
+
content = fs.readFileSync(abs, "utf8");
|
|
7830
7907
|
} catch {
|
|
7831
|
-
|
|
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
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
7856
|
-
|
|
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
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7953
|
+
reexports.set(el.name.text, {
|
|
7954
|
+
source: stmt.moduleSpecifier.text,
|
|
7955
|
+
innerName: (el.propertyName ?? el.name).text
|
|
7956
|
+
});
|
|
7863
7957
|
}
|
|
7864
|
-
}
|
|
7865
|
-
|
|
7866
|
-
|
|
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
|
|
7871
|
-
if (
|
|
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
|
-
|
|
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
|
|
7889
|
-
if (
|
|
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: `'${
|
|
8029
|
+
detail: `'${capture.captured.join("', '")}'`,
|
|
7893
8030
|
loc: det.info.loc
|
|
7894
8031
|
});
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
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
|
|
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
|
-
|
|
8099
|
+
local.add(n);
|
|
7915
8100
|
continue;
|
|
7916
8101
|
}
|
|
7917
8102
|
if ((ts8.isFunctionDeclaration(stmt) || ts8.isClassDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name) {
|
|
7918
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8125
|
+
local.add(namedBindings.name.text);
|
|
7941
8126
|
}
|
|
7942
8127
|
}
|
|
7943
8128
|
}
|
|
7944
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
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
|
-
|
|
8167
|
-
const
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
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
|
|
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
|
|
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
|
|
8852
|
-
|
|
8853
|
-
|
|
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
|
-
|
|
8857
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
8924
|
-
|
|
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
|
-
|
|
8935
|
-
|
|
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
|
-
|
|
8943
|
-
|
|
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
|
|
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}, ${
|
|
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
|
|
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}, ${
|
|
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 >
|
|
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 = {
|