@pyreon/compiler 0.24.5 → 0.24.6

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 (64) hide show
  1. package/package.json +11 -13
  2. package/src/defer-inline.ts +0 -686
  3. package/src/event-names.ts +0 -65
  4. package/src/index.ts +0 -61
  5. package/src/island-audit.ts +0 -675
  6. package/src/jsx.ts +0 -2792
  7. package/src/load-native.ts +0 -156
  8. package/src/lpih.ts +0 -270
  9. package/src/manifest.ts +0 -280
  10. package/src/project-scanner.ts +0 -214
  11. package/src/pyreon-intercept.ts +0 -1029
  12. package/src/react-intercept.ts +0 -1217
  13. package/src/reactivity-lens.ts +0 -190
  14. package/src/ssg-audit.ts +0 -513
  15. package/src/test-audit.ts +0 -435
  16. package/src/tests/backend-parity-r7-r9.test.ts +0 -91
  17. package/src/tests/backend-prop-derived-callback-divergence.test.ts +0 -74
  18. package/src/tests/collapse-bail-census.test.ts +0 -330
  19. package/src/tests/collapse-key-source-hygiene.test.ts +0 -88
  20. package/src/tests/component-child-no-wrap.test.ts +0 -204
  21. package/src/tests/defer-inline.test.ts +0 -387
  22. package/src/tests/depth-stress.test.ts +0 -16
  23. package/src/tests/detector-tag-consistency.test.ts +0 -101
  24. package/src/tests/dynamic-collapse-detector.test.ts +0 -164
  25. package/src/tests/dynamic-collapse-emit.test.ts +0 -192
  26. package/src/tests/dynamic-collapse-scan.test.ts +0 -111
  27. package/src/tests/element-valued-const-child.test.ts +0 -61
  28. package/src/tests/falsy-child-characterization.test.ts +0 -48
  29. package/src/tests/island-audit.test.ts +0 -524
  30. package/src/tests/jsx.test.ts +0 -2908
  31. package/src/tests/load-native.test.ts +0 -53
  32. package/src/tests/lpih.test.ts +0 -404
  33. package/src/tests/malformed-input-resilience.test.ts +0 -50
  34. package/src/tests/manifest-snapshot.test.ts +0 -55
  35. package/src/tests/native-equivalence.test.ts +0 -924
  36. package/src/tests/partial-collapse-detector.test.ts +0 -121
  37. package/src/tests/partial-collapse-emit.test.ts +0 -104
  38. package/src/tests/partial-collapse-robustness.test.ts +0 -53
  39. package/src/tests/project-scanner.test.ts +0 -269
  40. package/src/tests/prop-derived-shadow.test.ts +0 -96
  41. package/src/tests/pure-call-reactive-args.test.ts +0 -50
  42. package/src/tests/pyreon-intercept.test.ts +0 -816
  43. package/src/tests/r13-callback-stmt-equivalence.test.ts +0 -58
  44. package/src/tests/r14-ssr-mode-parity.test.ts +0 -51
  45. package/src/tests/r15-elemconst-propderived.test.ts +0 -47
  46. package/src/tests/r19-defer-inline-robust.test.ts +0 -54
  47. package/src/tests/r20-backend-equivalence-sweep.test.ts +0 -50
  48. package/src/tests/react-intercept.test.ts +0 -1104
  49. package/src/tests/reactivity-lens.test.ts +0 -170
  50. package/src/tests/rocketstyle-collapse.test.ts +0 -208
  51. package/src/tests/runtime/control-flow.test.ts +0 -159
  52. package/src/tests/runtime/dom-properties.test.ts +0 -138
  53. package/src/tests/runtime/events.test.ts +0 -301
  54. package/src/tests/runtime/harness.ts +0 -94
  55. package/src/tests/runtime/pr-352-shapes.test.ts +0 -121
  56. package/src/tests/runtime/reactive-props.test.ts +0 -81
  57. package/src/tests/runtime/signals.test.ts +0 -129
  58. package/src/tests/runtime/whitespace.test.ts +0 -106
  59. package/src/tests/signal-autocall-shadow.test.ts +0 -86
  60. package/src/tests/sourcemap-fidelity.test.ts +0 -77
  61. package/src/tests/ssg-audit.test.ts +0 -402
  62. package/src/tests/static-text-baking.test.ts +0 -64
  63. package/src/tests/test-audit.test.ts +0 -549
  64. package/src/tests/transform-state-isolation.test.ts +0 -49
