@barefootjs/jsx 0.24.1 → 0.26.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.
Files changed (46) hide show
  1. package/dist/adapters/dangerous-inner-html.d.ts +52 -24
  2. package/dist/adapters/dangerous-inner-html.d.ts.map +1 -1
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/errors.d.ts +1 -0
  5. package/dist/errors.d.ts.map +1 -1
  6. package/dist/index.js +473 -165
  7. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +15 -1
  9. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  10. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +9 -6
  11. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +11 -5
  13. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +27 -1
  15. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/reactivity.d.ts +24 -2
  18. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  19. package/dist/ir-to-client-js/types.d.ts +13 -0
  20. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  21. package/dist/to-locale-date-lowering.d.ts +31 -10
  22. package/dist/to-locale-date-lowering.d.ts.map +1 -1
  23. package/dist/types.d.ts +29 -5
  24. package/dist/types.d.ts.map +1 -1
  25. package/package.json +2 -2
  26. package/src/__tests__/dangerous-inner-html-resolver.test.ts +27 -7
  27. package/src/__tests__/nested-loop-conditional.test.ts +133 -0
  28. package/src/__tests__/profile-nested-binding-ids.test.ts +5 -2
  29. package/src/__tests__/reactive-factory-cross-file.test.ts +833 -0
  30. package/src/__tests__/reactive-factory-inlining.test.ts +527 -1
  31. package/src/__tests__/reactive-factory-rename-fidelity.test.ts +318 -0
  32. package/src/__tests__/to-locale-date-lowering.test.ts +27 -2
  33. package/src/adapters/dangerous-inner-html.ts +101 -48
  34. package/src/analyzer.ts +607 -142
  35. package/src/errors.ts +4 -0
  36. package/src/ir-to-client-js/collect-elements.ts +28 -2
  37. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +56 -2
  38. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +30 -54
  39. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +11 -5
  40. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +78 -4
  41. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +3 -25
  42. package/src/ir-to-client-js/reactivity.ts +61 -7
  43. package/src/ir-to-client-js/types.ts +13 -0
  44. package/src/rich-type-refusal.ts +3 -2
  45. package/src/to-locale-date-lowering.ts +50 -13
  46. package/src/types.ts +30 -5
