@barefootjs/jsx 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/errors.ts CHANGED
@@ -89,6 +89,8 @@ export const ErrorCodes = {
89
89
  UNRECOGNIZED_REACTIVE_FACTORY: 'BF110',
90
90
  REACTIVE_FACTORY_RENAME_UNSUPPORTED: 'BF111',
91
91
  REACTIVE_FACTORY_MODULE_CAPTURE: 'BF112',
92
+ REACTIVE_FACTORY_IMPORT_COLLISION: 'BF113',
93
+ REACTIVE_FACTORY_PARAM_SHADOWED: 'BF114',
92
94
  } as const
93
95
 
94
96
  export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]
@@ -178,6 +180,14 @@ const errorMessages: Record<ErrorCode, string> = {
178
180
  'body cannot be inlined into the component file. Move those helpers into the ' +
179
181
  'component file, pass them to the factory as parameters, or define the factory ' +
180
182
  'in the component file.',
183
+
184
+ [ErrorCodes.REACTIVE_FACTORY_IMPORT_COLLISION]:
185
+ 'Inlining an imported reactive factory requires re-importing one of its helper ' +
186
+ 'imports into this file, but that name is already bound here to something else. ' +
187
+ "Rename the conflicting binding in this file, or alias the import in the factory's own file.",
188
+
189
+ [ErrorCodes.REACTIVE_FACTORY_PARAM_SHADOWED]:
190
+ 'Reactive factory parameter is shadowed by a nested declaration inside the factory body, so argument substitution at the inline site would be ambiguous. Rename the inner binding so it does not collide with the parameter.',
181
191
  }
182
192
 
183
193
  // =============================================================================
@@ -21,11 +21,15 @@ import type { LoweringNode, LoweringPlugin } from './lowering-registry.ts'
21
21
  import { formatDateLocalNames } from './adapters/env-signal.ts'
22
22
 
23
23
  const UTC_LITERAL: ParsedExpr = { kind: 'literal', value: 'UTC', literalType: 'string' }
24
+ const EMPTY_NAMES: ParsedExpr = { kind: 'array-literal', elements: [], raw: '[]' } as ParsedExpr
24
25
 
25
26
  /**
26
- * Recognise `formatDate(date, pattern[, timeZone])` against the component's
27
- * local import bindings, or decline (null): a non-identifier callee, a name
28
- * not bound to the `@barefootjs/client` import, or an arity outside 2–3.
27
+ * Recognise `formatDate(date, pattern[, timeZone[, names]])` against the
28
+ * component's local import bindings, or decline (null): a non-identifier
29
+ * callee, a name not bound to the `@barefootjs/client` import, or an arity
30
+ * outside 2–4. The canonical helper arity is 4 (#2334): omitted `timeZone` /
31
+ * `names` normalize to the `'UTC'` literal and the empty table the client
32
+ * function defaults to, so backend helpers stay fixed-arity.
29
33
  */
