@barefootjs/jsx 0.21.4 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/env-signal.d.ts +8 -0
- package/dist/adapters/env-signal.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts +16 -5
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts +10 -4
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/builtin-lowering-plugins.d.ts.map +1 -1
- package/dist/date-lowering.d.ts +16 -0
- package/dist/date-lowering.d.ts.map +1 -1
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/format-date-lowering.d.ts +30 -0
- package/dist/format-date-lowering.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1124 -74
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +1 -0
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/to-locale-date-lowering.d.ts +111 -0
- package/dist/to-locale-date-lowering.d.ts.map +1 -0
- package/dist/types.d.ts +47 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/format-date-lowering.test.ts +125 -0
- package/src/__tests__/reactive-factory-cross-file.test.ts +502 -0
- package/src/__tests__/reactive-factory-inlining.test.ts +293 -4
- package/src/__tests__/to-locale-date-lowering.test.ts +382 -0
- package/src/adapters/env-signal.ts +26 -3
- package/src/analyzer-context.ts +19 -4
- package/src/analyzer.ts +1012 -93
- package/src/builtin-lowering-plugins.ts +8 -1
- package/src/date-lowering.ts +1 -1
- package/src/errors.ts +19 -0
- package/src/format-date-lowering.ts +55 -0
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/emit-reactive.ts +90 -1
- package/src/ir-to-client-js/html-template.ts +36 -2
- package/src/ir-to-client-js/imports.ts +4 -0
- package/src/jsx-to-ir.ts +90 -1
- package/src/rich-type-refusal.ts +9 -1
- package/src/to-locale-date-lowering.ts +563 -0
- package/src/types.ts +49 -1
package/dist/index.js
CHANGED
|
@@ -3141,18 +3141,39 @@ function createTemplateAwareStringProtector() {
|
|
|
3141
3141
|
stash.push(s);
|
|
3142
3142
|
return `__STRLIT_${i}__`;
|
|
3143
3143
|
};
|
|
3144
|
+
const STRING_LIT_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g;
|
|
3144
3145
|
const protect = (s) => {
|
|
3145
3146
|
s = s.replace(/`([^`]*)`/g, (_full, inner) => {
|
|
3146
3147
|
const parts = splitTemplateInterpolations(inner);
|
|
3147
3148
|
return "`" + parts.map((p) => p.startsWith("${") ? p : save(p)).join("") + "`";
|
|
3148
3149
|
});
|
|
3149
|
-
s = s.replace(
|
|
3150
|
+
s = s.replace(STRING_LIT_RE, (m) => save(m));
|
|
3150
3151
|
return s;
|
|
3151
3152
|
};
|
|
3152
3153
|
const restore = (s) => {
|
|
3153
3154
|
return s.replace(/__STRLIT_(\d+)__/g, (_, i) => stash[Number(i)]);
|
|
3154
3155
|
};
|
|
3155
|
-
|
|
3156
|
+
const replaceProtectedCall = (haystack, needle, replacement) => {
|
|
3157
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3158
|
+
const litValues = [];
|
|
3159
|
+
let pattern = "";
|
|
3160
|
+
let last = 0;
|
|
3161
|
+
for (const m of needle.matchAll(STRING_LIT_RE)) {
|
|
3162
|
+
pattern += escape(needle.slice(last, m.index));
|
|
3163
|
+
pattern += "__STRLIT_(\\d+)__";
|
|
3164
|
+
litValues.push(m[0]);
|
|
3165
|
+
last = m.index + m[0].length;
|
|
3166
|
+
}
|
|
3167
|
+
pattern += escape(needle.slice(last));
|
|
3168
|
+
for (const m of haystack.matchAll(new RegExp(pattern, "g"))) {
|
|
3169
|
+
const verified = m.slice(1).every((idx, i) => stash[Number(idx)] === litValues[i]);
|
|
3170
|
+
if (!verified)
|
|
3171
|
+
continue;
|
|
3172
|
+
return haystack.slice(0, m.index) + replacement() + haystack.slice(m.index + m[0].length);
|
|
3173
|
+
}
|
|
3174
|
+
return haystack;
|
|
3175
|
+
};
|
|
3176
|
+
return { protect, restore, replaceProtectedCall };
|
|
3156
3177
|
}
|
|
3157
3178
|
var VOID_ELEMENTS = new Set([
|
|
3158
3179
|
"area",
|
|
@@ -4709,6 +4730,9 @@ function createAnalyzerContext(sourceFile, filePath) {
|
|
|
4709
4730
|
jsxFunctions: new Map,
|
|
4710
4731
|
jsxMultiReturnFunctions: new Map,
|
|
4711
4732
|
reactiveFactories: new Map,
|
|
4733
|
+
declinedReactiveFactories: new Map,
|
|
4734
|
+
reactiveShapedHelpers: new Set,
|
|
4735
|
+
cleanFactoryImports: new Set,
|
|
4712
4736
|
signalTupleRefs: new Map,
|
|
4713
4737
|
propsType: null,
|
|
4714
4738
|
propsParams: [],
|
|
@@ -5217,7 +5241,10 @@ var ErrorCodes = {
|
|
|
5217
5241
|
STAGE_INIT_LOCAL_IN_TEMPLATE: "BF061",
|
|
5218
5242
|
STAGE_AWAIT_IN_TEMPLATE: "BF062",
|
|
5219
5243
|
INLINE_JSX_CALLBACK_CAPTURE: "BF080",
|
|
5220
|
-
UNRECOGNIZED_REACTIVE_FACTORY: "BF110"
|
|
5244
|
+
UNRECOGNIZED_REACTIVE_FACTORY: "BF110",
|
|
5245
|
+
REACTIVE_FACTORY_RENAME_UNSUPPORTED: "BF111",
|
|
5246
|
+
REACTIVE_FACTORY_MODULE_CAPTURE: "BF112",
|
|
5247
|
+
REACTIVE_FACTORY_IMPORT_COLLISION: "BF113"
|
|
5221
5248
|
};
|
|
5222
5249
|
var errorMessages = {
|
|
5223
5250
|
[ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
|
|
@@ -5241,7 +5268,10 @@ var errorMessages = {
|
|
|
5241
5268
|
[ErrorCodes.STAGE_INIT_LOCAL_IN_TEMPLATE]: "Init-scope local referenced from template scope. The template lambda runs at module scope (via render() / renderChild()) and cannot reach init-body locals. Wrap the JSX expression in /* @client */, or lift the value to a prop or module-scope const.",
|
|
5242
5269
|
[ErrorCodes.STAGE_AWAIT_IN_TEMPLATE]: "AwaitExpression in template scope. The generated template and init functions are synchronous — a bare `await` produces a SyntaxError at parse time. Move the await into the component body (before the return) or into an onMount/effect callback, and pass the resolved value to JSX.",
|
|
5243
5270
|
[ErrorCodes.INLINE_JSX_CALLBACK_CAPTURE]: "Inline JSX-returning arrow function captures a non-module identifier. Extract the callback into a top-level 'use client' component (e.g. `function MyNode(n) { return <div/> }` then `renderNode={MyNode}`) or pass captured values via component props.",
|
|
5244
|
-
[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
|
+
[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.",
|
|
5272
|
+
[ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED]: "Reactive factory object return/destructure must use shorthand properties only. " + "Property renames (`{ lists: myLists }`), defaults, and rest elements are not " + "supported — destructure with the factory's own property names.",
|
|
5273
|
+
[ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE]: "Imported reactive factory references bindings from its own module scope, so its " + "body cannot be inlined into the component file. Move those helpers into the " + "component file, pass them to the factory as parameters, or define the factory " + "in the component file.",
|
|
5274
|
+
[ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION]: "Inlining an imported reactive factory requires re-importing one of its helper " + "imports into this file, but that name is already bound here to something else. " + "Rename the conflicting binding in this file, or alias the import in the factory's own file."
|
|
5245
5275
|
};
|
|
5246
5276
|
function createError(code, loc, options) {
|
|
5247
5277
|
if (code === undefined || !(code in errorMessages)) {
|
|
@@ -5567,6 +5597,10 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
|
|
|
5567
5597
|
}
|
|
5568
5598
|
const ctx = createAnalyzerContext(sourceFile, filePath);
|
|
5569
5599
|
ctx.checker = checker;
|
|
5600
|
+
ctx.reactiveFactories = prescan.factories;
|
|
5601
|
+
ctx.declinedReactiveFactories = prescan.declined;
|
|
5602
|
+
ctx.reactiveShapedHelpers = prescan.reactiveShaped;
|
|
5603
|
+
ctx.cleanFactoryImports = prescan.cleanFactoryImports;
|
|
5570
5604
|
const brandImportLoc = findBrandPackageImportLoc(sourceFile, filePath);
|
|
5571
5605
|
if (!hadSharedProgram && brandImportLoc !== null) {
|
|
5572
5606
|
ctx.errors.push(createError(ErrorCodes.SHARED_PROGRAM_REQUIRED, brandImportLoc));
|
|
@@ -6472,6 +6506,7 @@ var CLIENT_EXPORTS = new Set([
|
|
|
6472
6506
|
"cleanupPortalPlaceholder",
|
|
6473
6507
|
"createSearchParams",
|
|
6474
6508
|
"queryHref",
|
|
6509
|
+
"formatDate",
|
|
6475
6510
|
"Async",
|
|
6476
6511
|
"Region"
|
|
6477
6512
|
]);
|
|
@@ -7721,27 +7756,368 @@ var REACTIVE_PRIMITIVES = new Set([
|
|
|
7721
7756
|
function prescanReactiveFactoriesInSource(source, filePath) {
|
|
7722
7757
|
const sourceFile = ts8.createSourceFile(filePath + ".prescan", source, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
|
|
7723
7758
|
const factories = new Map;
|
|
7759
|
+
const declined = new Map;
|
|
7760
|
+
const reactiveShaped = new Set;
|
|
7761
|
+
const cleanFactoryImports = new Set;
|
|
7724
7762
|
function visitTop(node) {
|
|
7725
7763
|
if (ts8.isFunctionDeclaration(node) && node.name && node.body) {
|
|
7726
|
-
const
|
|
7727
|
-
if (
|
|
7728
|
-
|
|
7764
|
+
const det = detectReactiveFactory(node, sourceFile, filePath);
|
|
7765
|
+
if (!det)
|
|
7766
|
+
return;
|
|
7767
|
+
switch (det.kind) {
|
|
7768
|
+
case "factory":
|
|
7769
|
+
factories.set(node.name.text, det.info);
|
|
7770
|
+
break;
|
|
7771
|
+
case "declined":
|
|
7772
|
+
declined.set(node.name.text, det.declined);
|
|
7773
|
+
break;
|
|
7774
|
+
case "reactive-shaped":
|
|
7775
|
+
reactiveShaped.add(node.name.text);
|
|
7776
|
+
break;
|
|
7777
|
+
}
|
|
7729
7778
|
}
|
|
7730
7779
|
}
|
|
7731
7780
|
ts8.forEachChild(sourceFile, visitTop);
|
|
7732
|
-
|
|
7781
|
+
const result = { factories, declined, reactiveShaped, cleanFactoryImports, sourceFile };
|
|
7782
|
+
prescanImportedReactiveFactories(sourceFile, filePath, result);
|
|
7783
|
+
return result;
|
|
7784
|
+
}
|
|
7785
|
+
function toComponentRelativeSpecifier(resolvedAbs, componentFilePath) {
|
|
7786
|
+
let rel = path_default.relative(path_default.dirname(componentFilePath), resolvedAbs).split(path_default.sep).join("/");
|
|
7787
|
+
rel = rel.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
7788
|
+
if (rel === "")
|
|
7789
|
+
rel = ".";
|
|
7790
|
+
if (!rel.startsWith("."))
|
|
7791
|
+
rel = "./" + rel;
|
|
7792
|
+
return rel;
|
|
7793
|
+
}
|
|
7794
|
+
function buildEntryImportIndex(sf, filePath) {
|
|
7795
|
+
const index = new Map;
|
|
7796
|
+
for (const stmt of sf.statements) {
|
|
7797
|
+
if (!ts8.isImportDeclaration(stmt))
|
|
7798
|
+
continue;
|
|
7799
|
+
if (!ts8.isStringLiteral(stmt.moduleSpecifier))
|
|
7800
|
+
continue;
|
|
7801
|
+
if (stmt.importClause?.isTypeOnly)
|
|
7802
|
+
continue;
|
|
7803
|
+
const src = stmt.moduleSpecifier.text;
|
|
7804
|
+
const targetKey = src.startsWith("./") || src.startsWith("../") ? resolveRelativeImportToFile(src, filePath) ?? "unresolved:" + src : src;
|
|
7805
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
7806
|
+
if (namedBindings && ts8.isNamedImports(namedBindings)) {
|
|
7807
|
+
for (const el of namedBindings.elements) {
|
|
7808
|
+
if (el.isTypeOnly)
|
|
7809
|
+
continue;
|
|
7810
|
+
index.set(el.name.text, { targetKey, exportedName: (el.propertyName ?? el.name).text });
|
|
7811
|
+
}
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
return index;
|
|
7815
|
+
}
|
|
7816
|
+
function collectEntryBindingNames(sf) {
|
|
7817
|
+
const names = new Set;
|
|
7818
|
+
function visit2(node) {
|
|
7819
|
+
if (ts8.isImportDeclaration(node) && node.importClause) {
|
|
7820
|
+
if (node.importClause.name)
|
|
7821
|
+
names.add(node.importClause.name.text);
|
|
7822
|
+
const namedBindings = node.importClause.namedBindings;
|
|
7823
|
+
if (namedBindings && ts8.isNamedImports(namedBindings)) {
|
|
7824
|
+
for (const el of namedBindings.elements)
|
|
7825
|
+
names.add(el.name.text);
|
|
7826
|
+
}
|
|
7827
|
+
if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
|
|
7828
|
+
names.add(namedBindings.name.text);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
if (ts8.isVariableDeclaration(node)) {
|
|
7832
|
+
const out = [];
|
|
7833
|
+
addBindingNames(node.name, out);
|
|
7834
|
+
for (const n of out)
|
|
7835
|
+
names.add(n);
|
|
7836
|
+
}
|
|
7837
|
+
if ((ts8.isFunctionDeclaration(node) || ts8.isClassDeclaration(node) || ts8.isEnumDeclaration(node)) && node.name) {
|
|
7838
|
+
names.add(node.name.text);
|
|
7839
|
+
}
|
|
7840
|
+
if (ts8.isFunctionLike(node)) {
|
|
7841
|
+
for (const p of node.parameters) {
|
|
7842
|
+
const out = [];
|
|
7843
|
+
addBindingNames(p.name, out);
|
|
7844
|
+
for (const n of out)
|
|
7845
|
+
names.add(n);
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
ts8.forEachChild(node, visit2);
|
|
7849
|
+
}
|
|
7850
|
+
visit2(sf);
|
|
7851
|
+
return names;
|
|
7852
|
+
}
|
|
7853
|
+
function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
|
|
7854
|
+
const candidateCallees = new Set;
|
|
7855
|
+
function collectCandidates(node) {
|
|
7856
|
+
if (ts8.isVariableDeclaration(node) && (ts8.isArrayBindingPattern(node.name) || ts8.isObjectBindingPattern(node.name)) && node.initializer && ts8.isCallExpression(node.initializer) && ts8.isIdentifier(node.initializer.expression)) {
|
|
7857
|
+
candidateCallees.add(node.initializer.expression.text);
|
|
7858
|
+
}
|
|
7859
|
+
ts8.forEachChild(node, collectCandidates);
|
|
7860
|
+
}
|
|
7861
|
+
collectCandidates(entrySourceFile);
|
|
7862
|
+
if (candidateCallees.size === 0)
|
|
7863
|
+
return;
|
|
7864
|
+
const importsToCheck = [];
|
|
7865
|
+
for (const stmt of entrySourceFile.statements) {
|
|
7866
|
+
if (!ts8.isImportDeclaration(stmt))
|
|
7867
|
+
continue;
|
|
7868
|
+
if (!ts8.isStringLiteral(stmt.moduleSpecifier))
|
|
7869
|
+
continue;
|
|
7870
|
+
const src = stmt.moduleSpecifier.text;
|
|
7871
|
+
if (!src.startsWith("./") && !src.startsWith("../"))
|
|
7872
|
+
continue;
|
|
7873
|
+
if (stmt.importClause?.isTypeOnly)
|
|
7874
|
+
continue;
|
|
7875
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
7876
|
+
if (!namedBindings || !ts8.isNamedImports(namedBindings))
|
|
7877
|
+
continue;
|
|
7878
|
+
const specs = [];
|
|
7879
|
+
for (const el of namedBindings.elements) {
|
|
7880
|
+
if (el.isTypeOnly)
|
|
7881
|
+
continue;
|
|
7882
|
+
const local = el.name.text;
|
|
7883
|
+
if (!candidateCallees.has(local))
|
|
7884
|
+
continue;
|
|
7885
|
+
specs.push({ exported: (el.propertyName ?? el.name).text, local });
|
|
7886
|
+
}
|
|
7887
|
+
if (specs.length === 0)
|
|
7888
|
+
continue;
|
|
7889
|
+
importsToCheck.push({ src, specs });
|
|
7890
|
+
}
|
|
7891
|
+
if (importsToCheck.length === 0)
|
|
7892
|
+
return;
|
|
7893
|
+
const entryBindingNames = collectEntryBindingNames(entrySourceFile);
|
|
7894
|
+
const entryImportIndex = buildEntryImportIndex(entrySourceFile, filePath);
|
|
7895
|
+
const plannedInjections = new Map;
|
|
7896
|
+
for (const { src, specs } of importsToCheck) {
|
|
7897
|
+
const resolved = resolveRelativeImportToFile(src, filePath);
|
|
7898
|
+
if (!resolved)
|
|
7899
|
+
continue;
|
|
7900
|
+
let content;
|
|
7901
|
+
try {
|
|
7902
|
+
content = fs.readFileSync(resolved, "utf8");
|
|
7903
|
+
} catch {
|
|
7904
|
+
continue;
|
|
7905
|
+
}
|
|
7906
|
+
const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
|
|
7907
|
+
const hasAnyPrimitiveText = [...REACTIVE_PRIMITIVES].some((p) => content.includes(p));
|
|
7908
|
+
if (!hasAnyPrimitiveText) {
|
|
7909
|
+
for (const spec of specs) {
|
|
7910
|
+
if (!alreadyKnown(spec.local))
|
|
7911
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7912
|
+
}
|
|
7913
|
+
continue;
|
|
7914
|
+
}
|
|
7915
|
+
const helperSf = ts8.createSourceFile(resolved + ".prescan", content, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
|
|
7916
|
+
const localFns = new Map;
|
|
7917
|
+
const exportedFns = new Map;
|
|
7918
|
+
for (const stmt of helperSf.statements) {
|
|
7919
|
+
if (ts8.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
|
|
7920
|
+
localFns.set(stmt.name.text, stmt);
|
|
7921
|
+
const hasExportModifier = stmt.modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword) ?? false;
|
|
7922
|
+
const hasDefaultModifier = stmt.modifiers?.some((m) => m.kind === ts8.SyntaxKind.DefaultKeyword) ?? false;
|
|
7923
|
+
if (hasExportModifier && !hasDefaultModifier) {
|
|
7924
|
+
exportedFns.set(stmt.name.text, stmt);
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
for (const stmt of helperSf.statements) {
|
|
7929
|
+
if (ts8.isExportDeclaration(stmt) && stmt.exportClause && ts8.isNamedExports(stmt.exportClause) && !stmt.moduleSpecifier && !stmt.isTypeOnly) {
|
|
7930
|
+
for (const el of stmt.exportClause.elements) {
|
|
7931
|
+
if (el.isTypeOnly)
|
|
7932
|
+
continue;
|
|
7933
|
+
const fn = localFns.get((el.propertyName ?? el.name).text);
|
|
7934
|
+
if (fn)
|
|
7935
|
+
exportedFns.set(el.name.text, fn);
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
const moduleBindings = collectHelperModuleValueBindings(helperSf);
|
|
7940
|
+
for (const spec of specs) {
|
|
7941
|
+
if (alreadyKnown(spec.local))
|
|
7942
|
+
continue;
|
|
7943
|
+
const fn = exportedFns.get(spec.exported);
|
|
7944
|
+
if (!fn) {
|
|
7945
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7946
|
+
continue;
|
|
7947
|
+
}
|
|
7948
|
+
const det = detectReactiveFactory(fn, helperSf, resolved);
|
|
7949
|
+
if (!det) {
|
|
7950
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7951
|
+
continue;
|
|
7952
|
+
}
|
|
7953
|
+
switch (det.kind) {
|
|
7954
|
+
case "reactive-shaped":
|
|
7955
|
+
result.reactiveShaped.add(spec.local);
|
|
7956
|
+
break;
|
|
7957
|
+
case "declined":
|
|
7958
|
+
result.declined.set(spec.local, det.declined);
|
|
7959
|
+
break;
|
|
7960
|
+
case "factory": {
|
|
7961
|
+
const capture = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
|
|
7962
|
+
if (capture.captured.length > 0) {
|
|
7963
|
+
result.declined.set(spec.local, {
|
|
7964
|
+
code: "BF112",
|
|
7965
|
+
detail: `'${capture.captured.join("', '")}'`,
|
|
7966
|
+
loc: det.info.loc
|
|
7967
|
+
});
|
|
7968
|
+
break;
|
|
7969
|
+
}
|
|
7970
|
+
const required = [];
|
|
7971
|
+
const pending = [];
|
|
7972
|
+
let declinedEntry = null;
|
|
7973
|
+
for (const ref of capture.importedRefs) {
|
|
7974
|
+
let specifier;
|
|
7975
|
+
let targetKey;
|
|
7976
|
+
if (ref.source.startsWith("./") || ref.source.startsWith("../")) {
|
|
7977
|
+
const abs = resolveRelativeImportToFile(ref.source, resolved);
|
|
7978
|
+
if (!abs) {
|
|
7979
|
+
declinedEntry = {
|
|
7980
|
+
code: "BF112",
|
|
7981
|
+
detail: `'${ref.localName}' (import '${ref.source}' did not resolve from the helper file)`,
|
|
7982
|
+
loc: det.info.loc
|
|
7983
|
+
};
|
|
7984
|
+
break;
|
|
7985
|
+
}
|
|
7986
|
+
specifier = toComponentRelativeSpecifier(abs, filePath);
|
|
7987
|
+
targetKey = abs;
|
|
7988
|
+
} else {
|
|
7989
|
+
specifier = ref.source;
|
|
7990
|
+
targetKey = ref.source;
|
|
7991
|
+
}
|
|
7992
|
+
const existing = entryImportIndex.get(ref.localName);
|
|
7993
|
+
if (existing && existing.targetKey === targetKey && existing.exportedName === ref.exportedName) {
|
|
7994
|
+
continue;
|
|
7995
|
+
}
|
|
7996
|
+
const planned = plannedInjections.get(ref.localName);
|
|
7997
|
+
const collides = existing !== undefined || planned !== undefined && (planned.targetKey !== targetKey || planned.exportedName !== ref.exportedName) || planned === undefined && entryBindingNames.has(ref.localName);
|
|
7998
|
+
if (collides) {
|
|
7999
|
+
declinedEntry = {
|
|
8000
|
+
code: "BF113",
|
|
8001
|
+
detail: `'${ref.localName}' from '${specifier}'`,
|
|
8002
|
+
loc: det.info.loc
|
|
8003
|
+
};
|
|
8004
|
+
break;
|
|
8005
|
+
}
|
|
8006
|
+
pending.push([ref.localName, { targetKey, exportedName: ref.exportedName }]);
|
|
8007
|
+
required.push({ localName: ref.localName, exportedName: ref.exportedName, specifier });
|
|
8008
|
+
}
|
|
8009
|
+
if (declinedEntry) {
|
|
8010
|
+
result.declined.set(spec.local, declinedEntry);
|
|
8011
|
+
break;
|
|
8012
|
+
}
|
|
8013
|
+
for (const [name, id] of pending)
|
|
8014
|
+
plannedInjections.set(name, id);
|
|
8015
|
+
det.info.sourceFilePath = resolved;
|
|
8016
|
+
if (required.length > 0)
|
|
8017
|
+
det.info.requiredImports = required;
|
|
8018
|
+
result.factories.set(spec.local, det.info);
|
|
8019
|
+
break;
|
|
8020
|
+
}
|
|
8021
|
+
}
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
}
|
|
8025
|
+
function collectHelperModuleValueBindings(sf) {
|
|
8026
|
+
const local = new Set;
|
|
8027
|
+
const imported = new Map;
|
|
8028
|
+
for (const stmt of sf.statements) {
|
|
8029
|
+
if (ts8.isVariableStatement(stmt)) {
|
|
8030
|
+
const out = [];
|
|
8031
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
8032
|
+
addBindingNames(decl.name, out);
|
|
8033
|
+
}
|
|
8034
|
+
for (const n of out)
|
|
8035
|
+
local.add(n);
|
|
8036
|
+
continue;
|
|
8037
|
+
}
|
|
8038
|
+
if ((ts8.isFunctionDeclaration(stmt) || ts8.isClassDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name) {
|
|
8039
|
+
local.add(stmt.name.text);
|
|
8040
|
+
continue;
|
|
8041
|
+
}
|
|
8042
|
+
if (ts8.isImportDeclaration(stmt)) {
|
|
8043
|
+
if (stmt.importClause?.isTypeOnly)
|
|
8044
|
+
continue;
|
|
8045
|
+
if (!ts8.isStringLiteral(stmt.moduleSpecifier))
|
|
8046
|
+
continue;
|
|
8047
|
+
const src = stmt.moduleSpecifier.text;
|
|
8048
|
+
if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
|
|
8049
|
+
continue;
|
|
8050
|
+
if (stmt.importClause?.name)
|
|
8051
|
+
local.add(stmt.importClause.name.text);
|
|
8052
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
8053
|
+
if (namedBindings && ts8.isNamedImports(namedBindings)) {
|
|
8054
|
+
for (const el of namedBindings.elements) {
|
|
8055
|
+
if (el.isTypeOnly)
|
|
8056
|
+
continue;
|
|
8057
|
+
imported.set(el.name.text, { source: src, exportedName: (el.propertyName ?? el.name).text });
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
|
|
8061
|
+
local.add(namedBindings.name.text);
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
return { local, imported };
|
|
8066
|
+
}
|
|
8067
|
+
function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
|
|
8068
|
+
if (!fn.body)
|
|
8069
|
+
return { captured: [], importedRefs: [] };
|
|
8070
|
+
const free = extractFreeIdentifiersFromNode(fn.body);
|
|
8071
|
+
const exclude = new Set(info.params);
|
|
8072
|
+
for (const b of info.localBindings)
|
|
8073
|
+
exclude.add(b);
|
|
8074
|
+
for (const r of info.returnTupleIdentifiers)
|
|
8075
|
+
exclude.add(r);
|
|
8076
|
+
for (const p of REACTIVE_PRIMITIVES)
|
|
8077
|
+
exclude.add(p);
|
|
8078
|
+
exclude.add(selfName);
|
|
8079
|
+
const captured = [];
|
|
8080
|
+
const importedRefs = [];
|
|
8081
|
+
for (const id of free) {
|
|
8082
|
+
if (exclude.has(id))
|
|
8083
|
+
continue;
|
|
8084
|
+
if (moduleBindings.local.has(id)) {
|
|
8085
|
+
captured.push(id);
|
|
8086
|
+
continue;
|
|
8087
|
+
}
|
|
8088
|
+
const imp = moduleBindings.imported.get(id);
|
|
8089
|
+
if (imp)
|
|
8090
|
+
importedRefs.push({ localName: id, source: imp.source, exportedName: imp.exportedName });
|
|
8091
|
+
}
|
|
8092
|
+
captured.sort();
|
|
8093
|
+
importedRefs.sort((a, b) => a.localName < b.localName ? -1 : 1);
|
|
8094
|
+
return { captured, importedRefs };
|
|
7733
8095
|
}
|
|
7734
8096
|
function detectReactiveFactory(node, sourceFile, filePath) {
|
|
7735
8097
|
if (!node.body || !node.name)
|
|
7736
8098
|
return null;
|
|
7737
|
-
let
|
|
8099
|
+
let hasReactiveCall = false;
|
|
8100
|
+
function checkForReactive(n) {
|
|
8101
|
+
if (hasReactiveCall)
|
|
8102
|
+
return;
|
|
8103
|
+
if (ts8.isCallExpression(n) && ts8.isIdentifier(n.expression) && REACTIVE_PRIMITIVES.has(n.expression.text)) {
|
|
8104
|
+
hasReactiveCall = true;
|
|
8105
|
+
return;
|
|
8106
|
+
}
|
|
8107
|
+
ts8.forEachChild(n, checkForReactive);
|
|
8108
|
+
}
|
|
8109
|
+
checkForReactive(node.body);
|
|
8110
|
+
if (!hasReactiveCall)
|
|
8111
|
+
return null;
|
|
8112
|
+
const loc = getSourceLocation(node, sourceFile, filePath);
|
|
8113
|
+
let returnExpr = null;
|
|
7738
8114
|
let returnCount = 0;
|
|
7739
8115
|
for (const stmt of node.body.statements) {
|
|
7740
8116
|
if (!ts8.isReturnStatement(stmt))
|
|
7741
8117
|
continue;
|
|
7742
8118
|
returnCount++;
|
|
7743
8119
|
if (!stmt.expression)
|
|
7744
|
-
return
|
|
8120
|
+
return { kind: "reactive-shaped" };
|
|
7745
8121
|
let expr = stmt.expression;
|
|
7746
8122
|
while (ts8.isParenthesizedExpression(expr))
|
|
7747
8123
|
expr = expr.expression;
|
|
@@ -7749,33 +8125,42 @@ function detectReactiveFactory(node, sourceFile, filePath) {
|
|
|
7749
8125
|
expr = expr.expression;
|
|
7750
8126
|
if (ts8.isTypeAssertionExpression(expr))
|
|
7751
8127
|
expr = expr.expression;
|
|
7752
|
-
|
|
7753
|
-
return null;
|
|
7754
|
-
tupleReturn = expr;
|
|
8128
|
+
returnExpr = expr;
|
|
7755
8129
|
}
|
|
7756
|
-
if (returnCount !== 1 || !
|
|
7757
|
-
return
|
|
8130
|
+
if (returnCount !== 1 || !returnExpr)
|
|
8131
|
+
return { kind: "reactive-shaped" };
|
|
7758
8132
|
const returnTupleIdentifiers = [];
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
8133
|
+
let returnKind;
|
|
8134
|
+
if (ts8.isArrayLiteralExpression(returnExpr)) {
|
|
8135
|
+
returnKind = "tuple";
|
|
8136
|
+
for (const el of returnExpr.elements) {
|
|
8137
|
+
if (!ts8.isIdentifier(el))
|
|
8138
|
+
return { kind: "reactive-shaped" };
|
|
8139
|
+
returnTupleIdentifiers.push(el.text);
|
|
8140
|
+
}
|
|
8141
|
+
if (returnTupleIdentifiers.length === 0)
|
|
8142
|
+
return { kind: "reactive-shaped" };
|
|
8143
|
+
} else if (ts8.isObjectLiteralExpression(returnExpr)) {
|
|
8144
|
+
returnKind = "object";
|
|
8145
|
+
const hasNonShorthand = returnExpr.properties.some((p) => !ts8.isShorthandPropertyAssignment(p));
|
|
8146
|
+
if (hasNonShorthand) {
|
|
8147
|
+
return {
|
|
8148
|
+
kind: "declined",
|
|
8149
|
+
declined: {
|
|
8150
|
+
code: "BF111",
|
|
8151
|
+
detail: `return object of '${node.name.text}' uses non-shorthand properties`,
|
|
8152
|
+
loc
|
|
8153
|
+
}
|
|
8154
|
+
};
|
|
7773
8155
|
}
|
|
7774
|
-
|
|
8156
|
+
for (const p of returnExpr.properties) {
|
|
8157
|
+
returnTupleIdentifiers.push(p.name.text);
|
|
8158
|
+
}
|
|
8159
|
+
if (returnTupleIdentifiers.length === 0)
|
|
8160
|
+
return { kind: "reactive-shaped" };
|
|
8161
|
+
} else {
|
|
8162
|
+
return { kind: "reactive-shaped" };
|
|
7775
8163
|
}
|
|
7776
|
-
checkForReactive(node.body);
|
|
7777
|
-
if (!hasReactiveCall)
|
|
7778
|
-
return null;
|
|
7779
8164
|
const localBindings = [];
|
|
7780
8165
|
for (const stmt of node.body.statements) {
|
|
7781
8166
|
if (ts8.isVariableStatement(stmt)) {
|
|
@@ -7788,19 +8173,24 @@ function detectReactiveFactory(node, sourceFile, filePath) {
|
|
|
7788
8173
|
}
|
|
7789
8174
|
const bodyStatements = node.body.statements.filter((s) => !ts8.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
|
|
7790
8175
|
`);
|
|
7791
|
-
const params =
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
return
|
|
8176
|
+
const params = [];
|
|
8177
|
+
for (const p of node.parameters) {
|
|
8178
|
+
if (ts8.isIdentifier(p.name)) {
|
|
8179
|
+
params.push(p.name.text);
|
|
8180
|
+
continue;
|
|
8181
|
+
}
|
|
8182
|
+
return { kind: "reactive-shaped" };
|
|
8183
|
+
}
|
|
7798
8184
|
return {
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
8185
|
+
kind: "factory",
|
|
8186
|
+
info: {
|
|
8187
|
+
params,
|
|
8188
|
+
bodySource: bodyStatements,
|
|
8189
|
+
returnTupleIdentifiers,
|
|
8190
|
+
returnKind,
|
|
8191
|
+
localBindings,
|
|
8192
|
+
loc
|
|
8193
|
+
}
|
|
7804
8194
|
};
|
|
7805
8195
|
}
|
|
7806
8196
|
function addBindingNames(name, out) {
|
|
@@ -7825,6 +8215,7 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7825
8215
|
const { factories, sourceFile } = prescan;
|
|
7826
8216
|
const edits = [];
|
|
7827
8217
|
let callSiteIndex = 0;
|
|
8218
|
+
const inlinedFactories = new Set;
|
|
7828
8219
|
function visitStmt(node, inComponent) {
|
|
7829
8220
|
if (ts8.isVariableStatement(node) && inComponent) {
|
|
7830
8221
|
for (const decl of node.declarationList.declarations) {
|
|
@@ -7838,8 +8229,6 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7838
8229
|
});
|
|
7839
8230
|
}
|
|
7840
8231
|
function maybeRewriteDecl(stmt, decl) {
|
|
7841
|
-
if (!ts8.isArrayBindingPattern(decl.name))
|
|
7842
|
-
return;
|
|
7843
8232
|
if (!decl.initializer || !ts8.isCallExpression(decl.initializer))
|
|
7844
8233
|
return;
|
|
7845
8234
|
if (!ts8.isIdentifier(decl.initializer.expression))
|
|
@@ -7848,7 +8237,21 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7848
8237
|
const factory = factories.get(factoryName);
|
|
7849
8238
|
if (!factory)
|
|
7850
8239
|
return;
|
|
7851
|
-
|
|
8240
|
+
if (ts8.isArrayBindingPattern(decl.name)) {
|
|
8241
|
+
if (factory.returnKind !== "tuple")
|
|
8242
|
+
return;
|
|
8243
|
+
rewriteTupleDecl(stmt, decl.name, decl.initializer, factory);
|
|
8244
|
+
return;
|
|
8245
|
+
}
|
|
8246
|
+
if (ts8.isObjectBindingPattern(decl.name)) {
|
|
8247
|
+
if (factory.returnKind !== "object")
|
|
8248
|
+
return;
|
|
8249
|
+
rewriteObjectDecl(stmt, decl.name, decl.initializer, factory);
|
|
8250
|
+
return;
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
8253
|
+
function rewriteTupleDecl(stmt, pattern, call, factory) {
|
|
8254
|
+
const elements = pattern.elements;
|
|
7852
8255
|
if (elements.length !== factory.returnTupleIdentifiers.length)
|
|
7853
8256
|
return;
|
|
7854
8257
|
const callerNames = [];
|
|
@@ -7857,15 +8260,43 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7857
8260
|
return;
|
|
7858
8261
|
callerNames.push(el.name.text);
|
|
7859
8262
|
}
|
|
7860
|
-
const
|
|
8263
|
+
const excludeFromSuffixRename = new Set(factory.params);
|
|
8264
|
+
for (const r of factory.returnTupleIdentifiers)
|
|
8265
|
+
excludeFromSuffixRename.add(r);
|
|
8266
|
+
const renameReturnToCallerNames = new Map;
|
|
8267
|
+
for (let i = 0;i < factory.returnTupleIdentifiers.length; i++) {
|
|
8268
|
+
renameReturnToCallerNames.set(factory.returnTupleIdentifiers[i], callerNames[i]);
|
|
8269
|
+
}
|
|
8270
|
+
inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, renameReturnToCallerNames);
|
|
8271
|
+
}
|
|
8272
|
+
function rewriteObjectDecl(stmt, pattern, call, factory) {
|
|
8273
|
+
const destructured = new Set;
|
|
8274
|
+
for (const el of pattern.elements) {
|
|
8275
|
+
if (el.dotDotDotToken)
|
|
8276
|
+
return;
|
|
8277
|
+
if (el.propertyName)
|
|
8278
|
+
return;
|
|
8279
|
+
if (el.initializer)
|
|
8280
|
+
return;
|
|
8281
|
+
if (!ts8.isIdentifier(el.name))
|
|
8282
|
+
return;
|
|
8283
|
+
if (!factory.returnTupleIdentifiers.includes(el.name.text))
|
|
8284
|
+
return;
|
|
8285
|
+
destructured.add(el.name.text);
|
|
8286
|
+
}
|
|
8287
|
+
const excludeFromSuffixRename = new Set(factory.params);
|
|
8288
|
+
for (const d of destructured)
|
|
8289
|
+
excludeFromSuffixRename.add(d);
|
|
8290
|
+
inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, null);
|
|
8291
|
+
}
|
|
8292
|
+
function inlineFactoryCallAtSite(stmt, factory, args, excludeFromSuffixRename, renameReturnToCallerNames) {
|
|
8293
|
+
const argTexts = args.map((a) => a.getText(sourceFile));
|
|
7861
8294
|
const thisCallIndex = callSiteIndex++;
|
|
7862
8295
|
const suffix = `_bf${thisCallIndex}`;
|
|
7863
8296
|
let body = factory.bodySource;
|
|
7864
8297
|
const internalRenames = new Set(factory.localBindings);
|
|
7865
|
-
for (const
|
|
7866
|
-
internalRenames.delete(
|
|
7867
|
-
for (const r of factory.returnTupleIdentifiers)
|
|
7868
|
-
internalRenames.delete(r);
|
|
8298
|
+
for (const ex of excludeFromSuffixRename)
|
|
8299
|
+
internalRenames.delete(ex);
|
|
7869
8300
|
for (const name of internalRenames) {
|
|
7870
8301
|
body = body.replace(new RegExp(`\\b${escapeRegex(name)}\\b`, "g"), name + suffix);
|
|
7871
8302
|
}
|
|
@@ -7876,20 +8307,44 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7876
8307
|
const wrapped = atomicArg.test(a.trim()) ? a.trim() : `(${a})`;
|
|
7877
8308
|
body = body.replace(new RegExp(`\\b${escapeRegex(p)}\\b`, "g"), wrapped);
|
|
7878
8309
|
}
|
|
7879
|
-
|
|
7880
|
-
const n
|
|
7881
|
-
|
|
7882
|
-
|
|
8310
|
+
if (renameReturnToCallerNames) {
|
|
8311
|
+
for (const [n, caller] of renameReturnToCallerNames) {
|
|
8312
|
+
body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
|
|
8313
|
+
}
|
|
7883
8314
|
}
|
|
7884
8315
|
edits.push({
|
|
7885
8316
|
start: stmt.getStart(sourceFile),
|
|
7886
8317
|
end: stmt.getEnd(),
|
|
7887
8318
|
replacement: body
|
|
7888
8319
|
});
|
|
8320
|
+
inlinedFactories.add(factory);
|
|
7889
8321
|
}
|
|
7890
8322
|
visitStmt(sourceFile, false);
|
|
7891
8323
|
if (edits.length === 0)
|
|
7892
8324
|
return source;
|
|
8325
|
+
const importsBySpecifier = new Map;
|
|
8326
|
+
for (const f of inlinedFactories) {
|
|
8327
|
+
for (const r of f.requiredImports ?? []) {
|
|
8328
|
+
let names = importsBySpecifier.get(r.specifier);
|
|
8329
|
+
if (!names) {
|
|
8330
|
+
names = new Map;
|
|
8331
|
+
importsBySpecifier.set(r.specifier, names);
|
|
8332
|
+
}
|
|
8333
|
+
names.set(r.localName, r.exportedName);
|
|
8334
|
+
}
|
|
8335
|
+
}
|
|
8336
|
+
if (importsBySpecifier.size > 0) {
|
|
8337
|
+
const lines = [...importsBySpecifier].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([spec, names]) => {
|
|
8338
|
+
const specifiers = [...names].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([local, exported]) => exported === local ? local : `${exported} as ${local}`);
|
|
8339
|
+
return `import { ${specifiers.join(", ")} } from '${spec}'`;
|
|
8340
|
+
});
|
|
8341
|
+
const at = factoryImportInsertionOffset(sourceFile);
|
|
8342
|
+
edits.push({ start: at, end: at, replacement: at === 0 ? lines.join(`
|
|
8343
|
+
`) + `
|
|
8344
|
+
` : `
|
|
8345
|
+
` + lines.join(`
|
|
8346
|
+
`) });
|
|
8347
|
+
}
|
|
7893
8348
|
edits.sort((a, b) => b.start - a.start);
|
|
7894
8349
|
let out = source;
|
|
7895
8350
|
for (const e of edits) {
|
|
@@ -7897,6 +8352,20 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7897
8352
|
}
|
|
7898
8353
|
return out;
|
|
7899
8354
|
}
|
|
8355
|
+
function factoryImportInsertionOffset(sf) {
|
|
8356
|
+
let lastImportEnd = -1;
|
|
8357
|
+
let directiveEnd = -1;
|
|
8358
|
+
for (const stmt of sf.statements) {
|
|
8359
|
+
if (ts8.isImportDeclaration(stmt)) {
|
|
8360
|
+
lastImportEnd = stmt.getEnd();
|
|
8361
|
+
continue;
|
|
8362
|
+
}
|
|
8363
|
+
if (directiveEnd === -1 && ts8.isExpressionStatement(stmt) && ts8.isStringLiteral(stmt.expression) && stmt.expression.text === "use client") {
|
|
8364
|
+
directiveEnd = stmt.getEnd();
|
|
8365
|
+
}
|
|
8366
|
+
}
|
|
8367
|
+
return lastImportEnd >= 0 ? lastImportEnd : directiveEnd >= 0 ? directiveEnd : 0;
|
|
8368
|
+
}
|
|
7900
8369
|
function isPascalCaseComponentFn(node) {
|
|
7901
8370
|
if (ts8.isFunctionDeclaration(node) && node.name) {
|
|
7902
8371
|
return /^[A-Z]/.test(node.name.text);
|
|
@@ -7909,6 +8378,25 @@ function isPascalCaseComponentFn(node) {
|
|
|
7909
8378
|
function escapeRegex(s) {
|
|
7910
8379
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7911
8380
|
}
|
|
8381
|
+
function declinedFactoryMessage(callee, d) {
|
|
8382
|
+
if (d.code === "BF112") {
|
|
8383
|
+
return `Reactive factory '${callee}' references ${d.detail} from its own module ` + `scope and cannot be inlined. Move the referenced helper(s) into this file, ` + `pass them as factory arguments, or inline the factory here.`;
|
|
8384
|
+
}
|
|
8385
|
+
if (d.code === "BF113") {
|
|
8386
|
+
return `Reactive factory '${callee}' cannot be inlined: it needs ${d.detail} ` + `imported into this file, but that name is already bound here to something ` + `else. Rename the conflicting binding in this file, or alias the import in ` + `the factory's own file (import { x as y }).`;
|
|
8387
|
+
}
|
|
8388
|
+
return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
|
|
8389
|
+
}
|
|
8390
|
+
function declinedFactoryErrorCode(code) {
|
|
8391
|
+
switch (code) {
|
|
8392
|
+
case "BF112":
|
|
8393
|
+
return ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE;
|
|
8394
|
+
case "BF113":
|
|
8395
|
+
return ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION;
|
|
8396
|
+
default:
|
|
8397
|
+
return ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED;
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
7912
8400
|
function validateReactiveFactoryCalls(ctx) {
|
|
7913
8401
|
if (!ctx.componentNode)
|
|
7914
8402
|
return;
|
|
@@ -7919,26 +8407,104 @@ function validateReactiveFactoryCalls(ctx) {
|
|
|
7919
8407
|
if (!ts8.isVariableStatement(stmt))
|
|
7920
8408
|
continue;
|
|
7921
8409
|
for (const decl of stmt.declarationList.declarations) {
|
|
7922
|
-
if (!ts8.isArrayBindingPattern(decl.name))
|
|
7923
|
-
continue;
|
|
7924
8410
|
if (!decl.initializer || !ts8.isCallExpression(decl.initializer))
|
|
7925
8411
|
continue;
|
|
7926
8412
|
if (!ts8.isIdentifier(decl.initializer.expression))
|
|
7927
8413
|
continue;
|
|
7928
8414
|
const callee = decl.initializer.expression.text;
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8415
|
+
const loc = getSourceLocation(stmt, ctx.sourceFile, ctx.filePath);
|
|
8416
|
+
if (ts8.isArrayBindingPattern(decl.name)) {
|
|
8417
|
+
if (callee === "createSignal" || callee === "createMemo")
|
|
8418
|
+
continue;
|
|
8419
|
+
if (resolveEnvSignalKey(decl.initializer, ctx))
|
|
8420
|
+
continue;
|
|
8421
|
+
const declinedEntry = ctx.declinedReactiveFactories.get(callee);
|
|
8422
|
+
if (declinedEntry) {
|
|
8423
|
+
ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
|
|
8424
|
+
continue;
|
|
8425
|
+
}
|
|
8426
|
+
const objectFactory = ctx.reactiveFactories.get(callee);
|
|
8427
|
+
if (objectFactory && objectFactory.returnKind === "object") {
|
|
8428
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8429
|
+
severity: "error",
|
|
8430
|
+
message: `'${callee}' is a reactive factory that returns an object — destructure ` + `it with a matching object pattern: const { ${objectFactory.returnTupleIdentifiers.join(", ")} } = ${callee}(...)`
|
|
8431
|
+
}));
|
|
8432
|
+
continue;
|
|
8433
|
+
}
|
|
8434
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8435
|
+
severity: "error",
|
|
8436
|
+
message: `Tuple destructuring of '${callee}(...)': this helper is not a ` + `recognised reactive factory (createSignal / createMemo / a ` + `same-file helper that wraps them with a single \`return [a, b, ...]\`).`,
|
|
8437
|
+
suggestion: {
|
|
8438
|
+
message: `Inline the createSignal call at the call site, or move the ` + `helper into this file as a function that returns a tuple of ` + `identifiers at its single exit point.`
|
|
8439
|
+
}
|
|
8440
|
+
}));
|
|
7932
8441
|
continue;
|
|
7933
|
-
|
|
8442
|
+
}
|
|
8443
|
+
if (ts8.isObjectBindingPattern(decl.name)) {
|
|
8444
|
+
validateObjectFactoryDestructure(ctx, decl.name, callee, loc);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
|
|
8450
|
+
const factory = ctx.reactiveFactories.get(callee);
|
|
8451
|
+
if (factory) {
|
|
8452
|
+
if (factory.returnKind === "tuple") {
|
|
8453
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
7934
8454
|
severity: "error",
|
|
7935
|
-
message: `
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
8455
|
+
message: `'${callee}' is a reactive factory that returns a tuple — destructure ` + `it positionally: const [${factory.returnTupleIdentifiers.join(", ")}] = ${callee}(...)`
|
|
8456
|
+
}));
|
|
8457
|
+
return;
|
|
8458
|
+
}
|
|
8459
|
+
const hasUnsupportedElement = pattern.elements.some((el) => !!el.propertyName || !!el.initializer || !!el.dotDotDotToken || !ts8.isIdentifier(el.name));
|
|
8460
|
+
if (hasUnsupportedElement) {
|
|
8461
|
+
ctx.errors.push(createError(ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, {
|
|
8462
|
+
severity: "error",
|
|
8463
|
+
message: `Object destructure of reactive factory '${callee}' uses a property ` + `rename, default, or rest element; only shorthand destructuring of ` + `{ ${factory.returnTupleIdentifiers.join(", ")} } is supported.`
|
|
7939
8464
|
}));
|
|
8465
|
+
return;
|
|
8466
|
+
}
|
|
8467
|
+
const unknown = pattern.elements.map((el) => ts8.isIdentifier(el.name) ? el.name.text : "").filter((name) => name && !factory.returnTupleIdentifiers.includes(name));
|
|
8468
|
+
if (unknown.length > 0) {
|
|
8469
|
+
const label = unknown.length === 1 ? "property" : "properties";
|
|
8470
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8471
|
+
severity: "error",
|
|
8472
|
+
message: `Object destructure of reactive factory '${callee}' references ${label} ` + `'${unknown.join("', '")}' not present in its return { ${factory.returnTupleIdentifiers.join(", ")} }.`
|
|
8473
|
+
}));
|
|
8474
|
+
return;
|
|
8475
|
+
}
|
|
8476
|
+
return;
|
|
8477
|
+
}
|
|
8478
|
+
const declinedEntry = ctx.declinedReactiveFactories.get(callee);
|
|
8479
|
+
if (declinedEntry) {
|
|
8480
|
+
ctx.errors.push(createError(declinedFactoryErrorCode(declinedEntry.code), loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
|
|
8481
|
+
return;
|
|
8482
|
+
}
|
|
8483
|
+
if (ctx.reactiveShapedHelpers.has(callee)) {
|
|
8484
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8485
|
+
severity: "error",
|
|
8486
|
+
message: `Object destructure of '${callee}(...)': this helper wraps a reactive ` + `primitive but does not match the inlinable factory shape (single ` + "`return { a, b }` of shorthand identifiers at its one exit point)."
|
|
8487
|
+
}));
|
|
8488
|
+
return;
|
|
8489
|
+
}
|
|
8490
|
+
if (ctx.cleanFactoryImports.has(callee))
|
|
8491
|
+
return;
|
|
8492
|
+
let matchedImportSource = null;
|
|
8493
|
+
for (const imp of ctx.imports) {
|
|
8494
|
+
if (imp.isTypeOnly)
|
|
8495
|
+
continue;
|
|
8496
|
+
const spec = imp.specifiers.find((s) => !s.isTypeOnly && (s.alias ?? s.name) === callee);
|
|
8497
|
+
if (spec) {
|
|
8498
|
+
matchedImportSource = imp.source;
|
|
8499
|
+
break;
|
|
7940
8500
|
}
|
|
7941
8501
|
}
|
|
8502
|
+
if (matchedImportSource !== null && !matchedImportSource.startsWith("@barefootjs/") && /^(use|create)[A-Z]/.test(callee)) {
|
|
8503
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8504
|
+
severity: "error",
|
|
8505
|
+
message: `Object destructure of imported '${callee}(...)': the compiler cannot ` + `inspect this import (non-relative or unresolvable path), so if it wraps ` + `createSignal/createMemo the destructured bindings will not be reactive. Move ` + `the helper to a relative-imported file or inline its body.`
|
|
8506
|
+
}));
|
|
8507
|
+
}
|
|
7942
8508
|
}
|
|
7943
8509
|
|
|
7944
8510
|
// src/jsx-to-ir.ts
|
|
@@ -8460,6 +9026,330 @@ function resolveFreeRefs(node, env) {
|
|
|
8460
9026
|
return resolveFreeRefsInternal(node, env, new Set);
|
|
8461
9027
|
}
|
|
8462
9028
|
|
|
9029
|
+
// src/to-locale-date-lowering.ts
|
|
9030
|
+
var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
|
|
9031
|
+
var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
|
|
9032
|
+
var formatCache = new Map;
|
|
9033
|
+
var namesCache = new Map;
|
|
9034
|
+
function deriveMonthNames(locale, ctx) {
|
|
9035
|
+
return deriveNamesCached(`${locale}|m|${ctx}`, () => {
|
|
9036
|
+
const months = (width) => Array.from({ length: 12 }, (_, m) => probePart(locale, ctx === "formatting" ? { month: width, day: "numeric" } : { month: width }, Date.UTC(2001, m, 15), "month"));
|
|
9037
|
+
return [...months("long"), ...months("short")];
|
|
9038
|
+
});
|
|
9039
|
+
}
|
|
9040
|
+
function deriveWeekdayNames(locale, ctx) {
|
|
9041
|
+
return deriveNamesCached(`${locale}|w|${ctx}`, () => {
|
|
9042
|
+
const weekdays = (width) => Array.from({ length: 7 }, (_, d) => probePart(locale, ctx === "formatting" ? { weekday: width, month: "numeric", day: "numeric" } : { weekday: width }, Date.UTC(2023, 0, 1 + d), "weekday"));
|
|
9043
|
+
return [...weekdays("long"), ...weekdays("short")];
|
|
9044
|
+
});
|
|
9045
|
+
}
|
|
9046
|
+
function probePart(locale, options, utc, type) {
|
|
9047
|
+
const parts = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).formatToParts(new Date(utc));
|
|
9048
|
+
const found = parts.find((p) => p.type === type);
|
|
9049
|
+
if (!found || !found.value)
|
|
9050
|
+
throw new Error("missing part");
|
|
9051
|
+
return found.value;
|
|
9052
|
+
}
|
|
9053
|
+
function deriveNamesCached(key, derive) {
|
|
9054
|
+
const cached = namesCache.get(key);
|
|
9055
|
+
if (cached !== undefined)
|
|
9056
|
+
return cached;
|
|
9057
|
+
let derived;
|
|
9058
|
+
try {
|
|
9059
|
+
derived = derive();
|
|
9060
|
+
} catch {
|
|
9061
|
+
derived = null;
|
|
9062
|
+
}
|
|
9063
|
+
namesCache.set(key, derived);
|
|
9064
|
+
return derived;
|
|
9065
|
+
}
|
|
9066
|
+
function resolveLocaleDateFormat(locale, probeOptions) {
|
|
9067
|
+
const key = `${locale}|${JSON.stringify(probeOptions, Object.keys(probeOptions).sort())}`;
|
|
9068
|
+
const cached = formatCache.get(key);
|
|
9069
|
+
if (cached !== undefined)
|
|
9070
|
+
return cached;
|
|
9071
|
+
const derived = deriveFormat(locale, probeOptions);
|
|
9072
|
+
formatCache.set(key, derived);
|
|
9073
|
+
return derived;
|
|
9074
|
+
}
|
|
9075
|
+
var VERIFY_UTC = new Date(Date.UTC(2001, 4, 13));
|
|
9076
|
+
function renderPatternAt(pattern, names, y, m, d, wd) {
|
|
9077
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
9078
|
+
return pattern.replace(/YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D/g, (token) => {
|
|
9079
|
+
switch (token) {
|
|
9080
|
+
case "YYYY":
|
|
9081
|
+
return String(y).padStart(4, "0");
|
|
9082
|
+
case "MMMM":
|
|
9083
|
+
return names[m - 1] ?? "";
|
|
9084
|
+
case "MMM":
|
|
9085
|
+
return names[12 + m - 1] ?? "";
|
|
9086
|
+
case "MM":
|
|
9087
|
+
return pad2(m);
|
|
9088
|
+
case "M":
|
|
9089
|
+
return String(m);
|
|
9090
|
+
case "DD":
|
|
9091
|
+
return pad2(d);
|
|
9092
|
+
case "D":
|
|
9093
|
+
return String(d);
|
|
9094
|
+
case "dddd":
|
|
9095
|
+
return names[24 + wd] ?? "";
|
|
9096
|
+
default:
|
|
9097
|
+
return names[31 + wd] ?? "";
|
|
9098
|
+
}
|
|
9099
|
+
});
|
|
9100
|
+
}
|
|
9101
|
+
function deriveFormat(locale, probeOptions) {
|
|
9102
|
+
let dtf;
|
|
9103
|
+
let parts;
|
|
9104
|
+
try {
|
|
9105
|
+
dtf = new Intl.DateTimeFormat(locale, {
|
|
9106
|
+
...probeOptions,
|
|
9107
|
+
timeZone: "UTC"
|
|
9108
|
+
});
|
|
9109
|
+
const resolved = dtf.resolvedOptions();
|
|
9110
|
+
if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
|
|
9111
|
+
return null;
|
|
9112
|
+
parts = dtf.formatToParts(PROBE_UTC);
|
|
9113
|
+
} catch {
|
|
9114
|
+
return null;
|
|
9115
|
+
}
|
|
9116
|
+
const monthTables = [
|
|
9117
|
+
deriveMonthNames(locale, "formatting"),
|
|
9118
|
+
deriveMonthNames(locale, "standalone")
|
|
9119
|
+
];
|
|
9120
|
+
const weekdayTables = [
|
|
9121
|
+
deriveWeekdayNames(locale, "formatting"),
|
|
9122
|
+
deriveWeekdayNames(locale, "standalone")
|
|
9123
|
+
];
|
|
9124
|
+
let monthTable = null;
|
|
9125
|
+
let weekdayTable = null;
|
|
9126
|
+
let pattern = "";
|
|
9127
|
+
let usesNames = false;
|
|
9128
|
+
for (const part of parts) {
|
|
9129
|
+
switch (part.type) {
|
|
9130
|
+
case "year":
|
|
9131
|
+
if (part.value !== "2001")
|
|
9132
|
+
return null;
|
|
9133
|
+
pattern += "YYYY";
|
|
9134
|
+
break;
|
|
9135
|
+
case "month": {
|
|
9136
|
+
if (part.value === "2") {
|
|
9137
|
+
pattern += "M";
|
|
9138
|
+
break;
|
|
9139
|
+
}
|
|
9140
|
+
if (part.value === "02") {
|
|
9141
|
+
pattern += "MM";
|
|
9142
|
+
break;
|
|
9143
|
+
}
|
|
9144
|
+
const wide = monthTables.find((t) => t && part.value === t[1]) ?? null;
|
|
9145
|
+
const abbr = wide ? null : monthTables.find((t) => t && part.value === t[12 + 1]) ?? null;
|
|
9146
|
+
if (wide)
|
|
9147
|
+
pattern += "MMMM";
|
|
9148
|
+
else if (abbr)
|
|
9149
|
+
pattern += "MMM";
|
|
9150
|
+
else
|
|
9151
|
+
return null;
|
|
9152
|
+
monthTable = wide ?? abbr;
|
|
9153
|
+
usesNames = true;
|
|
9154
|
+
break;
|
|
9155
|
+
}
|
|
9156
|
+
case "day":
|
|
9157
|
+
if (part.value === "3")
|
|
9158
|
+
pattern += "D";
|
|
9159
|
+
else if (part.value === "03")
|
|
9160
|
+
pattern += "DD";
|
|
9161
|
+
else
|
|
9162
|
+
return null;
|
|
9163
|
+
break;
|
|
9164
|
+
case "weekday": {
|
|
9165
|
+
const wide = weekdayTables.find((t) => t && part.value === t[6]) ?? null;
|
|
9166
|
+
const abbr = wide ? null : weekdayTables.find((t) => t && part.value === t[7 + 6]) ?? null;
|
|
9167
|
+
if (wide)
|
|
9168
|
+
pattern += "dddd";
|
|
9169
|
+
else if (abbr)
|
|
9170
|
+
pattern += "ddd";
|
|
9171
|
+
else
|
|
9172
|
+
return null;
|
|
9173
|
+
weekdayTable = wide ?? abbr;
|
|
9174
|
+
usesNames = true;
|
|
9175
|
+
break;
|
|
9176
|
+
}
|
|
9177
|
+
case "literal":
|
|
9178
|
+
if (/[YMD]/.test(part.value) || /ddd/.test(part.value))
|
|
9179
|
+
return null;
|
|
9180
|
+
pattern += part.value;
|
|
9181
|
+
break;
|
|
9182
|
+
default:
|
|
9183
|
+
return null;
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
if (!/YYYY|MMMM|MMM|MM|M/.test(pattern) && !/DD|D/.test(pattern))
|
|
9187
|
+
return null;
|
|
9188
|
+
if (!usesNames)
|
|
9189
|
+
return { pattern, names: null };
|
|
9190
|
+
const names = [
|
|
9191
|
+
...monthTable ?? monthTables[0] ?? monthTables[1] ?? Array(24).fill(""),
|
|
9192
|
+
...weekdayTable ?? weekdayTables[0] ?? weekdayTables[1] ?? Array(14).fill("")
|
|
9193
|
+
];
|
|
9194
|
+
if (renderPatternAt(pattern, names, 2001, 5, 13, 0) !== dtf.format(VERIFY_UTC))
|
|
9195
|
+
return null;
|
|
9196
|
+
return { pattern, names };
|
|
9197
|
+
}
|
|
9198
|
+
function unionMemberLiteral(member) {
|
|
9199
|
+
const m = /^'([^'\\]*)'$|^"([^"\\]*)"$/.exec(member.raw.trim());
|
|
9200
|
+
return m ? m[1] ?? m[2] : null;
|
|
9201
|
+
}
|
|
9202
|
+
function resolveLocaleUnionMembers(locale, metadata) {
|
|
9203
|
+
let sourcePropName = null;
|
|
9204
|
+
if (metadata.propsObjectName) {
|
|
9205
|
+
if (locale.kind === "member" && !locale.computed && locale.object.kind === "identifier" && locale.object.name === metadata.propsObjectName) {
|
|
9206
|
+
sourcePropName = locale.property;
|
|
9207
|
+
}
|
|
9208
|
+
} else if (locale.kind === "identifier") {
|
|
9209
|
+
const name = locale.name;
|
|
9210
|
+
const param = metadata.propsParams?.find((pp) => pp.name === name);
|
|
9211
|
+
if (param)
|
|
9212
|
+
sourcePropName = param.sourceName ?? param.name;
|
|
9213
|
+
}
|
|
9214
|
+
if (!sourcePropName)
|
|
9215
|
+
return null;
|
|
9216
|
+
const target = sourcePropName;
|
|
9217
|
+
const prop = metadata.propsType?.properties?.find((p) => p.name === target);
|
|
9218
|
+
if (!prop || prop.optional)
|
|
9219
|
+
return null;
|
|
9220
|
+
const type = prop.type;
|
|
9221
|
+
if (type.kind !== "union" || !type.unionTypes || type.unionTypes.length === 0)
|
|
9222
|
+
return null;
|
|
9223
|
+
const members = [];
|
|
9224
|
+
for (const member of type.unionTypes) {
|
|
9225
|
+
const value = unionMemberLiteral(member);
|
|
9226
|
+
if (value === null)
|
|
9227
|
+
return null;
|
|
9228
|
+
members.push(value);
|
|
9229
|
+
}
|
|
9230
|
+
return members;
|
|
9231
|
+
}
|
|
9232
|
+
var strLit = (value) => ({ kind: "literal", value, literalType: "string" });
|
|
9233
|
+
function strArr(values) {
|
|
9234
|
+
return {
|
|
9235
|
+
kind: "array-literal",
|
|
9236
|
+
elements: values.map((v) => strLit(v)),
|
|
9237
|
+
raw: JSON.stringify(values)
|
|
9238
|
+
};
|
|
9239
|
+
}
|
|
9240
|
+
function foldMembers(locale, members, leaves, allEqual) {
|
|
9241
|
+
let expr = leaves[leaves.length - 1];
|
|
9242
|
+
if (allEqual)
|
|
9243
|
+
return expr;
|
|
9244
|
+
for (let i = leaves.length - 2;i >= 0; i--) {
|
|
9245
|
+
expr = {
|
|
9246
|
+
kind: "conditional",
|
|
9247
|
+
test: { kind: "binary", op: "===", left: locale, right: strLit(members[i]) },
|
|
9248
|
+
consequent: leaves[i],
|
|
9249
|
+
alternate: expr
|
|
9250
|
+
};
|
|
9251
|
+
}
|
|
9252
|
+
return expr;
|
|
9253
|
+
}
|
|
9254
|
+
function matchToLocaleDateStringCall(callee, args, metadata) {
|
|
9255
|
+
if (callee.kind !== "member" || callee.computed)
|
|
9256
|
+
return null;
|
|
9257
|
+
if (callee.property !== "toLocaleDateString" || args.length !== 2)
|
|
9258
|
+
return null;
|
|
9259
|
+
const [locale, options] = args;
|
|
9260
|
+
if (options.kind !== "object-literal")
|
|
9261
|
+
return null;
|
|
9262
|
+
let tz = null;
|
|
9263
|
+
const probeOptions = {};
|
|
9264
|
+
for (const prop of options.properties) {
|
|
9265
|
+
if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
|
|
9266
|
+
return null;
|
|
9267
|
+
const value = String(prop.value.value);
|
|
9268
|
+
if (prop.key === "timeZone") {
|
|
9269
|
+
if (!TO_LOCALE_TZ_RE.test(value))
|
|
9270
|
+
return null;
|
|
9271
|
+
tz = value;
|
|
9272
|
+
} else {
|
|
9273
|
+
probeOptions[prop.key] = value;
|
|
9274
|
+
}
|
|
9275
|
+
}
|
|
9276
|
+
if (tz === null)
|
|
9277
|
+
return null;
|
|
9278
|
+
const receiverType = resolveReceiverType(callee.object, metadata, new Map);
|
|
9279
|
+
if (!receiverType || receiverType.kind !== "interface")
|
|
9280
|
+
return null;
|
|
9281
|
+
const typeName = baseTypeName(receiverType.raw);
|
|
9282
|
+
if (typeName !== "Date")
|
|
9283
|
+
return null;
|
|
9284
|
+
if (metadata.typeDefinitions.some((d) => d.name === typeName))
|
|
9285
|
+
return null;
|
|
9286
|
+
if (locale.kind === "literal" && locale.literalType === "string") {
|
|
9287
|
+
const format2 = resolveLocaleDateFormat(String(locale.value), probeOptions);
|
|
9288
|
+
if (format2 === null)
|
|
9289
|
+
return null;
|
|
9290
|
+
return {
|
|
9291
|
+
kind: "helper-call",
|
|
9292
|
+
helper: "format_date",
|
|
9293
|
+
args: [callee.object, strLit(format2.pattern), strLit(tz), strArr(format2.names ?? [])]
|
|
9294
|
+
};
|
|
9295
|
+
}
|
|
9296
|
+
const members = resolveLocaleUnionMembers(locale, metadata);
|
|
9297
|
+
if (!members)
|
|
9298
|
+
return null;
|
|
9299
|
+
const formats = [];
|
|
9300
|
+
for (const member of members) {
|
|
9301
|
+
const format2 = resolveLocaleDateFormat(member, probeOptions);
|
|
9302
|
+
if (format2 === null)
|
|
9303
|
+
return null;
|
|
9304
|
+
formats.push(format2);
|
|
9305
|
+
}
|
|
9306
|
+
const patterns = formats.map((f) => f.pattern);
|
|
9307
|
+
const nameTables = formats.map((f) => JSON.stringify(f.names ?? []));
|
|
9308
|
+
return {
|
|
9309
|
+
kind: "helper-call",
|
|
9310
|
+
helper: "format_date",
|
|
9311
|
+
args: [
|
|
9312
|
+
callee.object,
|
|
9313
|
+
foldMembers(locale, members, patterns.map(strLit), new Set(patterns).size === 1),
|
|
9314
|
+
strLit(tz),
|
|
9315
|
+
foldMembers(locale, members, formats.map((f) => strArr(f.names ?? [])), new Set(nameTables).size === 1)
|
|
9316
|
+
]
|
|
9317
|
+
};
|
|
9318
|
+
}
|
|
9319
|
+
function foldedArgToClientJs(arg, localeText) {
|
|
9320
|
+
if (arg.kind === "literal")
|
|
9321
|
+
return JSON.stringify(arg.value);
|
|
9322
|
+
if (arg.kind === "array-literal") {
|
|
9323
|
+
const values = [];
|
|
9324
|
+
for (const el of arg.elements) {
|
|
9325
|
+
if (el.kind !== "literal")
|
|
9326
|
+
return null;
|
|
9327
|
+
values.push(String(el.value));
|
|
9328
|
+
}
|
|
9329
|
+
return JSON.stringify(values);
|
|
9330
|
+
}
|
|
9331
|
+
if (arg.kind !== "conditional")
|
|
9332
|
+
return null;
|
|
9333
|
+
const t = arg.test;
|
|
9334
|
+
if (t.kind !== "binary" || t.op !== "===" || t.right.kind !== "literal")
|
|
9335
|
+
return null;
|
|
9336
|
+
if (arg.consequent.kind !== "literal" && arg.consequent.kind !== "array-literal")
|
|
9337
|
+
return null;
|
|
9338
|
+
const cons = foldedArgToClientJs(arg.consequent, localeText);
|
|
9339
|
+
const rest = foldedArgToClientJs(arg.alternate, localeText);
|
|
9340
|
+
if (cons === null || rest === null)
|
|
9341
|
+
return null;
|
|
9342
|
+
return `${localeText} === ${JSON.stringify(t.right.value)} ? ${cons} : ${rest}`;
|
|
9343
|
+
}
|
|
9344
|
+
var toLocaleDatePlugin = {
|
|
9345
|
+
name: "toLocaleDateString",
|
|
9346
|
+
prepare(metadata) {
|
|
9347
|
+
if (!metadata.propsType || !typeReachesDate(metadata.propsType, metadata, new Set))
|
|
9348
|
+
return null;
|
|
9349
|
+
return (callee, args) => matchToLocaleDateStringCall(callee, args, metadata);
|
|
9350
|
+
}
|
|
9351
|
+
};
|
|
9352
|
+
|
|
8463
9353
|
// src/jsx-to-ir.ts
|
|
8464
9354
|
import { toHTMLAttrName, decodeEntities } from "@barefootjs/shared";
|
|
8465
9355
|
var CLIENT_DIRECTIVE_INTERIOR_RE2 = /^\s*@client\s*$/;
|
|
@@ -8555,6 +9445,19 @@ function getDateLoweringMatcher(ctx) {
|
|
|
8555
9445
|
}
|
|
8556
9446
|
return ctx._dateLoweringMatcher;
|
|
8557
9447
|
}
|
|
9448
|
+
function getToLocaleDateLoweringMatcher(ctx) {
|
|
9449
|
+
if (ctx._toLocaleDateLoweringMatcher === undefined) {
|
|
9450
|
+
const a = ctx.analyzer;
|
|
9451
|
+
const metadataSlice = {
|
|
9452
|
+
propsType: a.propsType,
|
|
9453
|
+
propsObjectName: a.propsObjectName,
|
|
9454
|
+
propsParams: a.propsParams,
|
|
9455
|
+
typeDefinitions: a.typeDefinitions
|
|
9456
|
+
};
|
|
9457
|
+
ctx._toLocaleDateLoweringMatcher = toLocaleDatePlugin.prepare(metadataSlice);
|
|
9458
|
+
}
|
|
9459
|
+
return ctx._toLocaleDateLoweringMatcher;
|
|
9460
|
+
}
|
|
8558
9461
|
function lowerDateCalls(text, expr, ctx) {
|
|
8559
9462
|
const matcher = getDateLoweringMatcher(ctx);
|
|
8560
9463
|
if (!matcher)
|
|
@@ -8583,8 +9486,48 @@ function lowerDateCalls(text, expr, ctx) {
|
|
|
8583
9486
|
}
|
|
8584
9487
|
return restore(result);
|
|
8585
9488
|
}
|
|
9489
|
+
function lowerToLocaleDateCalls(text, expr, ctx) {
|
|
9490
|
+
const matcher = getToLocaleDateLoweringMatcher(ctx);
|
|
9491
|
+
if (!matcher)
|
|
9492
|
+
return text;
|
|
9493
|
+
const candidates = [];
|
|
9494
|
+
function visit2(n) {
|
|
9495
|
+
if (ts11.isCallExpression(n) && n.arguments.length === 2 && ts11.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
|
|
9496
|
+
candidates.push(n);
|
|
9497
|
+
}
|
|
9498
|
+
ts11.forEachChild(n, visit2);
|
|
9499
|
+
}
|
|
9500
|
+
visit2(expr);
|
|
9501
|
+
if (candidates.length === 0)
|
|
9502
|
+
return text;
|
|
9503
|
+
const { protect, restore, replaceProtectedCall } = createTemplateAwareStringProtector();
|
|
9504
|
+
let result = protect(text);
|
|
9505
|
+
for (const call of candidates) {
|
|
9506
|
+
const propAccess = call.expression;
|
|
9507
|
+
const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
|
|
9508
|
+
if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
|
|
9509
|
+
continue;
|
|
9510
|
+
const [, patternArg, tzArg, namesArg] = node.args;
|
|
9511
|
+
if (!patternArg || tzArg?.kind !== "literal")
|
|
9512
|
+
continue;
|
|
9513
|
+
const localeText = ctx.getJS(call.arguments[0]);
|
|
9514
|
+
const patternJs = foldedArgToClientJs(patternArg, localeText);
|
|
9515
|
+
if (patternJs === null)
|
|
9516
|
+
continue;
|
|
9517
|
+
let namesJs = null;
|
|
9518
|
+
if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
|
|
9519
|
+
namesJs = foldedArgToClientJs(namesArg, localeText);
|
|
9520
|
+
if (namesJs === null)
|
|
9521
|
+
continue;
|
|
9522
|
+
}
|
|
9523
|
+
const receiverText = ctx.getJS(propAccess.expression);
|
|
9524
|
+
const matchText = ctx.getJS(call);
|
|
9525
|
+
result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
|
|
9526
|
+
}
|
|
9527
|
+
return restore(result);
|
|
9528
|
+
}
|
|
8586
9529
|
function rewriteBarePropRefs2(text, expr, ctx) {
|
|
8587
|
-
const dateLowered = lowerDateCalls(text, expr, ctx);
|
|
9530
|
+
const dateLowered = lowerToLocaleDateCalls(lowerDateCalls(text, expr, ctx), expr, ctx);
|
|
8588
9531
|
let propNames = getDestructuredPropNames(ctx);
|
|
8589
9532
|
if (!propNames)
|
|
8590
9533
|
return dateLowered === text ? undefined : dateLowered;
|
|
@@ -13436,7 +14379,8 @@ var RUNTIME_IMPORT_CANDIDATES = [
|
|
|
13436
14379
|
"tAfter",
|
|
13437
14380
|
"beginTurn",
|
|
13438
14381
|
"endTurn",
|
|
13439
|
-
"date"
|
|
14382
|
+
"date",
|
|
14383
|
+
"formatDate"
|
|
13440
14384
|
];
|
|
13441
14385
|
var RUNTIME_MODULE = "@barefootjs/client/runtime";
|
|
13442
14386
|
var IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
|
|
@@ -14722,7 +15666,7 @@ function importsSearchParams(metadata) {
|
|
|
14722
15666
|
function queryHrefLocalNames(metadata) {
|
|
14723
15667
|
const names = new Set;
|
|
14724
15668
|
for (const imp of metadata.imports) {
|
|
14725
|
-
if (!
|
|
15669
|
+
if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
|
|
14726
15670
|
continue;
|
|
14727
15671
|
for (const s of imp.specifiers) {
|
|
14728
15672
|
if (s.isTypeOnly || s.isNamespace || s.isDefault)
|
|
@@ -14733,10 +15677,24 @@ function queryHrefLocalNames(metadata) {
|
|
|
14733
15677
|
}
|
|
14734
15678
|
return names;
|
|
14735
15679
|
}
|
|
14736
|
-
var
|
|
15680
|
+
var CLIENT_HELPER_SOURCES = new Set([
|
|
14737
15681
|
"@barefootjs/client",
|
|
14738
15682
|
"@barefootjs/client/runtime"
|
|
14739
15683
|
]);
|
|
15684
|
+
function formatDateLocalNames(metadata) {
|
|
15685
|
+
const names = new Set;
|
|
15686
|
+
for (const imp of metadata.imports) {
|
|
15687
|
+
if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
|
|
15688
|
+
continue;
|
|
15689
|
+
for (const s of imp.specifiers) {
|
|
15690
|
+
if (s.isTypeOnly || s.isNamespace || s.isDefault)
|
|
15691
|
+
continue;
|
|
15692
|
+
if (s.name === "formatDate")
|
|
15693
|
+
names.add(s.alias ?? s.name);
|
|
15694
|
+
}
|
|
15695
|
+
}
|
|
15696
|
+
return names;
|
|
15697
|
+
}
|
|
14740
15698
|
function matchSearchParamsMethodCall(callee, args, localNames) {
|
|
14741
15699
|
if (callee.kind !== "member" || callee.computed)
|
|
14742
15700
|
return null;
|
|
@@ -16602,6 +17560,66 @@ function getReactiveDateLoweringMatcher(ctx) {
|
|
|
16602
17560
|
};
|
|
16603
17561
|
return datePlugin.prepare(metadataSlice);
|
|
16604
17562
|
}
|
|
17563
|
+
function getReactiveToLocaleMatcher(ctx) {
|
|
17564
|
+
if (!ctx.propsType)
|
|
17565
|
+
return null;
|
|
17566
|
+
const metadataSlice = {
|
|
17567
|
+
propsType: ctx.propsType,
|
|
17568
|
+
propsObjectName: ctx.propsObjectName,
|
|
17569
|
+
propsParams: ctx.propsParams,
|
|
17570
|
+
typeDefinitions: ctx.typeDefinitions ?? []
|
|
17571
|
+
};
|
|
17572
|
+
return toLocaleDatePlugin.prepare(metadataSlice);
|
|
17573
|
+
}
|
|
17574
|
+
function lowerToLocaleCallsInReactiveExpr(expr, matcher) {
|
|
17575
|
+
if (!matcher)
|
|
17576
|
+
return expr;
|
|
17577
|
+
let sourceFile;
|
|
17578
|
+
try {
|
|
17579
|
+
sourceFile = ts14.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts14.ScriptTarget.Latest, true, ts14.ScriptKind.TS);
|
|
17580
|
+
} catch {
|
|
17581
|
+
return expr;
|
|
17582
|
+
}
|
|
17583
|
+
const stmt = sourceFile.statements[0];
|
|
17584
|
+
if (!stmt || !ts14.isExpressionStatement(stmt))
|
|
17585
|
+
return expr;
|
|
17586
|
+
const root = ts14.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
|
|
17587
|
+
const candidates = [];
|
|
17588
|
+
const visit3 = (n) => {
|
|
17589
|
+
if (ts14.isCallExpression(n) && n.arguments.length === 2 && ts14.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
|
|
17590
|
+
candidates.push(n);
|
|
17591
|
+
}
|
|
17592
|
+
ts14.forEachChild(n, visit3);
|
|
17593
|
+
};
|
|
17594
|
+
visit3(root);
|
|
17595
|
+
if (candidates.length === 0)
|
|
17596
|
+
return expr;
|
|
17597
|
+
const { protect, restore, replaceProtectedCall } = createTemplateAwareStringProtector();
|
|
17598
|
+
let result = protect(expr);
|
|
17599
|
+
for (const call of candidates) {
|
|
17600
|
+
const propAccess = call.expression;
|
|
17601
|
+
const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
|
|
17602
|
+
if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
|
|
17603
|
+
continue;
|
|
17604
|
+
const [, patternArg, tzArg, namesArg] = node.args;
|
|
17605
|
+
if (!patternArg || tzArg?.kind !== "literal")
|
|
17606
|
+
continue;
|
|
17607
|
+
const localeText = call.arguments[0].getText(sourceFile);
|
|
17608
|
+
const patternJs = foldedArgToClientJs(patternArg, localeText);
|
|
17609
|
+
if (patternJs === null)
|
|
17610
|
+
continue;
|
|
17611
|
+
let namesJs = null;
|
|
17612
|
+
if (namesArg && !(namesArg.kind === "array-literal" && namesArg.elements.length === 0)) {
|
|
17613
|
+
namesJs = foldedArgToClientJs(namesArg, localeText);
|
|
17614
|
+
if (namesJs === null)
|
|
17615
|
+
continue;
|
|
17616
|
+
}
|
|
17617
|
+
const receiverText = propAccess.expression.getText(sourceFile);
|
|
17618
|
+
const matchText = call.getText(sourceFile);
|
|
17619
|
+
result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ""})`);
|
|
17620
|
+
}
|
|
17621
|
+
return restore(result);
|
|
17622
|
+
}
|
|
16605
17623
|
function lowerDateCallsInReactiveExpr(expr, matcher) {
|
|
16606
17624
|
if (!matcher)
|
|
16607
17625
|
return expr;
|
|
@@ -16641,6 +17659,7 @@ function lowerDateCallsInReactiveExpr(expr, matcher) {
|
|
|
16641
17659
|
}
|
|
16642
17660
|
function emitDynamicTextUpdates(lines, ctx) {
|
|
16643
17661
|
const dateLoweringMatcher = getReactiveDateLoweringMatcher(ctx);
|
|
17662
|
+
const toLocaleMatcher = getReactiveToLocaleMatcher(ctx);
|
|
16644
17663
|
const byExpression = new Map;
|
|
16645
17664
|
for (const elem of ctx.dynamicElements) {
|
|
16646
17665
|
const key = elem.expression;
|
|
@@ -16650,7 +17669,7 @@ function emitDynamicTextUpdates(lines, ctx) {
|
|
|
16650
17669
|
byExpression.get(key).push(elem);
|
|
16651
17670
|
}
|
|
16652
17671
|
for (const [rawExpr, elems] of byExpression) {
|
|
16653
|
-
const expr = lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher);
|
|
17672
|
+
const expr = lowerToLocaleCallsInReactiveExpr(lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher), toLocaleMatcher);
|
|
16654
17673
|
const conditionalElems = elems.filter((e) => e.insideConditional);
|
|
16655
17674
|
const normalElems = elems.filter((e) => !e.insideConditional);
|
|
16656
17675
|
if (normalElems.length > 0 || conditionalElems.length > 0) {
|
|
@@ -19875,13 +20894,14 @@ function pushDiagnostic(errors, seen, loc, method, receiverPath, isProp, typeNam
|
|
|
19875
20894
|
return;
|
|
19876
20895
|
seen.add(key);
|
|
19877
20896
|
const receiver = isProp ? `prop '${receiverPath}'` : `'${receiverPath}'`;
|
|
20897
|
+
const suggestion = method === "toLocaleDateString" && typeName === "Date" ? "Pass a literal locale and an explicit literal timeZone — .toLocaleDateString('ja-JP', { timeZone: 'UTC' }) (or a fixed '±HH:MM' offset) — to compile it to the format_date helper; for a runtime locale, resolve the pattern in your i18n layer and use formatDate(date, pattern, tz) from @barefootjs/client. Alternatively add /* @client */ or pre-compute server-side." : "Add /* @client */ to evaluate this expression on the client only, or pre-compute the value server-side.";
|
|
19878
20898
|
errors.push({
|
|
19879
20899
|
code: ErrorCodes.UNSUPPORTED_JSX_PATTERN,
|
|
19880
20900
|
severity: "error",
|
|
19881
20901
|
message: `Expression cannot be compiled to marked template: method '.${method}()' on ${receiver} of host type '${typeName}' has no catalogued lowering.`,
|
|
19882
20902
|
loc,
|
|
19883
20903
|
suggestion: {
|
|
19884
|
-
message:
|
|
20904
|
+
message: suggestion
|
|
19885
20905
|
}
|
|
19886
20906
|
});
|
|
19887
20907
|
}
|
|
@@ -21397,6 +22417,30 @@ function isOmitBranch(node) {
|
|
|
21397
22417
|
}
|
|
21398
22418
|
return false;
|
|
21399
22419
|
}
|
|
22420
|
+
// src/format-date-lowering.ts
|
|
22421
|
+
var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
|
|
22422
|
+
var EMPTY_NAMES = { kind: "array-literal", elements: [], raw: "[]" };
|
|
22423
|
+
function matchFormatDateCall(callee, args, locals) {
|
|
22424
|
+
if (callee.kind !== "identifier" || !locals.has(callee.name))
|
|
22425
|
+
return null;
|
|
22426
|
+
if (args.length < 2 || args.length > 4)
|
|
22427
|
+
return null;
|
|
22428
|
+
return {
|
|
22429
|
+
kind: "helper-call",
|
|
22430
|
+
helper: "format_date",
|
|
22431
|
+
args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES]
|
|
22432
|
+
};
|
|
22433
|
+
}
|
|
22434
|
+
var formatDatePlugin = {
|
|
22435
|
+
name: "formatDate",
|
|
22436
|
+
prepare(metadata) {
|
|
22437
|
+
const locals = formatDateLocalNames(metadata);
|
|
22438
|
+
if (locals.size === 0)
|
|
22439
|
+
return null;
|
|
22440
|
+
return (callee, args) => matchFormatDateCall(callee, args, locals);
|
|
22441
|
+
}
|
|
22442
|
+
};
|
|
22443
|
+
|
|
21400
22444
|
// src/builtin-lowering-plugins.ts
|
|
21401
22445
|
var queryHrefPlugin = {
|
|
21402
22446
|
name: "queryHref",
|
|
@@ -21410,7 +22454,12 @@ var queryHrefPlugin = {
|
|
|
21410
22454
|
};
|
|
21411
22455
|
}
|
|
21412
22456
|
};
|
|
21413
|
-
var BUILTIN_LOWERING_PLUGINS = [
|
|
22457
|
+
var BUILTIN_LOWERING_PLUGINS = [
|
|
22458
|
+
queryHrefPlugin,
|
|
22459
|
+
datePlugin,
|
|
22460
|
+
formatDatePlugin,
|
|
22461
|
+
toLocaleDatePlugin
|
|
22462
|
+
];
|
|
21414
22463
|
function registerBuiltinLoweringPlugins() {
|
|
21415
22464
|
for (const plugin of BUILTIN_LOWERING_PLUGINS)
|
|
21416
22465
|
registerLoweringPlugin(plugin);
|
|
@@ -24453,6 +25502,7 @@ export {
|
|
|
24453
25502
|
formatFallbackExplanations,
|
|
24454
25503
|
formatEventSummary,
|
|
24455
25504
|
formatError,
|
|
25505
|
+
formatDateLocalNames,
|
|
24456
25506
|
formatComponentSummary,
|
|
24457
25507
|
formatComponentGraph,
|
|
24458
25508
|
formatBudgetDiff,
|