@barefootjs/jsx 0.26.2 → 0.26.4

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 (98) hide show
  1. package/dist/adapters/interface.d.ts +29 -0
  2. package/dist/adapters/interface.d.ts.map +1 -1
  3. package/dist/adapters/jsx-adapter.d.ts +9 -0
  4. package/dist/adapters/jsx-adapter.d.ts.map +1 -1
  5. package/dist/adapters/loop-bound-names.d.ts.map +1 -1
  6. package/dist/adapters/parsed-expr-emitter.d.ts +0 -10
  7. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  8. package/dist/adapters/test-adapter.d.ts.map +1 -1
  9. package/dist/analyzer-context.d.ts +11 -1
  10. package/dist/analyzer-context.d.ts.map +1 -1
  11. package/dist/analyzer.d.ts +40 -1
  12. package/dist/analyzer.d.ts.map +1 -1
  13. package/dist/expression-parser.d.ts.map +1 -1
  14. package/dist/index.js +1086 -269
  15. package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +14 -1
  18. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  19. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  25. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +16 -1
  26. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +39 -0
  28. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  29. package/dist/ir-to-client-js/control-flow/shared.d.ts +12 -1
  30. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  31. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts +1 -1
  32. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  33. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +19 -0
  34. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  35. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/html-template.d.ts +67 -1
  37. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/imports.d.ts +2 -2
  39. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
  41. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/types.d.ts +29 -4
  43. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  44. package/dist/jsx-to-ir.d.ts.map +1 -1
  45. package/dist/loop-destructure.d.ts.map +1 -1
  46. package/dist/strip-types.d.ts +18 -0
  47. package/dist/strip-types.d.ts.map +1 -1
  48. package/dist/types.d.ts +143 -32
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +277 -33
  52. package/src/__tests__/client-js-generation.test.ts +18 -5
  53. package/src/__tests__/compiler-runtime-contract.test.ts +4 -4
  54. package/src/__tests__/compiler-stress-1244.test.ts +12 -1
  55. package/src/__tests__/delegated-handler-preamble.test.ts +153 -0
  56. package/src/__tests__/event-delegation-scope-2367.test.ts +108 -0
  57. package/src/__tests__/flatmap-segments.test.ts +262 -0
  58. package/src/__tests__/map-arbitrary-body.test.ts +226 -0
  59. package/src/__tests__/map-body-no-silent-divergence.test.ts +405 -0
  60. package/src/__tests__/map-multi-return-body.test.ts +172 -0
  61. package/src/__tests__/preamble-region-patch.test.ts +150 -0
  62. package/src/__tests__/static-loop-csr-materialize.test.ts +3 -2
  63. package/src/__tests__/unsupported-expression.test.ts +20 -2
  64. package/src/adapters/interface.ts +40 -0
  65. package/src/adapters/jsx-adapter.ts +10 -0
  66. package/src/adapters/loop-bound-names.ts +6 -2
  67. package/src/adapters/parsed-expr-emitter.ts +5 -10
  68. package/src/adapters/test-adapter.ts +10 -0
  69. package/src/analyzer-context.ts +35 -1
  70. package/src/analyzer.ts +162 -24
  71. package/src/compiler.ts +2 -2
  72. package/src/expression-parser.ts +27 -19
  73. package/src/ir-to-client-js/build-references.ts +19 -2
  74. package/src/ir-to-client-js/collect-elements.ts +69 -13
  75. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +14 -1
  76. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +18 -5
  77. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +4 -0
  78. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +15 -2
  79. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +32 -3
  80. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +26 -3
  81. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +53 -2
  82. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +16 -1
  83. package/src/ir-to-client-js/control-flow/plan/loop.ts +40 -0
  84. package/src/ir-to-client-js/control-flow/shared.ts +28 -2
  85. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +25 -3
  86. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +76 -21
  87. package/src/ir-to-client-js/control-flow/stringify/loop.ts +65 -1
  88. package/src/ir-to-client-js/control-flow.ts +11 -0
  89. package/src/ir-to-client-js/html-template.ts +244 -39
  90. package/src/ir-to-client-js/imports.ts +1 -1
  91. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +22 -10
  92. package/src/ir-to-client-js/reactivity.ts +6 -0
  93. package/src/ir-to-client-js/types.ts +28 -3
  94. package/src/jsx-to-ir.ts +1059 -167
  95. package/src/loop-destructure.ts +9 -5
  96. package/src/rich-type-refusal.ts +6 -2
  97. package/src/strip-types.ts +47 -0
  98. package/src/types.ts +159 -35
