@barefootjs/jsx 0.16.0 → 0.17.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 (57) hide show
  1. package/dist/adapters/env-signal.d.ts +38 -15
  2. package/dist/adapters/env-signal.d.ts.map +1 -1
  3. package/dist/adapters/jsx-adapter.d.ts.map +1 -1
  4. package/dist/adapters/parsed-expr-emitter.d.ts +7 -6
  5. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  6. package/dist/analyzer-context.d.ts +29 -1
  7. package/dist/analyzer-context.d.ts.map +1 -1
  8. package/dist/analyzer.d.ts.map +1 -1
  9. package/dist/builtin-lowering-plugins.d.ts +34 -0
  10. package/dist/builtin-lowering-plugins.d.ts.map +1 -0
  11. package/dist/expression-parser.d.ts +219 -163
  12. package/dist/expression-parser.d.ts.map +1 -1
  13. package/dist/index.d.ts +7 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +6754 -6129
  16. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/plan/build-declaration-emit.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/plan/declaration-emit.d.ts +9 -0
  19. package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
  20. package/dist/jsx-to-ir.d.ts.map +1 -1
  21. package/dist/lowering-registry.d.ts +122 -0
  22. package/dist/lowering-registry.d.ts.map +1 -0
  23. package/dist/query-href-lowering.d.ts +63 -0
  24. package/dist/query-href-lowering.d.ts.map +1 -0
  25. package/dist/ssr-defaults.d.ts.map +1 -1
  26. package/dist/types.d.ts +169 -11
  27. package/dist/types.d.ts.map +1 -1
  28. package/package.json +2 -2
  29. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +68 -3
  30. package/src/__tests__/analyzer.test.ts +53 -0
  31. package/src/__tests__/expression-parser.test.ts +703 -391
  32. package/src/__tests__/ir-reduce-op.test.ts +18 -21
  33. package/src/__tests__/ir-sort-comparator.test.ts +19 -20
  34. package/src/__tests__/lowering-registry.test.ts +141 -0
  35. package/src/__tests__/primitive-resolver-alias.test.ts +23 -0
  36. package/src/__tests__/query-href-recognition.test.ts +58 -0
  37. package/src/__tests__/serialize-parsed-expr.test.ts +204 -0
  38. package/src/__tests__/unsupported-expression.test.ts +98 -4
  39. package/src/adapters/env-signal.ts +60 -21
  40. package/src/adapters/jsx-adapter.ts +17 -0
  41. package/src/adapters/parsed-expr-emitter.ts +39 -41
  42. package/src/analyzer-context.ts +72 -27
  43. package/src/analyzer.ts +226 -9
  44. package/src/builtin-lowering-plugins.ts +54 -0
  45. package/src/expression-parser.ts +1183 -927
  46. package/src/index.ts +26 -3
  47. package/src/ir-to-client-js/csr-substitute.ts +5 -0
  48. package/src/ir-to-client-js/plan/build-declaration-emit.ts +16 -0
  49. package/src/ir-to-client-js/plan/declaration-emit.ts +9 -0
  50. package/src/ir-to-client-js/stringify/declaration-emit.ts +11 -0
  51. package/src/jsx-to-ir.ts +182 -43
  52. package/src/lowering-registry.ts +160 -0
  53. package/src/query-href-lowering.ts +147 -0
  54. package/src/ssr-defaults.ts +5 -1
  55. package/src/types.ts +171 -12
  56. package/src/__tests__/flatmap-support.test.ts +0 -218
  57. package/src/__tests__/reduce-op.test.ts +0 -201
@@ -943,7 +943,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
943
943
  export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
