@barefootjs/jsx 0.21.3 → 0.23.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/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 +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/format-date-lowering.d.ts +27 -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 +706 -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 +72 -0
- package/dist/to-locale-date-lowering.d.ts.map +1 -0
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/format-date-lowering.test.ts +109 -0
- package/src/__tests__/reactive-factory-cross-file.test.ts +239 -0
- package/src/__tests__/reactive-factory-inlining.test.ts +293 -4
- package/src/__tests__/to-locale-date-lowering.test.ts +181 -0
- package/src/adapters/env-signal.ts +26 -3
- package/src/analyzer-context.ts +19 -4
- package/src/analyzer.ts +712 -91
- package/src/builtin-lowering-plugins.ts +8 -1
- package/src/date-lowering.ts +1 -1
- package/src/errors.ts +13 -0
- package/src/format-date-lowering.ts +51 -0
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/emit-reactive.ts +80 -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 +79 -1
- package/src/rich-type-refusal.ts +9 -1
- package/src/to-locale-date-lowering.ts +175 -0
- package/src/types.ts +24 -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,9 @@ 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"
|
|
5221
5247
|
};
|
|
5222
5248
|
var errorMessages = {
|
|
5223
5249
|
[ErrorCodes.MISSING_USE_CLIENT]: "'use client' directive required for components with createSignal or event handlers",
|
|
@@ -5241,7 +5267,9 @@ var errorMessages = {
|
|
|
5241
5267
|
[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
5268
|
[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
5269
|
[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."
|
|
5270
|
+
[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.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."
|
|
5245
5273
|
};
|
|
5246
5274
|
function createError(code, loc, options) {
|
|
5247
5275
|
if (code === undefined || !(code in errorMessages)) {
|
|
@@ -5567,6 +5595,10 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
|
|
|
5567
5595
|
}
|
|
5568
5596
|
const ctx = createAnalyzerContext(sourceFile, filePath);
|
|
5569
5597
|
ctx.checker = checker;
|
|
5598
|
+
ctx.reactiveFactories = prescan.factories;
|
|
5599
|
+
ctx.declinedReactiveFactories = prescan.declined;
|
|
5600
|
+
ctx.reactiveShapedHelpers = prescan.reactiveShaped;
|
|
5601
|
+
ctx.cleanFactoryImports = prescan.cleanFactoryImports;
|
|
5570
5602
|
const brandImportLoc = findBrandPackageImportLoc(sourceFile, filePath);
|
|
5571
5603
|
if (!hadSharedProgram && brandImportLoc !== null) {
|
|
5572
5604
|
ctx.errors.push(createError(ErrorCodes.SHARED_PROGRAM_REQUIRED, brandImportLoc));
|
|
@@ -6472,6 +6504,7 @@ var CLIENT_EXPORTS = new Set([
|
|
|
6472
6504
|
"cleanupPortalPlaceholder",
|
|
6473
6505
|
"createSearchParams",
|
|
6474
6506
|
"queryHref",
|
|
6507
|
+
"formatDate",
|
|
6475
6508
|
"Async",
|
|
6476
6509
|
"Region"
|
|
6477
6510
|
]);
|
|
@@ -7721,27 +7754,241 @@ var REACTIVE_PRIMITIVES = new Set([
|
|
|
7721
7754
|
function prescanReactiveFactoriesInSource(source, filePath) {
|
|
7722
7755
|
const sourceFile = ts8.createSourceFile(filePath + ".prescan", source, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
|
|
7723
7756
|
const factories = new Map;
|
|
7757
|
+
const declined = new Map;
|
|
7758
|
+
const reactiveShaped = new Set;
|
|
7759
|
+
const cleanFactoryImports = new Set;
|
|
7724
7760
|
function visitTop(node) {
|
|
7725
7761
|
if (ts8.isFunctionDeclaration(node) && node.name && node.body) {
|
|
7726
|
-
const
|
|
7727
|
-
if (
|
|
7728
|
-
|
|
7762
|
+
const det = detectReactiveFactory(node, sourceFile, filePath);
|
|
7763
|
+
if (!det)
|
|
7764
|
+
return;
|
|
7765
|
+
switch (det.kind) {
|
|
7766
|
+
case "factory":
|
|
7767
|
+
factories.set(node.name.text, det.info);
|
|
7768
|
+
break;
|
|
7769
|
+
case "declined":
|
|
7770
|
+
declined.set(node.name.text, det.declined);
|
|
7771
|
+
break;
|
|
7772
|
+
case "reactive-shaped":
|
|
7773
|
+
reactiveShaped.add(node.name.text);
|
|
7774
|
+
break;
|
|
7775
|
+
}
|
|
7729
7776
|
}
|
|
7730
7777
|
}
|
|
7731
7778
|
ts8.forEachChild(sourceFile, visitTop);
|
|
7732
|
-
|
|
7779
|
+
const result = { factories, declined, reactiveShaped, cleanFactoryImports, sourceFile };
|
|
7780
|
+
prescanImportedReactiveFactories(sourceFile, filePath, result);
|
|
7781
|
+
return result;
|
|
7782
|
+
}
|
|
7783
|
+
function prescanImportedReactiveFactories(entrySourceFile, filePath, result) {
|
|
7784
|
+
const candidateCallees = new Set;
|
|
7785
|
+
function collectCandidates(node) {
|
|
7786
|
+
if (ts8.isVariableDeclaration(node) && (ts8.isArrayBindingPattern(node.name) || ts8.isObjectBindingPattern(node.name)) && node.initializer && ts8.isCallExpression(node.initializer) && ts8.isIdentifier(node.initializer.expression)) {
|
|
7787
|
+
candidateCallees.add(node.initializer.expression.text);
|
|
7788
|
+
}
|
|
7789
|
+
ts8.forEachChild(node, collectCandidates);
|
|
7790
|
+
}
|
|
7791
|
+
collectCandidates(entrySourceFile);
|
|
7792
|
+
if (candidateCallees.size === 0)
|
|
7793
|
+
return;
|
|
7794
|
+
const importsToCheck = [];
|
|
7795
|
+
for (const stmt of entrySourceFile.statements) {
|
|
7796
|
+
if (!ts8.isImportDeclaration(stmt))
|
|
7797
|
+
continue;
|
|
7798
|
+
if (!ts8.isStringLiteral(stmt.moduleSpecifier))
|
|
7799
|
+
continue;
|
|
7800
|
+
const src = stmt.moduleSpecifier.text;
|
|
7801
|
+
if (!src.startsWith("./") && !src.startsWith("../"))
|
|
7802
|
+
continue;
|
|
7803
|
+
if (stmt.importClause?.isTypeOnly)
|
|
7804
|
+
continue;
|
|
7805
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
7806
|
+
if (!namedBindings || !ts8.isNamedImports(namedBindings))
|
|
7807
|
+
continue;
|
|
7808
|
+
const specs = [];
|
|
7809
|
+
for (const el of namedBindings.elements) {
|
|
7810
|
+
if (el.isTypeOnly)
|
|
7811
|
+
continue;
|
|
7812
|
+
const local = el.name.text;
|
|
7813
|
+
if (!candidateCallees.has(local))
|
|
7814
|
+
continue;
|
|
7815
|
+
specs.push({ exported: (el.propertyName ?? el.name).text, local });
|
|
7816
|
+
}
|
|
7817
|
+
if (specs.length === 0)
|
|
7818
|
+
continue;
|
|
7819
|
+
importsToCheck.push({ src, specs });
|
|
7820
|
+
}
|
|
7821
|
+
if (importsToCheck.length === 0)
|
|
7822
|
+
return;
|
|
7823
|
+
for (const { src, specs } of importsToCheck) {
|
|
7824
|
+
const resolved = resolveRelativeImportToFile(src, filePath);
|
|
7825
|
+
if (!resolved)
|
|
7826
|
+
continue;
|
|
7827
|
+
let content;
|
|
7828
|
+
try {
|
|
7829
|
+
content = fs.readFileSync(resolved, "utf8");
|
|
7830
|
+
} catch {
|
|
7831
|
+
continue;
|
|
7832
|
+
}
|
|
7833
|
+
const alreadyKnown = (name) => result.factories.has(name) || result.declined.has(name) || result.reactiveShaped.has(name);
|
|
7834
|
+
const hasAnyPrimitiveText = [...REACTIVE_PRIMITIVES].some((p) => content.includes(p));
|
|
7835
|
+
if (!hasAnyPrimitiveText) {
|
|
7836
|
+
for (const spec of specs) {
|
|
7837
|
+
if (!alreadyKnown(spec.local))
|
|
7838
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7839
|
+
}
|
|
7840
|
+
continue;
|
|
7841
|
+
}
|
|
7842
|
+
const helperSf = ts8.createSourceFile(resolved + ".prescan", content, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TSX);
|
|
7843
|
+
const localFns = new Map;
|
|
7844
|
+
const exportedFns = new Map;
|
|
7845
|
+
for (const stmt of helperSf.statements) {
|
|
7846
|
+
if (ts8.isFunctionDeclaration(stmt) && stmt.name && stmt.body) {
|
|
7847
|
+
localFns.set(stmt.name.text, stmt);
|
|
7848
|
+
const hasExportModifier = stmt.modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword) ?? false;
|
|
7849
|
+
const hasDefaultModifier = stmt.modifiers?.some((m) => m.kind === ts8.SyntaxKind.DefaultKeyword) ?? false;
|
|
7850
|
+
if (hasExportModifier && !hasDefaultModifier) {
|
|
7851
|
+
exportedFns.set(stmt.name.text, stmt);
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
for (const stmt of helperSf.statements) {
|
|
7856
|
+
if (ts8.isExportDeclaration(stmt) && stmt.exportClause && ts8.isNamedExports(stmt.exportClause) && !stmt.moduleSpecifier && !stmt.isTypeOnly) {
|
|
7857
|
+
for (const el of stmt.exportClause.elements) {
|
|
7858
|
+
if (el.isTypeOnly)
|
|
7859
|
+
continue;
|
|
7860
|
+
const fn = localFns.get((el.propertyName ?? el.name).text);
|
|
7861
|
+
if (fn)
|
|
7862
|
+
exportedFns.set(el.name.text, fn);
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
const moduleBindings = collectHelperModuleValueBindings(helperSf);
|
|
7867
|
+
for (const spec of specs) {
|
|
7868
|
+
if (alreadyKnown(spec.local))
|
|
7869
|
+
continue;
|
|
7870
|
+
const fn = exportedFns.get(spec.exported);
|
|
7871
|
+
if (!fn) {
|
|
7872
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7873
|
+
continue;
|
|
7874
|
+
}
|
|
7875
|
+
const det = detectReactiveFactory(fn, helperSf, resolved);
|
|
7876
|
+
if (!det) {
|
|
7877
|
+
result.cleanFactoryImports.add(spec.local);
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7880
|
+
switch (det.kind) {
|
|
7881
|
+
case "reactive-shaped":
|
|
7882
|
+
result.reactiveShaped.add(spec.local);
|
|
7883
|
+
break;
|
|
7884
|
+
case "declined":
|
|
7885
|
+
result.declined.set(spec.local, det.declined);
|
|
7886
|
+
break;
|
|
7887
|
+
case "factory": {
|
|
7888
|
+
const offending = moduleCaptureCheck(fn, det.info, moduleBindings, fn.name.text);
|
|
7889
|
+
if (offending.length > 0) {
|
|
7890
|
+
result.declined.set(spec.local, {
|
|
7891
|
+
code: "BF112",
|
|
7892
|
+
detail: `'${offending.join("', '")}'`,
|
|
7893
|
+
loc: det.info.loc
|
|
7894
|
+
});
|
|
7895
|
+
} else {
|
|
7896
|
+
det.info.sourceFilePath = resolved;
|
|
7897
|
+
result.factories.set(spec.local, det.info);
|
|
7898
|
+
}
|
|
7899
|
+
break;
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
function collectHelperModuleValueBindings(sf) {
|
|
7906
|
+
const names = new Set;
|
|
7907
|
+
for (const stmt of sf.statements) {
|
|
7908
|
+
if (ts8.isVariableStatement(stmt)) {
|
|
7909
|
+
const out = [];
|
|
7910
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
7911
|
+
addBindingNames(decl.name, out);
|
|
7912
|
+
}
|
|
7913
|
+
for (const n of out)
|
|
7914
|
+
names.add(n);
|
|
7915
|
+
continue;
|
|
7916
|
+
}
|
|
7917
|
+
if ((ts8.isFunctionDeclaration(stmt) || ts8.isClassDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name) {
|
|
7918
|
+
names.add(stmt.name.text);
|
|
7919
|
+
continue;
|
|
7920
|
+
}
|
|
7921
|
+
if (ts8.isImportDeclaration(stmt)) {
|
|
7922
|
+
if (stmt.importClause?.isTypeOnly)
|
|
7923
|
+
continue;
|
|
7924
|
+
if (!ts8.isStringLiteral(stmt.moduleSpecifier))
|
|
7925
|
+
continue;
|
|
7926
|
+
const src = stmt.moduleSpecifier.text;
|
|
7927
|
+
if (src === "@barefootjs/client" || src === "@barefootjs/client/runtime")
|
|
7928
|
+
continue;
|
|
7929
|
+
if (stmt.importClause?.name)
|
|
7930
|
+
names.add(stmt.importClause.name.text);
|
|
7931
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
7932
|
+
if (namedBindings && ts8.isNamedImports(namedBindings)) {
|
|
7933
|
+
for (const el of namedBindings.elements) {
|
|
7934
|
+
if (el.isTypeOnly)
|
|
7935
|
+
continue;
|
|
7936
|
+
names.add(el.name.text);
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
if (namedBindings && ts8.isNamespaceImport(namedBindings)) {
|
|
7940
|
+
names.add(namedBindings.name.text);
|
|
7941
|
+
}
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
return names;
|
|
7945
|
+
}
|
|
7946
|
+
function moduleCaptureCheck(fn, info, moduleBindings, selfName) {
|
|
7947
|
+
if (!fn.body)
|
|
7948
|
+
return [];
|
|
7949
|
+
const free = extractFreeIdentifiersFromNode(fn.body);
|
|
7950
|
+
const exclude = new Set(info.params);
|
|
7951
|
+
for (const b of info.localBindings)
|
|
7952
|
+
exclude.add(b);
|
|
7953
|
+
for (const r of info.returnTupleIdentifiers)
|
|
7954
|
+
exclude.add(r);
|
|
7955
|
+
for (const p of REACTIVE_PRIMITIVES)
|
|
7956
|
+
exclude.add(p);
|
|
7957
|
+
exclude.add(selfName);
|
|
7958
|
+
const offending = [];
|
|
7959
|
+
for (const id of free) {
|
|
7960
|
+
if (exclude.has(id))
|
|
7961
|
+
continue;
|
|
7962
|
+
if (moduleBindings.has(id))
|
|
7963
|
+
offending.push(id);
|
|
7964
|
+
}
|
|
7965
|
+
return offending.sort();
|
|
7733
7966
|
}
|
|
7734
7967
|
function detectReactiveFactory(node, sourceFile, filePath) {
|
|
7735
7968
|
if (!node.body || !node.name)
|
|
7736
7969
|
return null;
|
|
7737
|
-
let
|
|
7970
|
+
let hasReactiveCall = false;
|
|
7971
|
+
function checkForReactive(n) {
|
|
7972
|
+
if (hasReactiveCall)
|
|
7973
|
+
return;
|
|
7974
|
+
if (ts8.isCallExpression(n) && ts8.isIdentifier(n.expression) && REACTIVE_PRIMITIVES.has(n.expression.text)) {
|
|
7975
|
+
hasReactiveCall = true;
|
|
7976
|
+
return;
|
|
7977
|
+
}
|
|
7978
|
+
ts8.forEachChild(n, checkForReactive);
|
|
7979
|
+
}
|
|
7980
|
+
checkForReactive(node.body);
|
|
7981
|
+
if (!hasReactiveCall)
|
|
7982
|
+
return null;
|
|
7983
|
+
const loc = getSourceLocation(node, sourceFile, filePath);
|
|
7984
|
+
let returnExpr = null;
|
|
7738
7985
|
let returnCount = 0;
|
|
7739
7986
|
for (const stmt of node.body.statements) {
|
|
7740
7987
|
if (!ts8.isReturnStatement(stmt))
|
|
7741
7988
|
continue;
|
|
7742
7989
|
returnCount++;
|
|
7743
7990
|
if (!stmt.expression)
|
|
7744
|
-
return
|
|
7991
|
+
return { kind: "reactive-shaped" };
|
|
7745
7992
|
let expr = stmt.expression;
|
|
7746
7993
|
while (ts8.isParenthesizedExpression(expr))
|
|
7747
7994
|
expr = expr.expression;
|
|
@@ -7749,33 +7996,42 @@ function detectReactiveFactory(node, sourceFile, filePath) {
|
|
|
7749
7996
|
expr = expr.expression;
|
|
7750
7997
|
if (ts8.isTypeAssertionExpression(expr))
|
|
7751
7998
|
expr = expr.expression;
|
|
7752
|
-
|
|
7753
|
-
return null;
|
|
7754
|
-
tupleReturn = expr;
|
|
7999
|
+
returnExpr = expr;
|
|
7755
8000
|
}
|
|
7756
|
-
if (returnCount !== 1 || !
|
|
7757
|
-
return
|
|
8001
|
+
if (returnCount !== 1 || !returnExpr)
|
|
8002
|
+
return { kind: "reactive-shaped" };
|
|
7758
8003
|
const returnTupleIdentifiers = [];
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
8004
|
+
let returnKind;
|
|
8005
|
+
if (ts8.isArrayLiteralExpression(returnExpr)) {
|
|
8006
|
+
returnKind = "tuple";
|
|
8007
|
+
for (const el of returnExpr.elements) {
|
|
8008
|
+
if (!ts8.isIdentifier(el))
|
|
8009
|
+
return { kind: "reactive-shaped" };
|
|
8010
|
+
returnTupleIdentifiers.push(el.text);
|
|
8011
|
+
}
|
|
8012
|
+
if (returnTupleIdentifiers.length === 0)
|
|
8013
|
+
return { kind: "reactive-shaped" };
|
|
8014
|
+
} else if (ts8.isObjectLiteralExpression(returnExpr)) {
|
|
8015
|
+
returnKind = "object";
|
|
8016
|
+
const hasNonShorthand = returnExpr.properties.some((p) => !ts8.isShorthandPropertyAssignment(p));
|
|
8017
|
+
if (hasNonShorthand) {
|
|
8018
|
+
return {
|
|
8019
|
+
kind: "declined",
|
|
8020
|
+
declined: {
|
|
8021
|
+
code: "BF111",
|
|
8022
|
+
detail: `return object of '${node.name.text}' uses non-shorthand properties`,
|
|
8023
|
+
loc
|
|
8024
|
+
}
|
|
8025
|
+
};
|
|
7773
8026
|
}
|
|
7774
|
-
|
|
8027
|
+
for (const p of returnExpr.properties) {
|
|
8028
|
+
returnTupleIdentifiers.push(p.name.text);
|
|
8029
|
+
}
|
|
8030
|
+
if (returnTupleIdentifiers.length === 0)
|
|
8031
|
+
return { kind: "reactive-shaped" };
|
|
8032
|
+
} else {
|
|
8033
|
+
return { kind: "reactive-shaped" };
|
|
7775
8034
|
}
|
|
7776
|
-
checkForReactive(node.body);
|
|
7777
|
-
if (!hasReactiveCall)
|
|
7778
|
-
return null;
|
|
7779
8035
|
const localBindings = [];
|
|
7780
8036
|
for (const stmt of node.body.statements) {
|
|
7781
8037
|
if (ts8.isVariableStatement(stmt)) {
|
|
@@ -7788,19 +8044,24 @@ function detectReactiveFactory(node, sourceFile, filePath) {
|
|
|
7788
8044
|
}
|
|
7789
8045
|
const bodyStatements = node.body.statements.filter((s) => !ts8.isReturnStatement(s)).map((s) => s.getText(sourceFile)).join(`
|
|
7790
8046
|
`);
|
|
7791
|
-
const params =
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
return
|
|
8047
|
+
const params = [];
|
|
8048
|
+
for (const p of node.parameters) {
|
|
8049
|
+
if (ts8.isIdentifier(p.name)) {
|
|
8050
|
+
params.push(p.name.text);
|
|
8051
|
+
continue;
|
|
8052
|
+
}
|
|
8053
|
+
return { kind: "reactive-shaped" };
|
|
8054
|
+
}
|
|
7798
8055
|
return {
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
8056
|
+
kind: "factory",
|
|
8057
|
+
info: {
|
|
8058
|
+
params,
|
|
8059
|
+
bodySource: bodyStatements,
|
|
8060
|
+
returnTupleIdentifiers,
|
|
8061
|
+
returnKind,
|
|
8062
|
+
localBindings,
|
|
8063
|
+
loc
|
|
8064
|
+
}
|
|
7804
8065
|
};
|
|
7805
8066
|
}
|
|
7806
8067
|
function addBindingNames(name, out) {
|
|
@@ -7838,8 +8099,6 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7838
8099
|
});
|
|
7839
8100
|
}
|
|
7840
8101
|
function maybeRewriteDecl(stmt, decl) {
|
|
7841
|
-
if (!ts8.isArrayBindingPattern(decl.name))
|
|
7842
|
-
return;
|
|
7843
8102
|
if (!decl.initializer || !ts8.isCallExpression(decl.initializer))
|
|
7844
8103
|
return;
|
|
7845
8104
|
if (!ts8.isIdentifier(decl.initializer.expression))
|
|
@@ -7848,7 +8107,21 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7848
8107
|
const factory = factories.get(factoryName);
|
|
7849
8108
|
if (!factory)
|
|
7850
8109
|
return;
|
|
7851
|
-
|
|
8110
|
+
if (ts8.isArrayBindingPattern(decl.name)) {
|
|
8111
|
+
if (factory.returnKind !== "tuple")
|
|
8112
|
+
return;
|
|
8113
|
+
rewriteTupleDecl(stmt, decl.name, decl.initializer, factory);
|
|
8114
|
+
return;
|
|
8115
|
+
}
|
|
8116
|
+
if (ts8.isObjectBindingPattern(decl.name)) {
|
|
8117
|
+
if (factory.returnKind !== "object")
|
|
8118
|
+
return;
|
|
8119
|
+
rewriteObjectDecl(stmt, decl.name, decl.initializer, factory);
|
|
8120
|
+
return;
|
|
8121
|
+
}
|
|
8122
|
+
}
|
|
8123
|
+
function rewriteTupleDecl(stmt, pattern, call, factory) {
|
|
8124
|
+
const elements = pattern.elements;
|
|
7852
8125
|
if (elements.length !== factory.returnTupleIdentifiers.length)
|
|
7853
8126
|
return;
|
|
7854
8127
|
const callerNames = [];
|
|
@@ -7857,15 +8130,43 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7857
8130
|
return;
|
|
7858
8131
|
callerNames.push(el.name.text);
|
|
7859
8132
|
}
|
|
7860
|
-
const
|
|
8133
|
+
const excludeFromSuffixRename = new Set(factory.params);
|
|
8134
|
+
for (const r of factory.returnTupleIdentifiers)
|
|
8135
|
+
excludeFromSuffixRename.add(r);
|
|
8136
|
+
const renameReturnToCallerNames = new Map;
|
|
8137
|
+
for (let i = 0;i < factory.returnTupleIdentifiers.length; i++) {
|
|
8138
|
+
renameReturnToCallerNames.set(factory.returnTupleIdentifiers[i], callerNames[i]);
|
|
8139
|
+
}
|
|
8140
|
+
inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, renameReturnToCallerNames);
|
|
8141
|
+
}
|
|
8142
|
+
function rewriteObjectDecl(stmt, pattern, call, factory) {
|
|
8143
|
+
const destructured = new Set;
|
|
8144
|
+
for (const el of pattern.elements) {
|
|
8145
|
+
if (el.dotDotDotToken)
|
|
8146
|
+
return;
|
|
8147
|
+
if (el.propertyName)
|
|
8148
|
+
return;
|
|
8149
|
+
if (el.initializer)
|
|
8150
|
+
return;
|
|
8151
|
+
if (!ts8.isIdentifier(el.name))
|
|
8152
|
+
return;
|
|
8153
|
+
if (!factory.returnTupleIdentifiers.includes(el.name.text))
|
|
8154
|
+
return;
|
|
8155
|
+
destructured.add(el.name.text);
|
|
8156
|
+
}
|
|
8157
|
+
const excludeFromSuffixRename = new Set(factory.params);
|
|
8158
|
+
for (const d of destructured)
|
|
8159
|
+
excludeFromSuffixRename.add(d);
|
|
8160
|
+
inlineFactoryCallAtSite(stmt, factory, call.arguments, excludeFromSuffixRename, null);
|
|
8161
|
+
}
|
|
8162
|
+
function inlineFactoryCallAtSite(stmt, factory, args, excludeFromSuffixRename, renameReturnToCallerNames) {
|
|
8163
|
+
const argTexts = args.map((a) => a.getText(sourceFile));
|
|
7861
8164
|
const thisCallIndex = callSiteIndex++;
|
|
7862
8165
|
const suffix = `_bf${thisCallIndex}`;
|
|
7863
8166
|
let body = factory.bodySource;
|
|
7864
8167
|
const internalRenames = new Set(factory.localBindings);
|
|
7865
|
-
for (const
|
|
7866
|
-
internalRenames.delete(
|
|
7867
|
-
for (const r of factory.returnTupleIdentifiers)
|
|
7868
|
-
internalRenames.delete(r);
|
|
8168
|
+
for (const ex of excludeFromSuffixRename)
|
|
8169
|
+
internalRenames.delete(ex);
|
|
7869
8170
|
for (const name of internalRenames) {
|
|
7870
8171
|
body = body.replace(new RegExp(`\\b${escapeRegex(name)}\\b`, "g"), name + suffix);
|
|
7871
8172
|
}
|
|
@@ -7876,10 +8177,10 @@ function rewriteFactoryCallsInSource(source, prescan) {
|
|
|
7876
8177
|
const wrapped = atomicArg.test(a.trim()) ? a.trim() : `(${a})`;
|
|
7877
8178
|
body = body.replace(new RegExp(`\\b${escapeRegex(p)}\\b`, "g"), wrapped);
|
|
7878
8179
|
}
|
|
7879
|
-
|
|
7880
|
-
const n
|
|
7881
|
-
|
|
7882
|
-
|
|
8180
|
+
if (renameReturnToCallerNames) {
|
|
8181
|
+
for (const [n, caller] of renameReturnToCallerNames) {
|
|
8182
|
+
body = body.replace(new RegExp(`\\b${escapeRegex(n)}\\b`, "g"), caller);
|
|
8183
|
+
}
|
|
7883
8184
|
}
|
|
7884
8185
|
edits.push({
|
|
7885
8186
|
start: stmt.getStart(sourceFile),
|
|
@@ -7909,6 +8210,12 @@ function isPascalCaseComponentFn(node) {
|
|
|
7909
8210
|
function escapeRegex(s) {
|
|
7910
8211
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7911
8212
|
}
|
|
8213
|
+
function declinedFactoryMessage(callee, d) {
|
|
8214
|
+
if (d.code === "BF112") {
|
|
8215
|
+
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
|
+
}
|
|
8217
|
+
return `Reactive factory '${callee}' cannot be inlined: ${d.detail}.`;
|
|
8218
|
+
}
|
|
7912
8219
|
function validateReactiveFactoryCalls(ctx) {
|
|
7913
8220
|
if (!ctx.componentNode)
|
|
7914
8221
|
return;
|
|
@@ -7919,26 +8226,104 @@ function validateReactiveFactoryCalls(ctx) {
|
|
|
7919
8226
|
if (!ts8.isVariableStatement(stmt))
|
|
7920
8227
|
continue;
|
|
7921
8228
|
for (const decl of stmt.declarationList.declarations) {
|
|
7922
|
-
if (!ts8.isArrayBindingPattern(decl.name))
|
|
7923
|
-
continue;
|
|
7924
8229
|
if (!decl.initializer || !ts8.isCallExpression(decl.initializer))
|
|
7925
8230
|
continue;
|
|
7926
8231
|
if (!ts8.isIdentifier(decl.initializer.expression))
|
|
7927
8232
|
continue;
|
|
7928
8233
|
const callee = decl.initializer.expression.text;
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8234
|
+
const loc = getSourceLocation(stmt, ctx.sourceFile, ctx.filePath);
|
|
8235
|
+
if (ts8.isArrayBindingPattern(decl.name)) {
|
|
8236
|
+
if (callee === "createSignal" || callee === "createMemo")
|
|
8237
|
+
continue;
|
|
8238
|
+
if (resolveEnvSignalKey(decl.initializer, ctx))
|
|
8239
|
+
continue;
|
|
8240
|
+
const declinedEntry = ctx.declinedReactiveFactories.get(callee);
|
|
8241
|
+
if (declinedEntry) {
|
|
8242
|
+
ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
|
|
8243
|
+
continue;
|
|
8244
|
+
}
|
|
8245
|
+
const objectFactory = ctx.reactiveFactories.get(callee);
|
|
8246
|
+
if (objectFactory && objectFactory.returnKind === "object") {
|
|
8247
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8248
|
+
severity: "error",
|
|
8249
|
+
message: `'${callee}' is a reactive factory that returns an object — destructure ` + `it with a matching object pattern: const { ${objectFactory.returnTupleIdentifiers.join(", ")} } = ${callee}(...)`
|
|
8250
|
+
}));
|
|
8251
|
+
continue;
|
|
8252
|
+
}
|
|
8253
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8254
|
+
severity: "error",
|
|
8255
|
+
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, ...]\`).`,
|
|
8256
|
+
suggestion: {
|
|
8257
|
+
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.`
|
|
8258
|
+
}
|
|
8259
|
+
}));
|
|
7932
8260
|
continue;
|
|
7933
|
-
|
|
8261
|
+
}
|
|
8262
|
+
if (ts8.isObjectBindingPattern(decl.name)) {
|
|
8263
|
+
validateObjectFactoryDestructure(ctx, decl.name, callee, loc);
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
}
|
|
8268
|
+
function validateObjectFactoryDestructure(ctx, pattern, callee, loc) {
|
|
8269
|
+
const factory = ctx.reactiveFactories.get(callee);
|
|
8270
|
+
if (factory) {
|
|
8271
|
+
if (factory.returnKind === "tuple") {
|
|
8272
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
7934
8273
|
severity: "error",
|
|
7935
|
-
message: `
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
8274
|
+
message: `'${callee}' is a reactive factory that returns a tuple — destructure ` + `it positionally: const [${factory.returnTupleIdentifiers.join(", ")}] = ${callee}(...)`
|
|
8275
|
+
}));
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8278
|
+
const hasUnsupportedElement = pattern.elements.some((el) => !!el.propertyName || !!el.initializer || !!el.dotDotDotToken || !ts8.isIdentifier(el.name));
|
|
8279
|
+
if (hasUnsupportedElement) {
|
|
8280
|
+
ctx.errors.push(createError(ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, {
|
|
8281
|
+
severity: "error",
|
|
8282
|
+
message: `Object destructure of reactive factory '${callee}' uses a property ` + `rename, default, or rest element; only shorthand destructuring of ` + `{ ${factory.returnTupleIdentifiers.join(", ")} } is supported.`
|
|
8283
|
+
}));
|
|
8284
|
+
return;
|
|
8285
|
+
}
|
|
8286
|
+
const unknown = pattern.elements.map((el) => ts8.isIdentifier(el.name) ? el.name.text : "").filter((name) => name && !factory.returnTupleIdentifiers.includes(name));
|
|
8287
|
+
if (unknown.length > 0) {
|
|
8288
|
+
const label = unknown.length === 1 ? "property" : "properties";
|
|
8289
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8290
|
+
severity: "error",
|
|
8291
|
+
message: `Object destructure of reactive factory '${callee}' references ${label} ` + `'${unknown.join("', '")}' not present in its return { ${factory.returnTupleIdentifiers.join(", ")} }.`
|
|
7939
8292
|
}));
|
|
8293
|
+
return;
|
|
8294
|
+
}
|
|
8295
|
+
return;
|
|
8296
|
+
}
|
|
8297
|
+
const declinedEntry = ctx.declinedReactiveFactories.get(callee);
|
|
8298
|
+
if (declinedEntry) {
|
|
8299
|
+
ctx.errors.push(createError(declinedEntry.code === "BF112" ? ErrorCodes.REACTIVE_FACTORY_MODULE_CAPTURE : ErrorCodes.REACTIVE_FACTORY_RENAME_UNSUPPORTED, loc, { severity: "error", message: declinedFactoryMessage(callee, declinedEntry) }));
|
|
8300
|
+
return;
|
|
8301
|
+
}
|
|
8302
|
+
if (ctx.reactiveShapedHelpers.has(callee)) {
|
|
8303
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8304
|
+
severity: "error",
|
|
8305
|
+
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)."
|
|
8306
|
+
}));
|
|
8307
|
+
return;
|
|
8308
|
+
}
|
|
8309
|
+
if (ctx.cleanFactoryImports.has(callee))
|
|
8310
|
+
return;
|
|
8311
|
+
let matchedImportSource = null;
|
|
8312
|
+
for (const imp of ctx.imports) {
|
|
8313
|
+
if (imp.isTypeOnly)
|
|
8314
|
+
continue;
|
|
8315
|
+
const spec = imp.specifiers.find((s) => !s.isTypeOnly && (s.alias ?? s.name) === callee);
|
|
8316
|
+
if (spec) {
|
|
8317
|
+
matchedImportSource = imp.source;
|
|
8318
|
+
break;
|
|
7940
8319
|
}
|
|
7941
8320
|
}
|
|
8321
|
+
if (matchedImportSource !== null && !matchedImportSource.startsWith("@barefootjs/") && /^(use|create)[A-Z]/.test(callee)) {
|
|
8322
|
+
ctx.errors.push(createError(ErrorCodes.UNRECOGNIZED_REACTIVE_FACTORY, loc, {
|
|
8323
|
+
severity: "error",
|
|
8324
|
+
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.`
|
|
8325
|
+
}));
|
|
8326
|
+
}
|
|
7942
8327
|
}
|
|
7943
8328
|
|
|
7944
8329
|
// src/jsx-to-ir.ts
|
|
@@ -8460,6 +8845,114 @@ function resolveFreeRefs(node, env) {
|
|
|
8460
8845
|
return resolveFreeRefsInternal(node, env, new Set);
|
|
8461
8846
|
}
|
|
8462
8847
|
|
|
8848
|
+
// src/to-locale-date-lowering.ts
|
|
8849
|
+
var TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
|
|
8850
|
+
var PROBE_UTC = new Date(Date.UTC(2001, 1, 3));
|
|
8851
|
+
var patternCache = new Map;
|
|
8852
|
+
function resolveLocaleDatePattern(locale) {
|
|
8853
|
+
const cached = patternCache.get(locale);
|
|
8854
|
+
if (cached !== undefined)
|
|
8855
|
+
return cached;
|
|
8856
|
+
const derived = derivePattern(locale);
|
|
8857
|
+
patternCache.set(locale, derived);
|
|
8858
|
+
return derived;
|
|
8859
|
+
}
|
|
8860
|
+
function derivePattern(locale) {
|
|
8861
|
+
let parts;
|
|
8862
|
+
try {
|
|
8863
|
+
const dtf = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
|
|
8864
|
+
const resolved = dtf.resolvedOptions();
|
|
8865
|
+
if (resolved.calendar !== "gregory" || resolved.numberingSystem !== "latn")
|
|
8866
|
+
return null;
|
|
8867
|
+
parts = dtf.formatToParts(PROBE_UTC);
|
|
8868
|
+
} catch {
|
|
8869
|
+
return null;
|
|
8870
|
+
}
|
|
8871
|
+
let pattern = "";
|
|
8872
|
+
for (const part of parts) {
|
|
8873
|
+
switch (part.type) {
|
|
8874
|
+
case "year":
|
|
8875
|
+
if (part.value !== "2001")
|
|
8876
|
+
return null;
|
|
8877
|
+
pattern += "YYYY";
|
|
8878
|
+
break;
|
|
8879
|
+
case "month":
|
|
8880
|
+
if (part.value === "2")
|
|
8881
|
+
pattern += "M";
|
|
8882
|
+
else if (part.value === "02")
|
|
8883
|
+
pattern += "MM";
|
|
8884
|
+
else
|
|
8885
|
+
return null;
|
|
8886
|
+
break;
|
|
8887
|
+
case "day":
|
|
8888
|
+
if (part.value === "3")
|
|
8889
|
+
pattern += "D";
|
|
8890
|
+
else if (part.value === "03")
|
|
8891
|
+
pattern += "DD";
|
|
8892
|
+
else
|
|
8893
|
+
return null;
|
|
8894
|
+
break;
|
|
8895
|
+
case "literal":
|
|
8896
|
+
if (/[YMD]/.test(part.value))
|
|
8897
|
+
return null;
|
|
8898
|
+
pattern += part.value;
|
|
8899
|
+
break;
|
|
8900
|
+
default:
|
|
8901
|
+
return null;
|
|
8902
|
+
}
|
|
8903
|
+
}
|
|
8904
|
+
if (!pattern.includes("YYYY") || !/M/.test(pattern) || !/D/.test(pattern))
|
|
8905
|
+
return null;
|
|
8906
|
+
return pattern;
|
|
8907
|
+
}
|
|
8908
|
+
function matchToLocaleDateStringCall(callee, args, metadata) {
|
|
8909
|
+
if (callee.kind !== "member" || callee.computed)
|
|
8910
|
+
return null;
|
|
8911
|
+
if (callee.property !== "toLocaleDateString" || args.length !== 2)
|
|
8912
|
+
return null;
|
|
8913
|
+
const [locale, options] = args;
|
|
8914
|
+
if (locale.kind !== "literal" || locale.literalType !== "string")
|
|
8915
|
+
return null;
|
|
8916
|
+
if (options.kind !== "object-literal" || options.properties.length !== 1)
|
|
8917
|
+
return null;
|
|
8918
|
+
const prop = options.properties[0];
|
|
8919
|
+
if (prop.key !== "timeZone")
|
|
8920
|
+
return null;
|
|
8921
|
+
if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
|
|
8922
|
+
return null;
|
|
8923
|
+
const tz = String(prop.value.value);
|
|
8924
|
+
if (!TO_LOCALE_TZ_RE.test(tz))
|
|
8925
|
+
return null;
|
|
8926
|
+
const receiverType = resolveReceiverType(callee.object, metadata, new Map);
|
|
8927
|
+
if (!receiverType || receiverType.kind !== "interface")
|
|
8928
|
+
return null;
|
|
8929
|
+
const typeName = baseTypeName(receiverType.raw);
|
|
8930
|
+
if (typeName !== "Date")
|
|
8931
|
+
return null;
|
|
8932
|
+
if (metadata.typeDefinitions.some((d) => d.name === typeName))
|
|
8933
|
+
return null;
|
|
8934
|
+
const pattern = resolveLocaleDatePattern(String(locale.value));
|
|
8935
|
+
if (pattern === null)
|
|
8936
|
+
return null;
|
|
8937
|
+
return {
|
|
8938
|
+
kind: "helper-call",
|
|
8939
|
+
helper: "format_date",
|
|
8940
|
+
args: [
|
|
8941
|
+
callee.object,
|
|
8942
|
+
{ kind: "literal", value: pattern, literalType: "string" },
|
|
8943
|
+
{ kind: "literal", value: tz, literalType: "string" }
|
|
8944
|
+
]
|
|
8945
|
+
};
|
|
8946
|
+
}
|
|
8947
|
+
var toLocaleDatePlugin = {
|
|
8948
|
+
name: "toLocaleDateString",
|
|
8949
|
+
prepare(metadata) {
|
|
8950
|
+
if (!metadata.propsType || !typeReachesDate(metadata.propsType, metadata, new Set))
|
|
8951
|
+
return null;
|
|
8952
|
+
return (callee, args) => matchToLocaleDateStringCall(callee, args, metadata);
|
|
8953
|
+
}
|
|
8954
|
+
};
|
|
8955
|
+
|
|
8463
8956
|
// src/jsx-to-ir.ts
|
|
8464
8957
|
import { toHTMLAttrName, decodeEntities } from "@barefootjs/shared";
|
|
8465
8958
|
var CLIENT_DIRECTIVE_INTERIOR_RE2 = /^\s*@client\s*$/;
|
|
@@ -8555,6 +9048,19 @@ function getDateLoweringMatcher(ctx) {
|
|
|
8555
9048
|
}
|
|
8556
9049
|
return ctx._dateLoweringMatcher;
|
|
8557
9050
|
}
|
|
9051
|
+
function getToLocaleDateLoweringMatcher(ctx) {
|
|
9052
|
+
if (ctx._toLocaleDateLoweringMatcher === undefined) {
|
|
9053
|
+
const a = ctx.analyzer;
|
|
9054
|
+
const metadataSlice = {
|
|
9055
|
+
propsType: a.propsType,
|
|
9056
|
+
propsObjectName: a.propsObjectName,
|
|
9057
|
+
propsParams: a.propsParams,
|
|
9058
|
+
typeDefinitions: a.typeDefinitions
|
|
9059
|
+
};
|
|
9060
|
+
ctx._toLocaleDateLoweringMatcher = toLocaleDatePlugin.prepare(metadataSlice);
|
|
9061
|
+
}
|
|
9062
|
+
return ctx._toLocaleDateLoweringMatcher;
|
|
9063
|
+
}
|
|
8558
9064
|
function lowerDateCalls(text, expr, ctx) {
|
|
8559
9065
|
const matcher = getDateLoweringMatcher(ctx);
|
|
8560
9066
|
if (!matcher)
|
|
@@ -8583,8 +9089,38 @@ function lowerDateCalls(text, expr, ctx) {
|
|
|
8583
9089
|
}
|
|
8584
9090
|
return restore(result);
|
|
8585
9091
|
}
|
|
9092
|
+
function lowerToLocaleDateCalls(text, expr, ctx) {
|
|
9093
|
+
const matcher = getToLocaleDateLoweringMatcher(ctx);
|
|
9094
|
+
if (!matcher)
|
|
9095
|
+
return text;
|
|
9096
|
+
const candidates = [];
|
|
9097
|
+
function visit2(n) {
|
|
9098
|
+
if (ts11.isCallExpression(n) && n.arguments.length === 2 && ts11.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
|
|
9099
|
+
candidates.push(n);
|
|
9100
|
+
}
|
|
9101
|
+
ts11.forEachChild(n, visit2);
|
|
9102
|
+
}
|
|
9103
|
+
visit2(expr);
|
|
9104
|
+
if (candidates.length === 0)
|
|
9105
|
+
return text;
|
|
9106
|
+
const { protect, restore, replaceProtectedCall } = createTemplateAwareStringProtector();
|
|
9107
|
+
let result = protect(text);
|
|
9108
|
+
for (const call of candidates) {
|
|
9109
|
+
const propAccess = call.expression;
|
|
9110
|
+
const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
|
|
9111
|
+
if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
|
|
9112
|
+
continue;
|
|
9113
|
+
const [, patternArg, tzArg] = node.args;
|
|
9114
|
+
if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
|
|
9115
|
+
continue;
|
|
9116
|
+
const receiverText = ctx.getJS(propAccess.expression);
|
|
9117
|
+
const matchText = ctx.getJS(call);
|
|
9118
|
+
result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
|
|
9119
|
+
}
|
|
9120
|
+
return restore(result);
|
|
9121
|
+
}
|
|
8586
9122
|
function rewriteBarePropRefs2(text, expr, ctx) {
|
|
8587
|
-
const dateLowered = lowerDateCalls(text, expr, ctx);
|
|
9123
|
+
const dateLowered = lowerToLocaleDateCalls(lowerDateCalls(text, expr, ctx), expr, ctx);
|
|
8588
9124
|
let propNames = getDestructuredPropNames(ctx);
|
|
8589
9125
|
if (!propNames)
|
|
8590
9126
|
return dateLowered === text ? undefined : dateLowered;
|
|
@@ -13436,7 +13972,8 @@ var RUNTIME_IMPORT_CANDIDATES = [
|
|
|
13436
13972
|
"tAfter",
|
|
13437
13973
|
"beginTurn",
|
|
13438
13974
|
"endTurn",
|
|
13439
|
-
"date"
|
|
13975
|
+
"date",
|
|
13976
|
+
"formatDate"
|
|
13440
13977
|
];
|
|
13441
13978
|
var RUNTIME_MODULE = "@barefootjs/client/runtime";
|
|
13442
13979
|
var IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
|
|
@@ -14722,7 +15259,7 @@ function importsSearchParams(metadata) {
|
|
|
14722
15259
|
function queryHrefLocalNames(metadata) {
|
|
14723
15260
|
const names = new Set;
|
|
14724
15261
|
for (const imp of metadata.imports) {
|
|
14725
|
-
if (!
|
|
15262
|
+
if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
|
|
14726
15263
|
continue;
|
|
14727
15264
|
for (const s of imp.specifiers) {
|
|
14728
15265
|
if (s.isTypeOnly || s.isNamespace || s.isDefault)
|
|
@@ -14733,10 +15270,24 @@ function queryHrefLocalNames(metadata) {
|
|
|
14733
15270
|
}
|
|
14734
15271
|
return names;
|
|
14735
15272
|
}
|
|
14736
|
-
var
|
|
15273
|
+
var CLIENT_HELPER_SOURCES = new Set([
|
|
14737
15274
|
"@barefootjs/client",
|
|
14738
15275
|
"@barefootjs/client/runtime"
|
|
14739
15276
|
]);
|
|
15277
|
+
function formatDateLocalNames(metadata) {
|
|
15278
|
+
const names = new Set;
|
|
15279
|
+
for (const imp of metadata.imports) {
|
|
15280
|
+
if (!CLIENT_HELPER_SOURCES.has(imp.source) || imp.isTypeOnly)
|
|
15281
|
+
continue;
|
|
15282
|
+
for (const s of imp.specifiers) {
|
|
15283
|
+
if (s.isTypeOnly || s.isNamespace || s.isDefault)
|
|
15284
|
+
continue;
|
|
15285
|
+
if (s.name === "formatDate")
|
|
15286
|
+
names.add(s.alias ?? s.name);
|
|
15287
|
+
}
|
|
15288
|
+
}
|
|
15289
|
+
return names;
|
|
15290
|
+
}
|
|
14740
15291
|
function matchSearchParamsMethodCall(callee, args, localNames) {
|
|
14741
15292
|
if (callee.kind !== "member" || callee.computed)
|
|
14742
15293
|
return null;
|
|
@@ -16602,6 +17153,56 @@ function getReactiveDateLoweringMatcher(ctx) {
|
|
|
16602
17153
|
};
|
|
16603
17154
|
return datePlugin.prepare(metadataSlice);
|
|
16604
17155
|
}
|
|
17156
|
+
function getReactiveToLocaleMatcher(ctx) {
|
|
17157
|
+
if (!ctx.propsType)
|
|
17158
|
+
return null;
|
|
17159
|
+
const metadataSlice = {
|
|
17160
|
+
propsType: ctx.propsType,
|
|
17161
|
+
propsObjectName: ctx.propsObjectName,
|
|
17162
|
+
propsParams: ctx.propsParams,
|
|
17163
|
+
typeDefinitions: ctx.typeDefinitions ?? []
|
|
17164
|
+
};
|
|
17165
|
+
return toLocaleDatePlugin.prepare(metadataSlice);
|
|
17166
|
+
}
|
|
17167
|
+
function lowerToLocaleCallsInReactiveExpr(expr, matcher) {
|
|
17168
|
+
if (!matcher)
|
|
17169
|
+
return expr;
|
|
17170
|
+
let sourceFile;
|
|
17171
|
+
try {
|
|
17172
|
+
sourceFile = ts14.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts14.ScriptTarget.Latest, true, ts14.ScriptKind.TS);
|
|
17173
|
+
} catch {
|
|
17174
|
+
return expr;
|
|
17175
|
+
}
|
|
17176
|
+
const stmt = sourceFile.statements[0];
|
|
17177
|
+
if (!stmt || !ts14.isExpressionStatement(stmt))
|
|
17178
|
+
return expr;
|
|
17179
|
+
const root = ts14.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
|
|
17180
|
+
const candidates = [];
|
|
17181
|
+
const visit3 = (n) => {
|
|
17182
|
+
if (ts14.isCallExpression(n) && n.arguments.length === 2 && ts14.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
|
|
17183
|
+
candidates.push(n);
|
|
17184
|
+
}
|
|
17185
|
+
ts14.forEachChild(n, visit3);
|
|
17186
|
+
};
|
|
17187
|
+
visit3(root);
|
|
17188
|
+
if (candidates.length === 0)
|
|
17189
|
+
return expr;
|
|
17190
|
+
const { protect, restore, replaceProtectedCall } = createTemplateAwareStringProtector();
|
|
17191
|
+
let result = protect(expr);
|
|
17192
|
+
for (const call of candidates) {
|
|
17193
|
+
const propAccess = call.expression;
|
|
17194
|
+
const node = matcher(tsNodeToParsedExpr(propAccess), call.arguments.map((a) => tsNodeToParsedExpr(a)));
|
|
17195
|
+
if (!node || node.kind !== "helper-call" || node.helper !== "format_date")
|
|
17196
|
+
continue;
|
|
17197
|
+
const [, patternArg, tzArg] = node.args;
|
|
17198
|
+
if (patternArg?.kind !== "literal" || tzArg?.kind !== "literal")
|
|
17199
|
+
continue;
|
|
17200
|
+
const receiverText = propAccess.expression.getText(sourceFile);
|
|
17201
|
+
const matchText = call.getText(sourceFile);
|
|
17202
|
+
result = replaceProtectedCall(result, matchText, () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`);
|
|
17203
|
+
}
|
|
17204
|
+
return restore(result);
|
|
17205
|
+
}
|
|
16605
17206
|
function lowerDateCallsInReactiveExpr(expr, matcher) {
|
|
16606
17207
|
if (!matcher)
|
|
16607
17208
|
return expr;
|
|
@@ -16641,6 +17242,7 @@ function lowerDateCallsInReactiveExpr(expr, matcher) {
|
|
|
16641
17242
|
}
|
|
16642
17243
|
function emitDynamicTextUpdates(lines, ctx) {
|
|
16643
17244
|
const dateLoweringMatcher = getReactiveDateLoweringMatcher(ctx);
|
|
17245
|
+
const toLocaleMatcher = getReactiveToLocaleMatcher(ctx);
|
|
16644
17246
|
const byExpression = new Map;
|
|
16645
17247
|
for (const elem of ctx.dynamicElements) {
|
|
16646
17248
|
const key = elem.expression;
|
|
@@ -16650,7 +17252,7 @@ function emitDynamicTextUpdates(lines, ctx) {
|
|
|
16650
17252
|
byExpression.get(key).push(elem);
|
|
16651
17253
|
}
|
|
16652
17254
|
for (const [rawExpr, elems] of byExpression) {
|
|
16653
|
-
const expr = lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher);
|
|
17255
|
+
const expr = lowerToLocaleCallsInReactiveExpr(lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher), toLocaleMatcher);
|
|
16654
17256
|
const conditionalElems = elems.filter((e) => e.insideConditional);
|
|
16655
17257
|
const normalElems = elems.filter((e) => !e.insideConditional);
|
|
16656
17258
|
if (normalElems.length > 0 || conditionalElems.length > 0) {
|
|
@@ -19875,13 +20477,14 @@ function pushDiagnostic(errors, seen, loc, method, receiverPath, isProp, typeNam
|
|
|
19875
20477
|
return;
|
|
19876
20478
|
seen.add(key);
|
|
19877
20479
|
const receiver = isProp ? `prop '${receiverPath}'` : `'${receiverPath}'`;
|
|
20480
|
+
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
20481
|
errors.push({
|
|
19879
20482
|
code: ErrorCodes.UNSUPPORTED_JSX_PATTERN,
|
|
19880
20483
|
severity: "error",
|
|
19881
20484
|
message: `Expression cannot be compiled to marked template: method '.${method}()' on ${receiver} of host type '${typeName}' has no catalogued lowering.`,
|
|
19882
20485
|
loc,
|
|
19883
20486
|
suggestion: {
|
|
19884
|
-
message:
|
|
20487
|
+
message: suggestion
|
|
19885
20488
|
}
|
|
19886
20489
|
});
|
|
19887
20490
|
}
|
|
@@ -21397,6 +22000,29 @@ function isOmitBranch(node) {
|
|
|
21397
22000
|
}
|
|
21398
22001
|
return false;
|
|
21399
22002
|
}
|
|
22003
|
+
// src/format-date-lowering.ts
|
|
22004
|
+
var UTC_LITERAL = { kind: "literal", value: "UTC", literalType: "string" };
|
|
22005
|
+
function matchFormatDateCall(callee, args, locals) {
|
|
22006
|
+
if (callee.kind !== "identifier" || !locals.has(callee.name))
|
|
22007
|
+
return null;
|
|
22008
|
+
if (args.length < 2 || args.length > 3)
|
|
22009
|
+
return null;
|
|
22010
|
+
return {
|
|
22011
|
+
kind: "helper-call",
|
|
22012
|
+
helper: "format_date",
|
|
22013
|
+
args: [args[0], args[1], args[2] ?? UTC_LITERAL]
|
|
22014
|
+
};
|
|
22015
|
+
}
|
|
22016
|
+
var formatDatePlugin = {
|
|
22017
|
+
name: "formatDate",
|
|
22018
|
+
prepare(metadata) {
|
|
22019
|
+
const locals = formatDateLocalNames(metadata);
|
|
22020
|
+
if (locals.size === 0)
|
|
22021
|
+
return null;
|
|
22022
|
+
return (callee, args) => matchFormatDateCall(callee, args, locals);
|
|
22023
|
+
}
|
|
22024
|
+
};
|
|
22025
|
+
|
|
21400
22026
|
// src/builtin-lowering-plugins.ts
|
|
21401
22027
|
var queryHrefPlugin = {
|
|
21402
22028
|
name: "queryHref",
|
|
@@ -21410,7 +22036,12 @@ var queryHrefPlugin = {
|
|
|
21410
22036
|
};
|
|
21411
22037
|
}
|
|
21412
22038
|
};
|
|
21413
|
-
var BUILTIN_LOWERING_PLUGINS = [
|
|
22039
|
+
var BUILTIN_LOWERING_PLUGINS = [
|
|
22040
|
+
queryHrefPlugin,
|
|
22041
|
+
datePlugin,
|
|
22042
|
+
formatDatePlugin,
|
|
22043
|
+
toLocaleDatePlugin
|
|
22044
|
+
];
|
|
21414
22045
|
function registerBuiltinLoweringPlugins() {
|
|
21415
22046
|
for (const plugin of BUILTIN_LOWERING_PLUGINS)
|
|
21416
22047
|
registerLoweringPlugin(plugin);
|
|
@@ -24453,6 +25084,7 @@ export {
|
|
|
24453
25084
|
formatFallbackExplanations,
|
|
24454
25085
|
formatEventSummary,
|
|
24455
25086
|
formatError,
|
|
25087
|
+
formatDateLocalNames,
|
|
24456
25088
|
formatComponentSummary,
|
|
24457
25089
|
formatComponentGraph,
|
|
24458
25090
|
formatBudgetDiff,
|