@@ -1,4 +1,5 @@
1
1
  import type { IRLoop, IRNode, AttrValue, IRTemplatePart } from './types.ts'
2
+ import { preambleAnalysisText, preambleAnalysisTemplateText } from './types.ts'
2
3
 
3
4
  /**
4
5
  * True when a loop's `.map()` destructure param is one of the shapes this
@@ -154,12 +155,15 @@ function restNamesMisused(loop: IRLoop, names: string[]): boolean {
154
155
  check(l.array)
155
156
  check(l.templateArray)
156
157
  check(l.key)
157
- check(l.mapPreamble)
158
- check(l.templateMapPreamble)
158
+ if (l.preamble) {
159
+ check(preambleAnalysisText(l.preamble))
160
+ check(preambleAnalysisTemplateText(l.preamble))
161
+ }
159
162
  if (l.flatMapCallback) {
160
- check(l.flatMapCallback.body)
161
- check(l.flatMapCallback.templateBody)
162
- l.flatMapCallback.fragments.forEach(f => visit(f.ir))
163
+ check(preambleAnalysisText(l.flatMapCallback))
164
+ for (const seg of l.flatMapCallback.segments) {
165
+ if (seg.kind === 'jsx') visit(seg.ir)
166
+ }
163
167
  }
164
168
  l.children.forEach(visit)
165
169
  }
@@ -299,8 +299,12 @@ function walkNode(
299
299
  for (const nested of node.nestedComponents ?? []) {
300
300
  for (const child of nested.children) walkNode(child, meta, loopBindings, matchers, errors, seen)
301
301
  }
302
- for (const frag of node.flatMapCallback?.fragments ?? []) {
303
- walkNode(frag.ir, meta, loopBindings, matchers, errors, seen)
302
+ for (const seg of node.flatMapCallback?.segments ?? []) {
303
+ if (seg.kind === 'jsx') walkNode(seg.ir, meta, loopBindings, matchers, errors, seen)
304
+ }
305
+ // Preamble leaves (array-builder bodies) are nested IR the same way.
306
+ for (const seg of node.preamble?.segments ?? []) {
307
+ if (seg.kind === 'jsx') walkNode(seg.ir, meta, loopBindings, matchers, errors, seen)
304
308
  }
305
309
  break
306
310
  }
@@ -71,6 +71,53 @@ export function reconstructWithoutTypes(
71
71
  return result
72
72
  }
73
73
 
74
+ /**
75
+ * Split a node's text into JS/marker segments: type ranges are stripped from
76
+ * the JS text, and each `markers` span becomes its own segment boundary
77
+ * (emitting `{ marker: i }` for the i-th marker, in the order given). Used by
78
+ * the Stage-3 preamble collector to carry mixed content (JS text + JSX leaves)
79
+ * as structured segments instead of a sentinel-bearing string — the marker
80
+ * index maps to the compiled leaf IR. Markers are absolute-position,
81
+ * non-overlapping sub-spans of `node`; a type range inside a marker span is
82
+ * subsumed.
83
+ */
84
+ export function reconstructAsSegments(
85
+ node: ts.Node,
86
+ sourceFile: ts.SourceFile,
87
+ ranges: ExcludeRange[],
88
+ markers: Array<{ start: number; end: number }>
89
+ ): Array<{ js: string } | { marker: number }> {
90
+ const nodeStart = node.getStart(sourceFile)
91
+ const nodeEnd = node.getEnd()
92
+ const fullText = sourceFile.text
93
+
94
+ type Edit = { start: number; end: number; marker: number | null }
95
+ const edits: Edit[] = []
96
+ for (const r of ranges) {
97
+ if (r.end <= nodeStart || r.start >= nodeEnd) continue
98
+ edits.push({ start: r.start, end: r.end, marker: null })
99
+ }
100
+ markers.forEach((m, i) => edits.push({ start: m.start, end: m.end, marker: i }))
101
+ edits.sort((a, b) => a.start - b.start || b.end - a.end)
102
+
103
+ const segments: Array<{ js: string } | { marker: number }> = []
104
+ let jsBuf = ''
105
+ let pos = nodeStart
106
+ for (const edit of edits) {
107
+ if (edit.start < pos) continue // subsumed by an already-emitted span
108
+ jsBuf += fullText.slice(pos, edit.start)
109
+ if (edit.marker !== null) {
110
+ if (jsBuf) segments.push({ js: jsBuf })
111
+ jsBuf = ''
112
+ segments.push({ marker: edit.marker })
113
+ }
114
+ pos = edit.end
115
+ }
116
+ if (pos < nodeEnd) jsBuf += fullText.slice(pos, nodeEnd)
117
+ if (jsBuf) segments.push({ js: jsBuf })
118
+ return segments
119
+ }
120
+
74
121
  function mergeRanges(ranges: ExcludeRange[]): ExcludeRange[] {
75
122
  if (ranges.length === 0) return []
76
123
  const merged: ExcludeRange[] = [ranges[0]]
package/src/types.ts CHANGED
@@ -371,6 +371,24 @@ export interface IRExpression {
371
371
  loc: SourceLocation
372
372
  /** When true, expression should be evaluated on client side only */
373
373
  clientOnly?: boolean
374
+ /**
375
+ * Stage 3 / D4 (spec/callback-fidelity.md) — this expression child references
376
+ * an array built by an arbitrary `.map()` preamble (`return <tr>{out}</tr>`
377
+ * where `out` is `push`-populated with element strings). Phase-2 string-
378
+ * template emission must join it (`Array.isArray(out) ? out.join('') : …`)
379
+ * rather than `String([])`-comma-collapse it. JSX SSR adapters ignore the
380
+ * flag (their JSX runtime renders an array child natively).
381
+ */
382
+ joinArrayChild?: boolean
383
+ /**
384
+ * True when this expression was classified as a loop's preamble-patched
385
+ * region (#2389 — see `IRLoop.preambleRegions`): its free identifiers
386
+ * intersect the enclosing loop's `preamble.declaredNames`. Excludes the
387
+ * node from `collectLoopChildReactiveTexts` (which patches via
388
+ * `.textContent`, wrong for markup) regardless of `reactive` / `slotId` —
389
+ * the region-patch effect (`patchSlotRange`) is its only wiring.
390
+ */
391
+ preambleRegion?: boolean
374
392
  /** When true, expression calls signal getters or memos (has reactive `foo()` pattern). */
375
393
  callsReactiveGetters?: boolean
376
394
  /** When true, expression contains function call(s) — any `identifier()` pattern (computed from AST). */
@@ -678,21 +696,43 @@ export interface IRLoop {
678
696
  */
679
697
  bodyIsItemConditional?: boolean
680
698
 
681
- /**
682
- * Raw JS of pre-return statements in block body .map() callback.
683
- * Example: `items.map(item => { const label = item.name.toUpperCase(); return <li>{label}</li> })`
684
- * stores "const label = item.name.toUpperCase();" as mapPreamble.
685
- */
686
- mapPreamble?: string
687
- /** Pre-transformed mapPreamble with destructured prop refs rewritten to _p.xxx. */
688
- templateMapPreamble?: string
689
-
690
699
  /** Type annotation for loop param (e.g., 'Desk'), preserved for .tsx output */
691
700
  paramType?: string
692
701
  /** Type annotation for loop index param (e.g., 'number'), preserved for .tsx output */
693
702
  indexType?: string
694
- /** mapPreamble with TypeScript type annotations preserved, for .tsx output */
695
- typedMapPreamble?: string
703
+ /**
704
+ * Structured pre-return statements of a block-body `.map()` callback
705
+ * (Stage 3 root cure, spec/callback-fidelity.md). Replaces the former
706
+ * `mapPreamble` / `templateMapPreamble` / `typedMapPreamble` string carriers
707
+ * and `preambleFragments`: mixed content (JS text + JSX leaves) is carried as
708
+ * a segment list, never as a sentinel-bearing string, so an emitter that
709
+ * lacks preamble support cannot accidentally interpolate it — rendering goes
710
+ * through `renderPreamble()` (html-template.ts) or not at all.
711
+ */
712
+ preamble?: MapCallbackPreamble
713
+
714
+ /**
715
+ * Expression children of the loop body whose free identifiers intersect
716
+ * `preamble.declaredNames` (#2389, patch-on-update follow-up to the Stage 3
717
+ * root cure) — e.g. `{cells}` in `arr.map(t => { const cells = []; ...;
718
+ * return <tr>{cells}<td>{t.name}</td></tr> })`. `mapArray` reuses the same
719
+ * DOM node on a same-key item update via per-item `setItem`, re-running
720
+ * only the wired text/attr slots — a preamble-derived child has NEITHER
721
+ * (it's a bare interpolation, `joinArrayChild` array-join or otherwise),
722
+ * so without this it freezes at its mount-time value forever. Each entry's
723
+ * `slotId` is emitted as the usual `<!--bf:sN-->...<!--/-->` marker pair
724
+ * (same door as a reactive text slot — `irToHtmlTemplate` / Hono's
725
+ * `renderExpression` key off `slotId` alone), but the CLIENT wiring is a
726
+ * distinct `patchSlotRange`-based region-patch effect (`preambleRegions`
727
+ * in the client-JS loop plan), NOT a `reactiveTexts` entry — patching via
728
+ * `.textContent` would escape markup that a `joinArrayChild` region must
729
+ * render raw. `collectLoopChildReactiveTexts` excludes any node collected
730
+ * here (`IRExpression.preambleRegion`) so a qualifying expression is never
731
+ * double-wired. Populated only when `preamble` is set and the loop is NOT
732
+ * `isStaticArray` — a static array's SSR-rendered items are never
733
+ * recreated via signals, so there is nothing to patch.
734
+ */
735
+ preambleRegions?: PreambleRegionSource[]
696
736
 
697
737
  /**
698
738
  * When `.map(callback)` destructures its item parameter (array or object
@@ -720,9 +760,9 @@ export interface IRLoop {
720
760
  /**
721
761
  * For flatMap callbacks whose body can't be decomposed into simple
722
762
  * `children` (block bodies with conditional returns, variable-assigned
723
- * JSX, etc.). Carries the full callback body with JSX fragments
724
- * replaced by `__BF_JSX_N__` placeholders, plus the IR for each
725
- * fragment so each adapter can render them appropriately.
763
+ * JSX, etc.). Carries the body as structured segments (JS text +
764
+ * compiled JSX-leaf IR) rendered through `renderPreamble()`, plus the
765
+ * raw TSX for JSX-runtime SSR adapters.
726
766
  *
727
767
  * When present, `children` is empty — emitters use this field instead.
728
768
  */
@@ -730,31 +770,115 @@ export interface IRLoop {
730
770
  }
731
771
 
732
772
  /**
733
- * A compiled flatMap callback body. JSX elements in the original
734
- * callback have been transformed to IR nodes and replaced with
735
- * `__BF_JSX_0__`, `__BF_JSX_1__`, placeholders in `body`.
736
- *
737
- * Each emitter (html-template, hono-adapter) renders the IR fragments
738
- * in its own format and substitutes the placeholders accordingly.
773
+ * A compiled flatMap callback body, carried as structured segments (the same
774
+ * shape as {@link MapCallbackPreamble}, rendered through the same
775
+ * `renderPreamble()` door) never as a sentinel-bearing string. JSX-runtime
776
+ * SSR adapters emit `rawBody` (real TSX, branded) instead.
739
777
  */
740
778
  export interface FlatMapCallback {
741
779
  /** Callback parameters text, e.g. `"(frame, i)"` */
742
780
  params: string
743
- /** Callback body text with JSX replaced by `__BF_JSX_N__` placeholders */
744
- body: string
745
- /** Same as `body` but with prop refs rewritten for template context */
746
- templateBody?: string
747
- /** Original callback body text with JSX preserved (for Hono .tsx output) */
748
- rawBody: string
749
- /** IR nodes for each JSX placeholder, ordered by placeholder index */
750
- fragments: FlatMapJsxFragment[]
751
- }
752
-
753
- export interface FlatMapJsxFragment {
754
- /** Placeholder string, e.g. `"__BF_JSX_0__"` */
755
- placeholder: string
756
- /** Compiled IR node for this JSX fragment */
757
- ir: IRNode
781
+ /** Callback body as js-text / compiled-JSX-leaf segments. */
782
+ segments: PreambleSegment[]
783
+ /** Original callback body text (JSX intact) for JSX-runtime SSR adapters. */
784
+ rawBody: TsxSourceText
785
+ }
786
+
787
+ /**
788
+ * Raw TSX source text (types + JSX intact), branded so it is only assignable
789
+ * where a JSX-runtime SSR adapter emits real TSX (Hono / TestAdapter). Plain
790
+ * strings destined for the client bundle cannot satisfy this type, and this
791
+ * type must never be spliced into plain-JS emission — the write-side twin of
792
+ * the "never parse JS with regex" rule: raw source crossing into an emitted
793
+ * artifact is tagged with its only legal destination.
794
+ */
795
+ export type TsxSourceText = string & { readonly __tsxSourceBrand: unique symbol }
796
+
797
+ /** Constructor for {@link TsxSourceText} — the single place the brand is applied. */
798
+ export function tsxSourceText(raw: string): TsxSourceText {
799
+ return raw as TsxSourceText
800
+ }
801
+
802
+ /**
803
+ * One segment of a `.map()` callback preamble. Mixed content is structured,
804
+ * never a sentinel-bearing string: `js` segments carry JSX-free JS source text
805
+ * (types stripped; `templateText` set when the destructured-prop rewrite
806
+ * differs), `jsx` segments carry the compiled IR of one JSX leaf. Segments
807
+ * concatenate in source order with no separators (they are sub-statement
808
+ * spans).
809
+ */
810
+ export type PreambleSegment =
811
+ | {
812
+ kind: 'js'
813
+ /** Type-stripped JS text for the client bundle. */
814
+ text: string
815
+ /** `text` with destructured prop refs rewritten to `_p.xxx`, when different. */
816
+ templateText?: string
817
+ }
818
+ | { kind: 'jsx'; ir: IRNode }
819
+
820
+ /**
821
+ * The structured pre-return statements of a block-body `.map()` callback
822
+ * (Stage 3 root cure, spec/callback-fidelity.md). Client emission renders
823
+ * `segments` via `renderPreamble()` (html-template.ts) — the only door; a
824
+ * consumer that can't call it has no way to splice the preamble, so a missing
825
+ * wire-up is a type error or an explicit refusal, never a silent leak.
826
+ * JSX-runtime SSR adapters emit `ssrText` (real TSX) instead.
827
+ */
828
+ export interface MapCallbackPreamble {
829
+ segments: PreambleSegment[]
830
+ /** Raw preamble source (types + JSX intact) for JSX-runtime SSR adapters. */
831
+ ssrText: TsxSourceText
832
+ /** const/let/function names the preamble declares (D5 key-derivability guard). */
833
+ declaredNames: string[]
834
+ /**
835
+ * The subset of {@link declaredNames} that accumulate JSX leaves — the
836
+ * `push`/`unshift` targets of a `jsx` segment and the declaration targets of
837
+ * a leaf-bearing initializer (`const out = xs.map(x => <td/>)`). Only these
838
+ * get the `{out}` array-join child emission; a value-only local (`{label}`)
839
+ * keeps the plain interpolation it always had.
840
+ */
841
+ builderNames: string[]
842
+ }
843
+
844
+ /**
845
+ * One loop-body expression child classified as a preamble-patched region
846
+ * (#2389 — see `IRLoop.preambleRegions`). `expr` is the raw (unwrapped)
847
+ * expression text, exactly as carried on the source `IRExpression` node;
848
+ * emitters wrap it with the loop-param accessor at codegen time, matching
849
+ * every other loop-body expression. `joinArrayChild` mirrors the
850
+ * `IRExpression` field of the same name — the client-JS plan builder uses it
851
+ * to decide between the array-join value expression and a plain (escaped)
852
+ * text value, keeping the region's re-render byte-identical to the row
853
+ * template's own `irToHtmlTemplate` rendering of the same node.
854
+ */
855
+ export interface PreambleRegionSource {
856
+ slotId: string
857
+ expr: string
858
+ joinArrayChild?: boolean
859
+ }
860
+
861
+ /**
862
+ * The preamble's JS text (js segments only, concatenated), for read-side
863
+ * analysis — free-identifier scans, reachability edges, rest-misuse checks.
864
+ * JSX-leaf interiors are compiled IR and are analyzed through their own IR
865
+ * walks, exactly as the former placeholder carrier excluded them. NEVER use
866
+ * this for emission — emission goes through `renderPreamble()`.
867
+ */
868
+ export function preambleAnalysisText(p: Pick<MapCallbackPreamble, 'segments'>): string {
869
+ let out = ''
870
+ for (const seg of p.segments) if (seg.kind === 'js') out += seg.text
871
+ return out
872
+ }
873
+
874
+ /**
875
+ * Template-context variant of {@link preambleAnalysisText} (destructured prop
876
+ * refs rewritten). Read-side analysis only.
877
+ */
878
+ export function preambleAnalysisTemplateText(p: Pick<MapCallbackPreamble, 'segments'>): string {
879
+ let out = ''
880
+ for (const seg of p.segments) if (seg.kind === 'js') out += seg.templateText ?? seg.text
881
+ return out
758
882
  }
759
883
 
760
884
  /**