944
944
  `;
945
945
 
946
- exports[`docs/core/rendering/jsx-compatibility.md doc-examples L168 — ✅ Use /* @client */ 1`] = `
946
+ exports[`docs/core/rendering/jsx-compatibility.md doc-examples L167 — ✅ Value-producing block bodies normalize (let-inline) and lower everywhere 1`] = `
947
947
  "import { $, $t, __bfText, createComponent, createEffect, createSignal, escapeText, hydrate, initChild, mapArray, renderChild } from '@barefootjs/client/runtime'
948
948
 
949
949
  export function initTodoItem(__scope, _p = {}) {
@@ -997,14 +997,79 @@ export function initExample(__scope, _p = {}) {
997
997
 
998
998
  const [_s1] = $(__scope, 's1')
999
999
 
1000
- mapArray(() => items().sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }), _s1, (item) => String(item.id), (item, __idx, __existing) => {
1000
+ mapArray(() => items().toSorted((a, b) => a.name > b.name ? 1 : -1), _s1, (item) => String(item.id), (item, __idx, __existing) => {
1001
1001
  if (__existing) { initChild('Item__b3c36eee', __existing, { get item() { return item() } }); return __existing }
1002
1002
  return createComponent('Item__b3c36eee', { get item() { return item() } }, item().id)
1003
1003
  }, 'l0')
1004
1004
 
1005
1005
  }
1006
1006
 
1007
- hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
1007
+ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).toSorted((a, b) => a.name > b.name ? 1 : -1).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
1008
+ export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
1009
+ `;
1010
+
1011
+ exports[`docs/core/rendering/jsx-compatibility.md doc-examples L182 — ✅ Use /* @client */ 1`] = `
1012
+ "import { $, $t, __bfText, createComponent, createEffect, createSignal, escapeText, hydrate, initChild, mapArray, renderChild } from '@barefootjs/client/runtime'
1013
+
1014
+ export function initTodoItem(__scope, _p = {}) {
1015
+ if (!__scope) return
1016
+ const __scopeId = __scope.getAttribute('bf-s')
1017
+
1018
+ const [_s0] = $t(__scope, 's0')
1019
+
1020
+ let __anchor_s0 = _s0
1021
+ createEffect(() => {
1022
+ const __val = String(_p.todo)
1023
+ __anchor_s0 = __bfText(__anchor_s0, __val)
1024
+ })
1025
+
1026
+ }
1027
+
1028
+ hydrate('TodoItem__b3c36eee', { init: initTodoItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${escapeText(String(_p.todo))}<!--/--></li>\` })
1029
+ export function TodoItem(_p, __bfKey) { return createComponent('TodoItem__b3c36eee', _p, __bfKey) }
1030
+ export function initItem(__scope, _p = {}) {
1031
+ if (!__scope) return
1032
+ const __scopeId = __scope.getAttribute('bf-s')
1033
+
1034
+ const [_s0] = $t(__scope, 's0')
1035
+
1036
+ let __anchor_s0 = _s0
1037
+ createEffect(() => {
1038
+ const __val = String(_p.item)
1039
+ __anchor_s0 = __bfText(__anchor_s0, __val)
1040
+ })
1041
+
1042
+ }
1043
+
1044
+ hydrate('Item__b3c36eee', { init: initItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${escapeText(String(_p.item))}<!--/--></li>\` })
1045
+ export function Item(_p, __bfKey) { return createComponent('Item__b3c36eee', _p, __bfKey) }
1046
+ function initDashboard() {}
1047
+
1048
+ hydrate('Dashboard__b3c36eee', { init: initDashboard, template: (_p) => \`<div>D</div>\` })
1049
+ export function Dashboard(_p, __bfKey) { return createComponent('Dashboard__b3c36eee', _p, __bfKey) }
1050
+ export function initExample(__scope, _p = {}) {
1051
+ if (!__scope) return
1052
+ const __scopeId = __scope.getAttribute('bf-s')
1053
+
1054
+ const children = _p.children
1055
+ const [count, setCount] = createSignal(0)
1056
+ const [isLoggedIn] = createSignal(false)
1057
+ const [todos] = createSignal([])
1058
+ const [items] = createSignal([])
1059
+ const [filter] = createSignal('all')
1060
+ const [accepted] = createSignal(false)
1061
+ const [text, setText] = createSignal('')
1062
+
1063
+ const [_s1] = $(__scope, 's1')
1064
+
1065
+ mapArray(() => items().sort((a, b) => { let r = 0; r = a.name > b.name ? 1 : -1; return r }), _s1, (item) => String(item.id), (item, __idx, __existing) => {
1066
+ if (__existing) { initChild('Item__b3c36eee', __existing, { get item() { return item() } }); return __existing }
1067
+ return createComponent('Item__b3c36eee', { get item() { return item() } }, item().id)
1068
+ }, 'l0')
1069
+
1070
+ }
1071
+
1072
+ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).sort((a, b) => { let r = 0; r = a.name > b.name ? 1 : -1; return r }).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
1008
1073
  export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
1009
1074
  `;