30
34
  export function matchFormatDateCall(
31
35
  callee: ParsedExpr,
@@ -33,11 +37,11 @@ export function matchFormatDateCall(
33
37
  locals: ReadonlySet<string>,
34
38
  ): LoweringNode | null {
35
39
  if (callee.kind !== 'identifier' || !locals.has(callee.name)) return null
36
- if (args.length < 2 || args.length > 3) return null
40
+ if (args.length < 2 || args.length > 4) return null
37
41
  return {
38
42
  kind: 'helper-call',
39
43
  helper: 'format_date',
40
- args: [args[0], args[1], args[2] ?? UTC_LITERAL],
44
+ args: [args[0], args[1], args[2] ?? UTC_LITERAL, args[3] ?? EMPTY_NAMES],
41
45
  }
42
46
  }
43
47
 
@@ -11,7 +11,7 @@ import type { ClientJsContext } from './types.ts'
11
11
  import { toHtmlAttrName, varSlotId, PROPS_PARAM } from './utils.ts'
12
12
  import { createTemplateAwareStringProtector } from './html-template.ts'
13
13
  import { datePlugin, DATE_METHODS } from '../date-lowering.ts'
14
- import { toLocaleDatePlugin } from '../to-locale-date-lowering.ts'
14
+ import { toLocaleDatePlugin, foldedArgToClientJs } from '../to-locale-date-lowering.ts'
15
15
  import { tsNodeToParsedExpr } from '../expression-parser.ts'
16
16
  import type { LoweringMatcher } from '../lowering-registry.ts'
17
17
 
@@ -181,8 +181,17 @@ function lowerToLocaleCallsInReactiveExpr(expr: string, matcher: LoweringMatcher
181
181
  call.arguments.map((a) => tsNodeToParsedExpr(a)),
182
182
  )
183
183
  if (!node || node.kind !== 'helper-call' || node.helper !== 'format_date') continue
184
- const [, patternArg, tzArg] = node.args
185
- if (patternArg?.kind !== 'literal' || tzArg?.kind !== 'literal') continue
184
+ const [, patternArg, tzArg, namesArg] = node.args
185
+ if (!patternArg || tzArg?.kind !== 'literal') continue
186
+ const localeText = call.arguments[0].getText(sourceFile)
187
+ const patternJs = foldedArgToClientJs(patternArg, localeText)
188
+ if (patternJs === null) continue
189
+ // The names table (#2334) — omitted when empty, same as the static path.
190
+ let namesJs: string | null = null
191
+ if (namesArg && !(namesArg.kind === 'array-literal' && namesArg.elements.length === 0)) {
192
+ namesJs = foldedArgToClientJs(namesArg, localeText)
193
+ if (namesJs === null) continue
194
+ }
186
195
  const receiverText = propAccess.expression.getText(sourceFile)
187
196
  const matchText = call.getText(sourceFile)
188
197
  // The call text contains string literals (placeholders in the protected
@@ -191,7 +200,8 @@ function lowerToLocaleCallsInReactiveExpr(expr: string, matcher: LoweringMatcher
191
200
  result = replaceProtectedCall(
192
201
  result,
193
202
  matchText,
194
- () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`,
203
+ () =>
204
+ `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ''})`,
195
205
  )
196
206
  }
197
207
  return restore(result)
package/src/jsx-to-ir.ts CHANGED
@@ -49,7 +49,7 @@ import { resolveFreeRefs, isNameBound as isNameBoundInEnv, type BindingEnvironme
49
49
  import { computeFileScope } from './ir-to-client-js/component-scope.ts'
50
50
  import { createTemplateAwareStringProtector } from './ir-to-client-js/html-template.ts'
51
51
  import { datePlugin, DATE_METHODS } from './date-lowering.ts'
52
- import { toLocaleDatePlugin } from './to-locale-date-lowering.ts'
52
+ import { toLocaleDatePlugin, foldedArgToClientJs } from './to-locale-date-lowering.ts'
53
53
  import type { LoweringMatcher } from './lowering-registry.ts'
54
54
  import { extractFreeIdentifiersFromNode, initializerShapeContainsJsx } from './analyzer.ts'
55
55
  import { iterateJsTokens, replaceInExprContexts } from './scanner/js-scanner.ts'
@@ -462,8 +462,18 @@ function lowerToLocaleDateCalls(text: string, expr: ts.Node, ctx: TransformConte
462
462
  call.arguments.map((a) => tsNodeToParsedExpr(a)),
463
463
  )
464
464
  if (!node || node.kind !== 'helper-call' || node.helper !== 'format_date') continue
465
- const [, patternArg, tzArg] = node.args
466
- if (patternArg?.kind !== 'literal' || tzArg?.kind !== 'literal') continue
465
+ const [, patternArg, tzArg, namesArg] = node.args
466
+ if (!patternArg || tzArg?.kind !== 'literal') continue
467
+ const localeText = ctx.getJS(call.arguments[0])
468
+ const patternJs = foldedArgToClientJs(patternArg, localeText)
469
+ if (patternJs === null) continue
470
+ // The names table (#2334) — omitted from the client call when empty
471
+ // (the client function defaults to []).
472
+ let namesJs: string | null = null
473
+ if (namesArg && !(namesArg.kind === 'array-literal' && namesArg.elements.length === 0)) {
474
+ namesJs = foldedArgToClientJs(namesArg, localeText)
475
+ if (namesJs === null) continue
476
+ }
467
477
  const receiverText = ctx.getJS(propAccess.expression)
468
478
  const matchText = ctx.getJS(call)
469
479
  // Unlike `lowerDateCalls`' zero-arg needle, this call text CONTAINS
@@ -473,7 +483,8 @@ function lowerToLocaleDateCalls(text: string, expr: ts.Node, ctx: TransformConte
473
483
  result = replaceProtectedCall(
474
484
  result,
475
485
  matchText,
476
- () => `formatDate(${receiverText}, ${JSON.stringify(patternArg.value)}, ${JSON.stringify(tzArg.value)})`,
486
+ () =>
487
+ `formatDate(${receiverText}, ${patternJs}, ${JSON.stringify(tzArg.value)}${namesJs !== null ? `, ${namesJs}` : ''})`,
477
488
  )
478
489
  }
479
490
  return restore(result)