@@ -99,10 +99,11 @@ function pushDiagnostic(
99
99
  // `toLocaleDateString` has a catalogued explicit-input form (#2324 slice
100
100
  // 2) — point the fix at it instead of the generic escape hatches alone.
101
101
  // The implicit-environment forms (zero-arg, locale-only, non-literal
102
- // locale, IANA timeZone) stay refused by design.
102
+ // locale, an unverifiable timeZone literal) stay refused by design;
103
+ // canonical IANA zone literals compile since #2344.
103
104
  const suggestion =
104
105
  method === 'toLocaleDateString' && typeName === 'Date'
105
- ? "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."
106
+ ? "Pass a literal locale and an explicit literal timeZone — .toLocaleDateString('ja-JP', { timeZone: 'UTC' }), a fixed '±HH:MM' offset, or a canonical IANA zone ID like 'Asia/Tokyo' (exact case; #2344) — 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."
106
107
  : 'Add /* @client */ to evaluate this expression on the client only, or pre-compute the value server-side.'
107
108
  errors.push({
108
109
  code: ErrorCodes.UNSUPPORTED_JSX_PATTERN,
@@ -34,8 +34,15 @@
34
34
  * there, feeding `formatDate` directly. An OPTIONAL union prop also
35
35
  * declines: `undefined` at runtime makes real `toLocaleDateString` read
36
36
  * the host locale, which no frozen pattern table can reproduce;
37
- * - an IANA `timeZone` name couples output to the host's tzdata version
38
- * (only `'UTC'` and fixed `±HH:MM` offsets are deterministic);
37
+ * - an IANA `timeZone` literal the build machine cannot verify (#2344):
38
+ * a named zone IS admitted when the build's own `Intl` echoes it back
39
+ * verbatim from `resolvedOptions().timeZone` — the canonical primary
40
+ * ID, the one spelling every backend's tzdata resolves identically (the
41
+ * runtime helpers resolve it through Go `time.LoadLocation`, Ruby
42
+ * tzinfo, PHP `DateTimeZone`, Perl `DateTime::TimeZone`, Python
43
+ * `zoneinfo`, Rust `chrono-tz`). Non-canonical case, link/alias names
44
+ * the probe canonicalizes away, and unknown zones all decline — the
45
+ * probe-and-verify discipline of #2334, applied to tz;
39
46
  * - an options bag the probe cannot reproduce EXACTLY in the token+table
40
47
  * vocabulary — era, dayPeriod, 2-digit year, narrow name forms,
41
48
  * non-literal option values ("faithful or loud", never approximate);
@@ -57,17 +64,46 @@ import { resolveReceiverType, baseTypeName } from './rich-type-evidence.ts'
57
64
  import { typeReachesDate } from './date-lowering.ts'
58
65
 
59
66
  /**
60
- * `timeZone` literals the lowering admits: `'UTC'` or a fixed `±HH:MM`
61
- * offset **within ECMA-402's valid offset range** (hours 00–23, minutes
62
- * 00–59). An out-of-range shape like `'+25:00'` or `'+99:99'` must DECLINE
63
- * (→ BF021), not lower: real `toLocaleDateString` throws a RangeError on
64
- * it, so compiling it would render a nonsense offset on the template
65
- * adapters while the JS-native path (Hono, and the pre-rewrite semantics
66
- * the sugar stands in for) crashes — the exact divergence the sugar exists
67
- * to rule out.
67
+ * `timeZone` literals the lowering admits without probing: `'UTC'` or a
68
+ * fixed `±HH:MM` offset **within ECMA-402's valid offset range** (hours
69
+ * 00–23, minutes 00–59). An out-of-range shape like `'+25:00'` or
70
+ * `'+99:99'` must DECLINE (→ BF021), not lower: real `toLocaleDateString`
71
+ * throws a RangeError on it, so compiling it would render a nonsense
72
+ * offset on the template adapters while the JS-native path (Hono, and the
73
+ * pre-rewrite semantics the sugar stands in for) crashes — the exact
74
+ * divergence the sugar exists to rule out. Named IANA zones are admitted
75
+ * through {@link isBuildResolvableTimeZone} instead (#2344).
68
76
  */
69
77
  export const TO_LOCALE_TZ_RE = /^(?:UTC|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/
70
78
 
79
+ /** Build-time probe cache: tz literal → verified-canonical? */
80
+ const tzProbeCache = new Map<string, boolean>()
81
+
82
+ /**
83
+ * Probe-and-verify a named-zone `timeZone` literal against the build
84
+ * machine's own `Intl` (#2344, the #2334 discipline applied to tz): admit
85
+ * it iff `resolvedOptions().timeZone` echoes the literal back VERBATIM.
86
+ * The echo check is what pins the canonical primary IANA ID — `Intl`
87
+ * itself accepts case-folded spellings and link/alias names but
88
+ * canonicalizes them in `resolvedOptions()`, and those non-canonical
89
+ * spellings are exactly where backend tzdata layers disagree (Go/Python/
90
+ * Ruby/Rust resolve the exact ID set; PHP folds case; link availability
91
+ * varies by tzdata vintage). Anything the probe can't verify declines
92
+ * (→ BF021) — the `formatDate` primitive stays available for edge cases.
93
+ */
94
+ export function isBuildResolvableTimeZone(value: string): boolean {
95
+ const cached = tzProbeCache.get(value)
96
+ if (cached !== undefined) return cached
97
+ let verified: boolean
98
+ try {
99
+ verified = new Intl.DateTimeFormat('en-US', { timeZone: value }).resolvedOptions().timeZone === value
100
+ } catch {
101
+ verified = false
102
+ }
103
+ tzProbeCache.set(value, verified)
104
+ return verified
105
+ }
106
+
71
107
  /**
72
108
  * Probe instant for pattern derivation: 2001-02-03 UTC. Month and day are
73
109
  * distinct single-digit values, so the rendered part text distinguishes both
@@ -452,8 +488,9 @@ export function matchToLocaleDateStringCall(
452
488
  if (callee.kind !== 'member' || callee.computed) return null
453
489
  if (callee.property !== 'toLocaleDateString' || args.length !== 2) return null
454
490
  const [locale, options] = args
455
- // Options bag: `timeZone` is REQUIRED (a literal 'UTC' | valid ±HH:MM
456
- // its omission would read the host timezone); every OTHER key rides into
491
+ // Options bag: `timeZone` is REQUIRED (a literal 'UTC' | valid ±HH:MM |
492
+ // probe-verified canonical IANA zone (#2344) its omission would read
493
+ // the host timezone); every OTHER key rides into
457
494
  // the Intl probe as-is when its value is a string literal (#2334's
458
495
  // fidelity rule: admit any literal options bag the probe can reproduce
459
496
  // exactly, decline everything else — dateStyle, month/weekday forms, …).
@@ -464,7 +501,7 @@ export function matchToLocaleDateStringCall(
464
501
  if (prop.value.kind !== 'literal' || prop.value.literalType !== 'string') return null
465
502
  const value = String(prop.value.value)
466
503
  if (prop.key === 'timeZone') {
467
- if (!TO_LOCALE_TZ_RE.test(value)) return null
504
+ if (!TO_LOCALE_TZ_RE.test(value) && !isBuildResolvableTimeZone(value)) return null
468
505
  tz = value
469
506
  } else {
470
507
  probeOptions[prop.key] = value
package/src/types.ts CHANGED
@@ -1455,6 +1455,19 @@ export interface NamedExportSpecifier {
1455
1455
  isTypeOnly: boolean
1456
1456
  }
1457
1457
 
1458
+ /** One identifier occurrence in a factory body that call-site inlining may
1459
+ * rename (#2341 BUG-1). Offsets are relative to ReactiveFactoryInfo.bodySource. */
1460
+ export interface FactoryRenameSite {
1461
+ name: string
1462
+ start: number
1463
+ end: number
1464
+ /** 'shorthand' = the identifier is simultaneously a property key and a
1465
+ * value/binding reference ({ name } literal, or { name } object-pattern
1466
+ * element). Renaming it must EXPAND to `name: <replacement>` to preserve
1467
+ * the key. 'plain' = ordinary reference or declaration name. */
1468
+ form: 'plain' | 'shorthand'
1469
+ }
1470
+
1458
1471
  /**
1459
1472
  * Reactive factory helper metadata (#931). Collected when a same-file
1460
1473
  * function matches the factory shape: exactly one top-level `return` whose
@@ -1500,14 +1513,23 @@ export interface ReactiveFactoryInfo {
1500
1513
  * file are dropped at prescan time.
1501
1514
  */
1502
1515
  requiredImports?: RequiredFactoryImport[]
1516
+ /**
1517
+ * Identifier occurrences within `bodySource` that call-site inlining may
1518
+ * rename (#2341 BUG-1) — collected by the same AST walk that produces
1519
+ * `bodySource`, so offsets stay in lockstep with the serialized text.
1520
+ * Same-file and cross-file factories both get this (both flow through
1521
+ * `detectReactiveFactory`).
1522
+ */
1523
+ renameSites: FactoryRenameSite[]
1503
1524
  }
1504
1525
 
1505
1526
  /**
1506
1527
  * An import the helper file holds that an inlined factory body references
1507
- * (#2332). Not a BF112 capture: the component file can import the same
1508
- * binding itself. Collected at prescan with the specifier ALREADY rewritten
1509
- * relative to the component file (or unchanged for bare/npm specifiers);
1510
- * the source rewriter injects one deduped import statement per specifier.
1528
+ * (#2332, type-only refs added #2350). Not a BF112 capture: the component
1529
+ * file can import the same binding itself. Collected at prescan with the
1530
+ * specifier ALREADY rewritten relative to the component file (or unchanged
1531
+ * for bare/npm specifiers); the source rewriter injects one deduped import
1532
+ * statement per (specifier, isTypeOnly) pair.
1511
1533
  */
1512
1534
  export interface RequiredFactoryImport {
1513
1535
  /** Local binding name as referenced inside the factory body. */
@@ -1516,6 +1538,9 @@ export interface RequiredFactoryImport {
1516
1538
  exportedName: string
1517
1539
  /** Component-file-relative specifier (`../lib/mathmod`), or the unchanged bare specifier. */
1518
1540
  specifier: string
1541
+ /** True when the factory body only ever references this in type position —
1542
+ * re-provisioned as `import type { ... }` instead of a value import. */
1543
+ isTypeOnly?: boolean
1519
1544
  }
1520
1545
 
1521
1546
  /**
@@ -1526,7 +1551,7 @@ export interface RequiredFactoryImport {
1526
1551
  * the generic BF110.
1527
1552
  */
1528
1553
  export interface DeclinedReactiveFactory {
1529
- code: 'BF111' | 'BF112' | 'BF113'
1554
+ code: 'BF111' | 'BF112' | 'BF113' | 'BF114'
1530
1555
  /** Detail spliced into the call-site message (e.g. offending identifier list). */
1531
1556
  detail: string
1532
1557
  /** Definition site (in the helper file for cross-file declines). */