1010
1075
 
@@ -495,3 +495,56 @@ describe('analyzeComponent', () => {
495
495
  })
496
496
  })
497
497
  })
498
+
499
+ // #2040: complete, value-producing block-bodied memos are folded to a single
500
+ // `parsed` expression so adapters lower them through the expression path
501
+ // (retiring the per-idiom block recognizers). Idempotent reactive getter reads
502
+ // count as pure, so a guard read on several branches still folds.
503
+ describe('block-bodied memo → parsed fold (#2040)', () => {
504
+ test('guard-and-return-const memo folds to a conditional `parsed`', () => {
505
+ const source = `
506
+ 'use client'
507
+ import { createSignal, createMemo } from '@barefootjs/client'
508
+ const ALL = ['a', 'b']
509
+ export function Tags() {
510
+ const [sel, setSel] = createSignal<string | null>(null)
511
+ const visible = createMemo(() => {
512
+ const k = sel()
513
+ if (!k) return ALL
514
+ return ALL.filter(t => t === k)
515
+ })
516
+ return <ul>{visible().map(t => (<li key={t}>{t}</li>))}</ul>
517
+ }
518
+ `
519
+ const ctx = analyzeComponent(source, 'Tags.tsx')
520
+ const memo = ctx.memos.find(m => m.name === 'visible')
521
+ expect(memo?.parsedBlockComplete).toBe(true)
522
+ // `const k = sel(); if (!k) return ALL; return ALL.filter(...)` →
523
+ // `!sel() ? ALL : ALL.filter(...)`. `sel()` (a signal read) inlined twice.
524
+ expect(memo?.parsed?.kind).toBe('conditional')
525
+ const cond = memo?.parsed as { kind: 'conditional'; test: any; consequent: any }
526
+ expect(cond.test).toEqual({ kind: 'unary', op: '!', argument: { kind: 'call', callee: { kind: 'identifier', name: 'sel' }, args: [] } })
527
+ expect(cond.consequent).toEqual({ kind: 'identifier', name: 'ALL' })
528
+ })
529
+
530
+ test('imperative block-bodied memo leaves `parsed` unset', () => {
531
+ const source = `
532
+ 'use client'
533
+ import { createSignal, createMemo } from '@barefootjs/client'
534
+ export function C() {
535
+ const [n, setN] = createSignal(0)
536
+ const total = createMemo(() => {
537
+ let s = 0
538
+ for (const x of [1, 2, 3]) s += x
539
+ return s + n()
540
+ })
541
+ return <div>{total()}</div>
542
+ }
543
+ `
544
+ const ctx = analyzeComponent(source, 'C.tsx')
545
+ const memo = ctx.memos.find(m => m.name === 'total')
546
+ // The `for` loop can't be represented → tolerant parser drops it →
547
+ // parsedBlockComplete is false → no fold → parsed stays undefined.
548
+ expect(memo?.parsed).toBeUndefined()
549
+ })
550
+ })