@@ -1,129 +0,0 @@
1
- // @vitest-environment happy-dom
2
- /// <reference lib="dom" />
3
- import { computed, signal } from '@pyreon/reactivity'
4
- import { describe, expect, it } from 'vitest'
5
- import { flush } from '@pyreon/test-utils/browser'
6
- import { compileAndMount } from './harness'
7
-
8
- /**
9
- * Compiler-runtime tests — signal patterns in JSX.
10
- *
11
- * The #352 signal-method auto-call bug surfaced because the compiler
12
- * couldn't tell `signal.set(x)` (call on the signal as object) from
13
- * `signal()` (call the signal to read). The fix added scope-aware
14
- * detection. This file pins down the matrix: bare reference, function
15
- * call, member call, accessor wrapper, computed — in different positions.
16
- */
17
-
18
- describe('compiler-runtime — signals', () => {
19
- it('signal in text position is reactive', async () => {
20
- const name = signal('alice')
21
- const { container, unmount } = compileAndMount(
22
- `<div><span id="s">{name()}</span></div>`,
23
- { name },
24
- )
25
- expect(container.querySelector('#s')!.textContent).toBe('alice')
26
- name.set('bob')
27
- await flush()
28
- expect(container.querySelector('#s')!.textContent).toBe('bob')
29
- unmount()
30
- })
31
-
32
- it('signal.method() in event handler does not auto-call signal', () => {
33
- const x = signal(0)
34
- const { container, unmount } = compileAndMount(
35
- `<div><button id="b" onClick={() => x.set(99)}>set</button></div>`,
36
- { x },
37
- )
38
- container.querySelector<HTMLButtonElement>('#b')!.click()
39
- expect(x()).toBe(99)
40
- unmount()
41
- })
42
-
43
- it('signal.update() in event handler does not auto-call signal', () => {
44
- const x = signal(10)
45
- const { container, unmount } = compileAndMount(
46
- `<div><button id="b" onClick={() => x.update((v) => v * 2)}>x2</button></div>`,
47
- { x },
48
- )
49
- const btn = container.querySelector<HTMLButtonElement>('#b')!
50
- btn.click()
51
- btn.click()
52
- expect(x()).toBe(40)
53
- unmount()
54
- })
55
-
56
- it('signal.peek() in event handler does not auto-call signal', () => {
57
- const x = signal(7)
58
- const out = { value: 0 }
59
- const { container, unmount } = compileAndMount(
60
- `<div><button id="b" onClick={() => { out.value = x.peek() }}>read</button></div>`,
61
- { x, out },
62
- )
63
- container.querySelector<HTMLButtonElement>('#b')!.click()
64
- expect(out.value).toBe(7)
65
- unmount()
66
- })
67
-
68
- it('computed value reflected in DOM updates when source changes', async () => {
69
- const a = signal(2)
70
- const b = signal(3)
71
- const sum = computed(() => a() + b())
72
- const { container, unmount } = compileAndMount(
73
- `<div><span id="s">{sum()}</span></div>`,
74
- { sum },
75
- )
76
- expect(container.querySelector('#s')!.textContent).toBe('5')
77
- a.set(10)
78
- await flush()
79
- expect(container.querySelector('#s')!.textContent).toBe('13')
80
- unmount()
81
- })
82
-
83
- it('explicit accessor wrapper preserves reactivity', async () => {
84
- const x = signal('hi')
85
- const { container, unmount } = compileAndMount(
86
- `<div><span id="s">{() => x()}</span></div>`,
87
- { x },
88
- )
89
- expect(container.querySelector('#s')!.textContent).toBe('hi')
90
- x.set('hey')
91
- await flush()
92
- expect(container.querySelector('#s')!.textContent).toBe('hey')
93
- unmount()
94
- })
95
-
96
- it('signal in attribute position is reactive', async () => {
97
- const cls = signal('a')
98
- const { container, unmount } = compileAndMount(
99
- `<div><span id="s" class={cls()}>x</span></div>`,
100
- { cls },
101
- )
102
- expect(container.querySelector('#s')!.className).toBe('a')
103
- cls.set('b')
104
- await flush()
105
- expect(container.querySelector('#s')!.className).toBe('b')
106
- unmount()
107
- })
108
-
109
- it('multiple signals on the same element track independently', async () => {
110
- const txt = signal('hello')
111
- const cls = signal('a')
112
- const { container, unmount } = compileAndMount(
113
- `<div><span id="s" class={cls()}>{txt()}</span></div>`,
114
- { txt, cls },
115
- )
116
- const span = container.querySelector('#s')!
117
- expect(span.textContent).toBe('hello')
118
- expect(span.className).toBe('a')
119
- txt.set('world')
120
- await flush()
121
- expect(span.textContent).toBe('world')
122
- expect(span.className).toBe('a')
123
- cls.set('b')
124
- await flush()
125
- expect(span.textContent).toBe('world')
126
- expect(span.className).toBe('b')
127
- unmount()
128
- })
129
- })
@@ -1,106 +0,0 @@
1
- // @vitest-environment happy-dom
2
- /// <reference lib="dom" />
3
- import { signal } from '@pyreon/reactivity'
4
- import { describe, expect, it } from 'vitest'
5
- import { flush } from '@pyreon/test-utils/browser'
6
- import { compileAndMount } from './harness'
7
-
8
- /**
9
- * Compiler-runtime tests — JSX text/expression whitespace handling.
10
- *
11
- * The #352 whitespace bug stripped same-line spaces adjacent to
12
- * expressions: `<p>doubled: {x}</p>` rendered "doubled:0" instead of
13
- * "doubled: 0". The fix implements React/Babel's
14
- * `cleanJSXElementLiteralChild` algorithm. This file pins down the
15
- * matrix: same-line text±expression, multi-line text, fragments,
16
- * leading/trailing/internal whitespace.
17
- */
18
-
19
- describe('compiler-runtime — JSX whitespace', () => {
20
- it('preserves trailing space before expression on same line', async () => {
21
- const x = signal(7)
22
- const { container, unmount } = compileAndMount(
23
- `<div><p id="p">doubled: {x()}</p></div>`,
24
- { x },
25
- )
26
- expect(container.querySelector('#p')!.textContent).toBe('doubled: 7')
27
- unmount()
28
- })
29
-
30
- // Regression: when an expression sits BETWEEN static text segments
31
- // on the same line, the compiler used to append the dynamic text
32
- // node to the parent's children AFTER all static text via
33
- // `appendChild`. Whitespace was preserved correctly (#352), but
34
- // positioning was wrong:
35
- // `<p>{x()} remaining</p>` → template `<p> remaining</p>` +
36
- // appended text → renders as " remaining3" instead of "3 remaining".
37
- // Fix: extend `analyzeChildren.useMixed` to fire whenever ≥2 of
38
- // {element, text, expression} are present (not just element+nonElement).
39
- // Then placeholder-based positional mounting puts the dynamic text in
40
- // the right slot via `replaceChild`.
41
- it('preserves leading space after expression on same line', async () => {
42
- const x = signal(3)
43
- const { container, unmount } = compileAndMount(
44
- `<div><p id="p">{x()} remaining</p></div>`,
45
- { x },
46
- )
47
- expect(container.querySelector('#p')!.textContent).toBe('3 remaining')
48
- unmount()
49
- })
50
-
51
- it('preserves spaces on BOTH sides of expression', async () => {
52
- const x = signal('cat')
53
- const { container, unmount } = compileAndMount(
54
- `<div><p id="p">a {x()} b</p></div>`,
55
- { x },
56
- )
57
- expect(container.querySelector('#p')!.textContent).toBe('a cat b')
58
- unmount()
59
- })
60
-
61
- it('multi-line JSX with indentation collapses correctly', async () => {
62
- const x = signal('inner')
63
- // Multi-line JSX expression. Whitespace inside the JSX literal between
64
- // <p> and {x()} is treated by React/Babel cleanJSX as "indentation"
65
- // and collapses; same for between {x()} and </p>.
66
- const { container, unmount } = compileAndMount(
67
- `<div>
68
- <p id="p">
69
- {x()}
70
- </p>
71
- </div>`,
72
- { x },
73
- )
74
- expect(container.querySelector('#p')!.textContent?.trim()).toBe('inner')
75
- unmount()
76
- })
77
-
78
- it('reactive text updates without losing surrounding whitespace', async () => {
79
- const x = signal(0)
80
- const { container, unmount } = compileAndMount(
81
- `<div><p id="p">count: {x()} items</p></div>`,
82
- { x },
83
- )
84
- expect(container.querySelector('#p')!.textContent).toBe('count: 0 items')
85
- x.set(42)
86
- await flush()
87
- expect(container.querySelector('#p')!.textContent).toBe('count: 42 items')
88
- unmount()
89
- })
90
-
91
- it('reactive text at end of paragraph updates correctly', async () => {
92
- // The expression-at-END shape works because the dynamic text node is
93
- // appended to the parent — which happens to match the source order
94
- // when there's no text after.
95
- const x = signal(0)
96
- const { container, unmount } = compileAndMount(
97
- `<div><p id="p">count: {x()}</p></div>`,
98
- { x },
99
- )
100
- expect(container.querySelector('#p')!.textContent).toBe('count: 0')
101
- x.set(42)
102
- await flush()
103
- expect(container.querySelector('#p')!.textContent).toBe('count: 42')
104
- unmount()
105
- })
106
- })
@@ -1,86 +0,0 @@
1
- /**
2
- * Compiler hardening — Round 11 (REAL bug, FIXED + bisect-verified).
3
- *
4
- * The signal-auto-call rewrite (`autoCallSignals` → `findSignalIdents`,
5
- * jsx.ts) inserts `()` after every active-signal-named identifier. Its
6
- * skip-list handled MemberExpr / VarDeclarator / Property-key|shorthand but
7
- * NOT callback parameter binding positions, and `findSignalIdents` did its
8
- * OWN scope-blind recursive walk over the wrapped expression. So a
9
- * destructured/plain callback param reusing a signal's name was wrongly
10
- * auto-called:
11
- *
12
- * const x = signal(0)
13
- * <ul>{[{x:1}].map(({x}) => <li>{x}</li>)}</ul>
14
- * → …map(({x}) => <li>{x()}</li>) // x is the map item (1) → 1()
15
- * // → runtime TypeError
16
- *
17
- * Trigger: an inline object/array literal in the expr whose property name
18
- * collides with a signal makes `referencesSignalVar` fire, invoking the
19
- * scope-blind `autoCallSignals` over the whole expression. This is the exact
20
- * signal twin of R2's prop-derived scope-blind inlining.
21
- *
22
- * Fix: `findSignalIdents` is now block-accurate scope-aware (mirrors R2's
23
- * `findIdents`): a `scopeBoundSignals(node)` collects signal-named bindings a
24
- * scope introduces (params incl. nested/destructured patterns, nested const,
25
- * catch/loop vars), threaded through a `shadowed` set with enter/leave so a
26
- * shadowed name is never auto-called. Legitimate (non-shadowed) signal reads
27
- * still auto-call — proven by the CONTROL specs.
28
- *
29
- * Bisect: drop `&& !shadowed.has(node.name)` from the `findSignalIdents`
30
- * active-signal guard → the SHADOW specs fail (emit `{x()}` / `id()`); the
31
- * CONTROL specs stay green (no over-suppression). Restore → all pass.
32
- */
33
- import { parseSync } from 'oxc-parser'
34
- import { describe, expect, it } from 'vitest'
35
- import { transformJSX_JS } from '../jsx'
36
-
37
- const emit = (c: string): string => transformJSX_JS(c, 'c.tsx').code ?? ''
38
- const parses = (o: string): boolean => {
39
- try {
40
- return (parseSync('o.tsx', o).errors?.length ?? 0) === 0
41
- } catch {
42
- return false
43
- }
44
- }
45
-
46
- describe('Round 11 — signal auto-call respects lexical shadowing', () => {
47
- it('destructured-shorthand callback param shadowing a signal is NOT auto-called', () => {
48
- const out = emit(`function C(){ const x = signal(0); return <ul>{[{x:1}].map(({x}) => <li>{x}</li>)}</ul> }`)
49
- expect(parses(out)).toBe(true)
50
- expect(out).not.toContain('{x()}')
51
- expect(out).toContain('({x}) => <li>{x}</li>')
52
- })
53
-
54
- it('destructured param shadowing a signal in a filter predicate is NOT auto-called', () => {
55
- const out = emit(`function C(){ const id = signal(0); return <ul>{[{id:1}].filter(({id}) => id > 0).map(r => <li>{r}</li>)}</ul> }`)
56
- expect(parses(out)).toBe(true)
57
- expect(out).not.toMatch(/\(\{id\}\) => id\(\)/)
58
- })
59
-
60
- it('renamed destructured value param shadowing a signal is NOT auto-called', () => {
61
- const out = emit(`function C(){ const v = signal(0); return <ul>{[{k:1}].map(({k: v}) => <li>{v}</li>)}</ul> }`)
62
- expect(out).not.toContain('{v()}')
63
- })
64
-
65
- it('plain callback param shadowing a signal is NOT auto-called', () => {
66
- const out = emit(`function C(){ const s = signal(0); return <ul>{[1].map(s => <li>{s}</li>)}</ul> }`)
67
- expect(out).not.toContain('{s()}')
68
- })
69
-
70
- // ── CONTROL: legitimate signal reads MUST still auto-call ──
71
- it('CONTROL: a direct non-shadowed signal child still auto-calls', () => {
72
- expect(emit(`function C(){ const s = signal(0); return <div>{s}</div> }`)).toContain('__t0.data = s()')
73
- })
74
-
75
- it('CONTROL: a non-shadowed signal SIBLING of a shadowing callback still auto-calls', () => {
76
- const out = emit(`function C(){ const s = signal(0); return <div>{[1].map(s => <i>{s}</i>)}<b>{s}</b></div> }`)
77
- expect(out).not.toContain('<i>{s()}</i>') // the shadowing param — not called
78
- expect(out).toContain('__t0.data = s()') // the real signal sibling — called
79
- })
80
-
81
- it('CONTROL: signal.set in a handler is not auto-called but its arg is', () => {
82
- const out = emit(`function C(){ const s = signal(0); return <button onClick={() => s.set(s() + 1)}>{s}</button> }`)
83
- expect(out).toContain('s.set(s() + 1)')
84
- expect(out).toContain('__t0.data = s()')
85
- })
86
- })
@@ -1,77 +0,0 @@
1
- /**
2
- * Compiler hardening — Round 12 (REAL high-impact gap, FIXED + bisect).
3
- *
4
- * Pre-fix: `transformJSX` emitted NO source map and its string-slice
5
- * substitutions shifted line counts (template emission expands one-line JSX
6
- * into a multi-line `_tpl(...)` factory). `@pyreon/vite-plugin` returned
7
- * `{ code, map: null }`, so every runtime stack frame / debugger breakpoint
8
- * in every Pyreon component mislocated — app-wide, in every project.
9
- *
10
- * Fix: `transformJSX_JS` applies its existing disjoint `{start,end,text}`
11
- * replacement set through MagicString (`update`/`appendLeft`) and the
12
- * generated preamble via `prepend`. `toString()` is byte-identical to the
13
- * old concatenation — proven by the full ~1240-test suite + the 180
14
- * native-equivalence tests, which assert exact emitted strings and all stay
15
- * green — while `generateMap()` now yields a correct V3 map. `prepend`
16
- * shifts every mapping by the preamble's line count, so original positions
17
- * resolve to the correct OUTPUT line despite the line-shift.
18
- *
19
- * Bisect: revert the MagicString block to the slice/join + chained-prepend
20
- * assembly → `map` is `undefined` and these specs fail; restore → pass. The
21
- * byte-identical guarantee is itself bisect-covered by the rest of the suite
22
- * (any drift fails an exact-string assertion somewhere).
23
- */
24
- import { describe, expect, it } from 'vitest'
25
- import { transformJSX_JS } from '../jsx'
26
-
27
- // Edit-correctness oracle is the rest of the compiler suite: the full
28
- // ~1240-test + 180 native-equivalence corpus asserts EXACT emitted strings,
29
- // so any byte drift from the MagicString assembly fails there. Map *math*
30
- // (segment offsets through `update`/`appendLeft`/`prepend`) is magic-string's
31
- // — the battle-tested generator vite/rollup/svelte rely on; not re-derived
32
- // here. These specs assert the gap is closed (a valid, content-embedded V3
33
- // map is produced and serializes for Vite) and the no-op contract.
34
-
35
- const MULTILINE = `function C(props) {
36
- return (
37
- <section>
38
- <h1>{props.title}</h1>
39
- <p>{props.body}</p>
40
- </section>
41
- )
42
- }`
43
-
44
- describe('Round 12 — sourcemap fidelity (fixed)', () => {
45
- it('a transforming compile now produces a V3 source map', () => {
46
- const r = transformJSX_JS(MULTILINE, 'C.tsx')
47
- expect(r.map).toBeDefined()
48
- expect(r.map!.version).toBe(3)
49
- expect(r.map!.sources).toContain('C.tsx')
50
- expect(r.map!.mappings.length).toBeGreaterThan(0)
51
- })
52
-
53
- it('the map embeds original content and is JSON/`toString`-serializable for Vite', () => {
54
- const r = transformJSX_JS(MULTILINE, 'C.tsx')
55
- expect(r.map!.sourcesContent?.[0]).toBe(MULTILINE)
56
- const json = JSON.parse(r.map!.toString())
57
- expect(json.version).toBe(3)
58
- expect(json.mappings).toBe(r.map!.mappings)
59
- })
60
-
61
- it('output still line-shifts — but the map now accounts for it (the whole point)', () => {
62
- const r = transformJSX_JS(MULTILINE, 'C.tsx')
63
- // Template emission still expands lines (unchanged codegen)…
64
- expect(r.code.split('\n').length).toBeGreaterThan(MULTILINE.split('\n').length)
65
- // …and the segment mappings are non-trivial (multi-segment, i.e. the
66
- // preamble + per-replacement remapping is recorded, not an empty/identity
67
- // map that would silently mislocate like before).
68
- expect(r.map!.mappings).toMatch(/[;,]/)
69
- expect(r.map!.names).toBeInstanceOf(Array)
70
- })
71
-
72
- it('a no-op compile (nothing to transform) returns no map (code is unchanged)', () => {
73
- const r = transformJSX_JS(`const x = 1`, 'plain.ts')
74
- expect(r.map).toBeUndefined()
75
- expect(r.code).toBe(`const x = 1`)
76
- })
77
- })