@barefootjs/jsx 0.26.2 → 0.26.3
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/interface.d.ts +29 -0
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/adapters/jsx-adapter.d.ts +9 -0
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/adapters/parsed-expr-emitter.d.ts +0 -10
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/adapters/test-adapter.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts +11 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts +40 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.js +662 -223
- package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +2 -0
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +12 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +19 -1
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +4 -4
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/loop-destructure.d.ts.map +1 -1
- package/dist/strip-types.d.ts +18 -0
- package/dist/strip-types.d.ts.map +1 -1
- package/dist/types.d.ts +96 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +94 -0
- package/src/__tests__/client-js-generation.test.ts +10 -3
- package/src/__tests__/compiler-stress-1244.test.ts +12 -1
- package/src/__tests__/event-delegation-scope-2367.test.ts +108 -0
- package/src/__tests__/flatmap-segments.test.ts +80 -0
- package/src/__tests__/map-arbitrary-body.test.ts +226 -0
- package/src/__tests__/map-body-no-silent-divergence.test.ts +360 -0
- package/src/__tests__/map-multi-return-body.test.ts +172 -0
- package/src/__tests__/unsupported-expression.test.ts +20 -2
- package/src/adapters/interface.ts +40 -0
- package/src/adapters/jsx-adapter.ts +10 -0
- package/src/adapters/loop-bound-names.ts +6 -2
- package/src/adapters/parsed-expr-emitter.ts +5 -10
- package/src/adapters/test-adapter.ts +10 -0
- package/src/analyzer-context.ts +35 -1
- package/src/analyzer.ts +162 -24
- package/src/compiler.ts +2 -2
- package/src/expression-parser.ts +27 -19
- package/src/ir-to-client-js/build-references.ts +3 -2
- package/src/ir-to-client-js/collect-elements.ts +3 -3
- package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +2 -0
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +7 -2
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +4 -0
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +15 -2
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +16 -3
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +26 -3
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +17 -2
- package/src/ir-to-client-js/control-flow/plan/loop.ts +12 -0
- package/src/ir-to-client-js/control-flow/shared.ts +2 -1
- package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +9 -2
- package/src/ir-to-client-js/control-flow.ts +11 -0
- package/src/ir-to-client-js/html-template.ts +133 -37
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +22 -10
- package/src/ir-to-client-js/types.ts +4 -3
- package/src/jsx-to-ir.ts +690 -164
- package/src/loop-destructure.ts +9 -5
- package/src/rich-type-refusal.ts +6 -2
- package/src/strip-types.ts +47 -0
- package/src/types.ts +110 -35
package/src/loop-destructure.ts
CHANGED
|
@@ -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
|
-
|
|
158
|
-
|
|
158
|
+
if (l.preamble) {
|
|
159
|
+
check(preambleAnalysisText(l.preamble))
|
|
160
|
+
check(preambleAnalysisTemplateText(l.preamble))
|
|
161
|
+
}
|
|
159
162
|
if (l.flatMapCallback) {
|
|
160
|
-
check(l.flatMapCallback
|
|
161
|
-
|
|
162
|
-
|
|
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
|
}
|
package/src/rich-type-refusal.ts
CHANGED
|
@@ -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
|
|
303
|
-
walkNode(
|
|
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
|
}
|
package/src/strip-types.ts
CHANGED
|
@@ -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,15 @@ 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
|
|
374
383
|
/** When true, expression calls signal getters or memos (has reactive `foo()` pattern). */
|
|
375
384
|
callsReactiveGetters?: boolean
|
|
376
385
|
/** When true, expression contains function call(s) — any `identifier()` pattern (computed from AST). */
|
|
@@ -678,21 +687,20 @@ export interface IRLoop {
|
|
|
678
687
|
*/
|
|
679
688
|
bodyIsItemConditional?: boolean
|
|
680
689
|
|
|
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
690
|
/** Type annotation for loop param (e.g., 'Desk'), preserved for .tsx output */
|
|
691
691
|
paramType?: string
|
|
692
692
|
/** Type annotation for loop index param (e.g., 'number'), preserved for .tsx output */
|
|
693
693
|
indexType?: string
|
|
694
|
-
/**
|
|
695
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Structured pre-return statements of a block-body `.map()` callback
|
|
696
|
+
* (Stage 3 root cure, spec/callback-fidelity.md). Replaces the former
|
|
697
|
+
* `mapPreamble` / `templateMapPreamble` / `typedMapPreamble` string carriers
|
|
698
|
+
* and `preambleFragments`: mixed content (JS text + JSX leaves) is carried as
|
|
699
|
+
* a segment list, never as a sentinel-bearing string, so an emitter that
|
|
700
|
+
* lacks preamble support cannot accidentally interpolate it — rendering goes
|
|
701
|
+
* through `renderPreamble()` (html-template.ts) or not at all.
|
|
702
|
+
*/
|
|
703
|
+
preamble?: MapCallbackPreamble
|
|
696
704
|
|
|
697
705
|
/**
|
|
698
706
|
* When `.map(callback)` destructures its item parameter (array or object
|
|
@@ -720,9 +728,9 @@ export interface IRLoop {
|
|
|
720
728
|
/**
|
|
721
729
|
* For flatMap callbacks whose body can't be decomposed into simple
|
|
722
730
|
* `children` (block bodies with conditional returns, variable-assigned
|
|
723
|
-
* JSX, etc.). Carries the
|
|
724
|
-
*
|
|
725
|
-
*
|
|
731
|
+
* JSX, etc.). Carries the body as structured segments (JS text +
|
|
732
|
+
* compiled JSX-leaf IR) rendered through `renderPreamble()`, plus the
|
|
733
|
+
* raw TSX for JSX-runtime SSR adapters.
|
|
726
734
|
*
|
|
727
735
|
* When present, `children` is empty — emitters use this field instead.
|
|
728
736
|
*/
|
|
@@ -730,31 +738,98 @@ export interface IRLoop {
|
|
|
730
738
|
}
|
|
731
739
|
|
|
732
740
|
/**
|
|
733
|
-
* A compiled flatMap callback body
|
|
734
|
-
*
|
|
735
|
-
* `
|
|
736
|
-
*
|
|
737
|
-
* Each emitter (html-template, hono-adapter) renders the IR fragments
|
|
738
|
-
* in its own format and substitutes the placeholders accordingly.
|
|
741
|
+
* A compiled flatMap callback body, carried as structured segments (the same
|
|
742
|
+
* shape as {@link MapCallbackPreamble}, rendered through the same
|
|
743
|
+
* `renderPreamble()` door) — never as a sentinel-bearing string. JSX-runtime
|
|
744
|
+
* SSR adapters emit `rawBody` (real TSX, branded) instead.
|
|
739
745
|
*/
|
|
740
746
|
export interface FlatMapCallback {
|
|
741
747
|
/** Callback parameters text, e.g. `"(frame, i)"` */
|
|
742
748
|
params: string
|
|
743
|
-
/** Callback body text
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
749
|
+
/** Callback body as js-text / compiled-JSX-leaf segments. */
|
|
750
|
+
segments: PreambleSegment[]
|
|
751
|
+
/** Original callback body text (JSX intact) for JSX-runtime SSR adapters. */
|
|
752
|
+
rawBody: TsxSourceText
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Raw TSX source text (types + JSX intact), branded so it is only assignable
|
|
757
|
+
* where a JSX-runtime SSR adapter emits real TSX (Hono / TestAdapter). Plain
|
|
758
|
+
* strings destined for the client bundle cannot satisfy this type, and this
|
|
759
|
+
* type must never be spliced into plain-JS emission — the write-side twin of
|
|
760
|
+
* the "never parse JS with regex" rule: raw source crossing into an emitted
|
|
761
|
+
* artifact is tagged with its only legal destination.
|
|
762
|
+
*/
|
|
763
|
+
export type TsxSourceText = string & { readonly __tsxSourceBrand: unique symbol }
|
|
764
|
+
|
|
765
|
+
/** Constructor for {@link TsxSourceText} — the single place the brand is applied. */
|
|
766
|
+
export function tsxSourceText(raw: string): TsxSourceText {
|
|
767
|
+
return raw as TsxSourceText
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* One segment of a `.map()` callback preamble. Mixed content is structured,
|
|
772
|
+
* never a sentinel-bearing string: `js` segments carry JSX-free JS source text
|
|
773
|
+
* (types stripped; `templateText` set when the destructured-prop rewrite
|
|
774
|
+
* differs), `jsx` segments carry the compiled IR of one JSX leaf. Segments
|
|
775
|
+
* concatenate in source order with no separators (they are sub-statement
|
|
776
|
+
* spans).
|
|
777
|
+
*/
|
|
778
|
+
export type PreambleSegment =
|
|
779
|
+
| {
|
|
780
|
+
kind: 'js'
|
|
781
|
+
/** Type-stripped JS text for the client bundle. */
|
|
782
|
+
text: string
|
|
783
|
+
/** `text` with destructured prop refs rewritten to `_p.xxx`, when different. */
|
|
784
|
+
templateText?: string
|
|
785
|
+
}
|
|
786
|
+
| { kind: 'jsx'; ir: IRNode }
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* The structured pre-return statements of a block-body `.map()` callback
|
|
790
|
+
* (Stage 3 root cure, spec/callback-fidelity.md). Client emission renders
|
|
791
|
+
* `segments` via `renderPreamble()` (html-template.ts) — the only door; a
|
|
792
|
+
* consumer that can't call it has no way to splice the preamble, so a missing
|
|
793
|
+
* wire-up is a type error or an explicit refusal, never a silent leak.
|
|
794
|
+
* JSX-runtime SSR adapters emit `ssrText` (real TSX) instead.
|
|
795
|
+
*/
|
|
796
|
+
export interface MapCallbackPreamble {
|
|
797
|
+
segments: PreambleSegment[]
|
|
798
|
+
/** Raw preamble source (types + JSX intact) for JSX-runtime SSR adapters. */
|
|
799
|
+
ssrText: TsxSourceText
|
|
800
|
+
/** const/let/function names the preamble declares (D5 key-derivability guard). */
|
|
801
|
+
declaredNames: string[]
|
|
802
|
+
/**
|
|
803
|
+
* The subset of {@link declaredNames} that accumulate JSX leaves — the
|
|
804
|
+
* `push`/`unshift` targets of a `jsx` segment and the declaration targets of
|
|
805
|
+
* a leaf-bearing initializer (`const out = xs.map(x => <td/>)`). Only these
|
|
806
|
+
* get the `{out}` array-join child emission; a value-only local (`{label}`)
|
|
807
|
+
* keeps the plain interpolation it always had.
|
|
808
|
+
*/
|
|
809
|
+
builderNames: string[]
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* The preamble's JS text (js segments only, concatenated), for read-side
|
|
814
|
+
* analysis — free-identifier scans, reachability edges, rest-misuse checks.
|
|
815
|
+
* JSX-leaf interiors are compiled IR and are analyzed through their own IR
|
|
816
|
+
* walks, exactly as the former placeholder carrier excluded them. NEVER use
|
|
817
|
+
* this for emission — emission goes through `renderPreamble()`.
|
|
818
|
+
*/
|
|
819
|
+
export function preambleAnalysisText(p: Pick<MapCallbackPreamble, 'segments'>): string {
|
|
820
|
+
let out = ''
|
|
821
|
+
for (const seg of p.segments) if (seg.kind === 'js') out += seg.text
|
|
822
|
+
return out
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Template-context variant of {@link preambleAnalysisText} (destructured prop
|
|
827
|
+
* refs rewritten). Read-side analysis only.
|
|
828
|
+
*/
|
|
829
|
+
export function preambleAnalysisTemplateText(p: Pick<MapCallbackPreamble, 'segments'>): string {
|
|
830
|
+
let out = ''
|
|
831
|
+
for (const seg of p.segments) if (seg.kind === 'js') out += seg.templateText ?? seg.text
|
|
832
|
+
return out
|
|
758
833
|
}
|
|
759
834
|
|
|
760
835
|
/**
|