@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,402 +0,0 @@
1
- /**
2
- * Fixture-based tests for `auditSsg` (M3.4 of the SSG roadmap).
3
- *
4
- * Each finding type gets a parallel pair:
5
- * - "broken" fixture → finding fires
6
- * - "fixed" fixture → no finding fires
7
- *
8
- * Bisect-verified by removing the detector body and asserting the
9
- * broken-shape test fails.
10
- */
11
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
12
- import { tmpdir } from 'node:os'
13
- import { dirname, join } from 'node:path'
14
- import { auditSsg, formatSsgAudit, type SsgFindingCode } from '../ssg-audit'
15
-
16
- interface Fixture {
17
- root: string
18
- write: (relPath: string, body: string) => void
19
- cleanup: () => void
20
- }
21
-
22
- function makeFixture(): Fixture {
23
- const root = mkdtempSync(join(tmpdir(), 'pyreon-ssg-audit-fixture-'))
24
- mkdirSync(join(root, 'packages'), { recursive: true })
25
- return {
26
- root,
27
- write: (relPath, body) => {
28
- const full = join(root, relPath)
29
- mkdirSync(dirname(full), { recursive: true })
30
- writeFileSync(full, body)
31
- },
32
- cleanup: () => rmSync(root, { recursive: true, force: true }),
33
- }
34
- }
35
-
36
- function findingCodes(result: ReturnType<typeof auditSsg>): SsgFindingCode[] {
37
- return result.findings.map((f) => f.code)
38
- }
39
-
40
- // ═══════════════════════════════════════════════════════════════════════════════
41
- // Discovery
42
- // ═══════════════════════════════════════════════════════════════════════════════
43
-
44
- describe('auditSsg — discovery', () => {
45
- it('returns empty results for a directory with no routes/ subdir', () => {
46
- const empty = mkdtempSync(join(tmpdir(), 'pyreon-ssg-audit-empty-'))
47
- try {
48
- const result = auditSsg(empty)
49
- expect(result.findings).toEqual([])
50
- expect(result.summary.routesScanned).toBe(0)
51
- } finally {
52
- rmSync(empty, { recursive: true, force: true })
53
- }
54
- })
55
-
56
- it('counts route files + dynamic routes + revalidate exports in summary', () => {
57
- const fixture = makeFixture()
58
- try {
59
- // Plain page — no [param], no revalidate.
60
- fixture.write('examples/myapp/src/routes/_layout.tsx', 'export const layout = () => null')
61
- fixture.write('examples/myapp/src/routes/index.tsx', 'export default () => null')
62
- fixture.write('examples/myapp/src/routes/about.tsx', 'export default () => null')
63
- // Dynamic route with getStaticPaths + revalidate
64
- fixture.write(
65
- 'examples/myapp/src/routes/posts/[id].tsx',
66
- `export const getStaticPaths = () => [{ params: { id: '1' } }]
67
- export const revalidate = 60
68
- export default () => null`,
69
- )
70
- const result = auditSsg(fixture.root)
71
- expect(result.summary.routesScanned).toBe(4)
72
- expect(result.summary.dynamicRoutes).toBe(1)
73
- expect(result.summary.revalidateExports).toBe(1)
74
- } finally {
75
- fixture.cleanup()
76
- }
77
- })
78
- })
79
-
80
- // ═══════════════════════════════════════════════════════════════════════════════
81
- // 1) 404-outside-layout-dir
82
- // ═══════════════════════════════════════════════════════════════════════════════
83
-
84
- describe('auditSsg — 404-outside-layout-dir', () => {
85
- it('FIRES when _404.tsx has no co-located _layout.tsx', () => {
86
- const fixture = makeFixture()
87
- try {
88
- // Broken shape: _404.tsx alone in the routes dir, no _layout.tsx.
89
- fixture.write('examples/myapp/src/routes/_404.tsx', 'export default () => null')
90
- fixture.write('examples/myapp/src/routes/index.tsx', 'export default () => null')
91
- const result = auditSsg(fixture.root)
92
- expect(findingCodes(result)).toContain('404-outside-layout-dir')
93
- const finding = result.findings.find((f) => f.code === '404-outside-layout-dir')!
94
- expect(finding.location.relPath).toContain('_404.tsx')
95
- expect(finding.message).toContain('_layout.tsx')
96
- } finally {
97
- fixture.cleanup()
98
- }
99
- })
100
-
101
- it('FIRES for _not-found.tsx (alternate filename)', () => {
102
- const fixture = makeFixture()
103
- try {
104
- fixture.write('examples/myapp/src/routes/_not-found.tsx', 'export default () => null')
105
- const result = auditSsg(fixture.root)
106
- expect(findingCodes(result)).toContain('404-outside-layout-dir')
107
- } finally {
108
- fixture.cleanup()
109
- }
110
- })
111
-
112
- it('does NOT fire when _404.tsx is co-located with _layout.tsx', () => {
113
- const fixture = makeFixture()
114
- try {
115
- // Fixed shape: same directory contains both.
116
- fixture.write('examples/myapp/src/routes/_layout.tsx', 'export const layout = () => null')
117
- fixture.write('examples/myapp/src/routes/_404.tsx', 'export default () => null')
118
- const result = auditSsg(fixture.root)
119
- expect(findingCodes(result)).not.toContain('404-outside-layout-dir')
120
- } finally {
121
- fixture.cleanup()
122
- }
123
- })
124
- })
125
-
126
- // ═══════════════════════════════════════════════════════════════════════════════
127
- // 2) dynamic-route-missing-get-static-paths
128
- // ═══════════════════════════════════════════════════════════════════════════════
129
-
130
- describe('auditSsg — dynamic-route-missing-get-static-paths', () => {
131
- it('FIRES for [id].tsx without getStaticPaths', () => {
132
- const fixture = makeFixture()
133
- try {
134
- fixture.write(
135
- 'examples/myapp/src/routes/posts/[id].tsx',
136
- 'export default () => null',
137
- )
138
- const result = auditSsg(fixture.root)
139
- expect(findingCodes(result)).toContain('dynamic-route-missing-get-static-paths')
140
- const finding = result.findings.find(
141
- (f) => f.code === 'dynamic-route-missing-get-static-paths',
142
- )!
143
- expect(finding.location.relPath).toContain('[id].tsx')
144
- expect(finding.message).toContain('getStaticPaths')
145
- } finally {
146
- fixture.cleanup()
147
- }
148
- })
149
-
150
- it('FIRES for catch-all [...slug].tsx without getStaticPaths', () => {
151
- const fixture = makeFixture()
152
- try {
153
- fixture.write(
154
- 'examples/myapp/src/routes/blog/[...slug].tsx',
155
- 'export default () => null',
156
- )
157
- const result = auditSsg(fixture.root)
158
- expect(findingCodes(result)).toContain('dynamic-route-missing-get-static-paths')
159
- } finally {
160
- fixture.cleanup()
161
- }
162
- })
163
-
164
- it('does NOT fire for [id].tsx WITH `export const getStaticPaths`', () => {
165
- const fixture = makeFixture()
166
- try {
167
- fixture.write(
168
- 'examples/myapp/src/routes/posts/[id].tsx',
169
- `export const getStaticPaths = () => [{ params: { id: '1' } }]
170
- export default () => null`,
171
- )
172
- const result = auditSsg(fixture.root)
173
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
174
- } finally {
175
- fixture.cleanup()
176
- }
177
- })
178
-
179
- it('does NOT fire for [id].tsx WITH `export async function getStaticPaths`', () => {
180
- const fixture = makeFixture()
181
- try {
182
- fixture.write(
183
- 'examples/myapp/src/routes/posts/[id].tsx',
184
- `export async function getStaticPaths() { return [{ params: { id: '1' } }] }
185
- export default () => null`,
186
- )
187
- const result = auditSsg(fixture.root)
188
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
189
- } finally {
190
- fixture.cleanup()
191
- }
192
- })
193
-
194
- it('does NOT fire for static routes (no [param] in filename)', () => {
195
- const fixture = makeFixture()
196
- try {
197
- fixture.write('examples/myapp/src/routes/about.tsx', 'export default () => null')
198
- fixture.write('examples/myapp/src/routes/index.tsx', 'export default () => null')
199
- const result = auditSsg(fixture.root)
200
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
201
- } finally {
202
- fixture.cleanup()
203
- }
204
- })
205
-
206
- it('does NOT fire for _layout / _error / _loading / _404 even with brackets in name', () => {
207
- // Defensive: special files with bracketed names (unlikely but
208
- // possible — `_layout.[locale].tsx`) shouldn't be flagged.
209
- const fixture = makeFixture()
210
- try {
211
- fixture.write(
212
- 'examples/myapp/src/routes/_layout.tsx',
213
- 'export const layout = () => null',
214
- )
215
- fixture.write('examples/myapp/src/routes/_404.tsx', 'export default () => null')
216
- const result = auditSsg(fixture.root)
217
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
218
- } finally {
219
- fixture.cleanup()
220
- }
221
- })
222
-
223
- // M3.B follow-up — false-positive class surfaced by cpa-pw-blog's
224
- // `api/echo/[...path].ts` (real-world API route with bracket
225
- // filename). API routes are runtime-only by definition.
226
- it('does NOT fire for API routes under routes/api/ (path-based skip)', () => {
227
- const fixture = makeFixture()
228
- try {
229
- fixture.write(
230
- 'examples/myapp/src/routes/api/echo/[...path].ts',
231
- `export function GET({ params }) {
232
- return new Response(\`segments: \${params.path}\`)
233
- }`,
234
- )
235
- const result = auditSsg(fixture.root)
236
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
237
- } finally {
238
- fixture.cleanup()
239
- }
240
- })
241
-
242
- it('does NOT fire for files without `export default` outside api/ (export-shape skip)', () => {
243
- // Method-handler-only file outside api/ — covers users who put API
244
- // routes somewhere non-conventional. Page routes structurally
245
- // require a default export, so absence is a reliable signal.
246
- const fixture = makeFixture()
247
- try {
248
- fixture.write(
249
- 'examples/myapp/src/routes/webhook/[id].ts',
250
- `export function POST({ request }) {
251
- return new Response('ok')
252
- }`,
253
- )
254
- const result = auditSsg(fixture.root)
255
- expect(findingCodes(result)).not.toContain('dynamic-route-missing-get-static-paths')
256
- } finally {
257
- fixture.cleanup()
258
- }
259
- })
260
-
261
- it('STILL fires on page routes (with default export) missing getStaticPaths', () => {
262
- // Sanity — the export-shape skip doesn't accidentally silence the
263
- // rule on legitimate page routes.
264
- const fixture = makeFixture()
265
- try {
266
- fixture.write(
267
- 'examples/myapp/src/routes/posts/[id].tsx',
268
- `export const someHelper = 1
269
- export default function Post() { return null }`,
270
- )
271
- const result = auditSsg(fixture.root)
272
- expect(findingCodes(result)).toContain('dynamic-route-missing-get-static-paths')
273
- } finally {
274
- fixture.cleanup()
275
- }
276
- })
277
- })
278
-
279
- // ═══════════════════════════════════════════════════════════════════════════════
280
- // 3) non-literal-revalidate-export
281
- // ═══════════════════════════════════════════════════════════════════════════════
282
-
283
- describe('auditSsg — non-literal-revalidate-export', () => {
284
- it('FIRES for `export const revalidate = TTL` (identifier reference)', () => {
285
- const fixture = makeFixture()
286
- try {
287
- fixture.write(
288
- 'examples/myapp/src/routes/posts/index.tsx',
289
- `const TTL = 60
290
- export const revalidate = TTL
291
- export default () => null`,
292
- )
293
- const result = auditSsg(fixture.root)
294
- expect(findingCodes(result)).toContain('non-literal-revalidate-export')
295
- const finding = result.findings.find((f) => f.code === 'non-literal-revalidate-export')!
296
- expect(finding.message).toContain('NUMERIC LITERAL')
297
- } finally {
298
- fixture.cleanup()
299
- }
300
- })
301
-
302
- it('FIRES for `export const revalidate = 30 * 60` (arithmetic)', () => {
303
- const fixture = makeFixture()
304
- try {
305
- fixture.write(
306
- 'examples/myapp/src/routes/posts/index.tsx',
307
- `export const revalidate = 30 * 60
308
- export default () => null`,
309
- )
310
- const result = auditSsg(fixture.root)
311
- expect(findingCodes(result)).toContain('non-literal-revalidate-export')
312
- } finally {
313
- fixture.cleanup()
314
- }
315
- })
316
-
317
- it('does NOT fire for `export const revalidate = 60` (numeric literal)', () => {
318
- const fixture = makeFixture()
319
- try {
320
- fixture.write(
321
- 'examples/myapp/src/routes/posts/index.tsx',
322
- `export const revalidate = 60
323
- export default () => null`,
324
- )
325
- const result = auditSsg(fixture.root)
326
- expect(findingCodes(result)).not.toContain('non-literal-revalidate-export')
327
- } finally {
328
- fixture.cleanup()
329
- }
330
- })
331
-
332
- it('does NOT fire for `export const revalidate = false` (false keyword)', () => {
333
- const fixture = makeFixture()
334
- try {
335
- fixture.write(
336
- 'examples/myapp/src/routes/posts/index.tsx',
337
- `export const revalidate = false
338
- export default () => null`,
339
- )
340
- const result = auditSsg(fixture.root)
341
- expect(findingCodes(result)).not.toContain('non-literal-revalidate-export')
342
- } finally {
343
- fixture.cleanup()
344
- }
345
- })
346
-
347
- it('does NOT fire when there is no revalidate export at all', () => {
348
- const fixture = makeFixture()
349
- try {
350
- fixture.write('examples/myapp/src/routes/about.tsx', 'export default () => null')
351
- const result = auditSsg(fixture.root)
352
- expect(findingCodes(result)).not.toContain('non-literal-revalidate-export')
353
- } finally {
354
- fixture.cleanup()
355
- }
356
- })
357
- })
358
-
359
- // ═══════════════════════════════════════════════════════════════════════════════
360
- // Formatter
361
- // ═══════════════════════════════════════════════════════════════════════════════
362
-
363
- describe('formatSsgAudit', () => {
364
- it('renders a clean header when there are no findings', () => {
365
- const fixture = makeFixture()
366
- try {
367
- fixture.write('examples/myapp/src/routes/index.tsx', 'export default () => null')
368
- const result = auditSsg(fixture.root)
369
- const output = formatSsgAudit(result)
370
- expect(output).toContain('SSG audit')
371
- expect(output).toContain('No SSG / ISR issues found')
372
- } finally {
373
- fixture.cleanup()
374
- }
375
- })
376
-
377
- it('renders each finding with relPath:line:col + actionable message', () => {
378
- const fixture = makeFixture()
379
- try {
380
- fixture.write('examples/myapp/src/routes/_404.tsx', 'export default () => null')
381
- const result = auditSsg(fixture.root)
382
- const output = formatSsgAudit(result)
383
- expect(output).toContain('[404-outside-layout-dir]')
384
- expect(output).toContain('_404.tsx')
385
- expect(output).toContain('_layout.tsx')
386
- } finally {
387
- fixture.cleanup()
388
- }
389
- })
390
-
391
- it('mentions the --json flag for machine-readable output', () => {
392
- const fixture = makeFixture()
393
- try {
394
- fixture.write('examples/myapp/src/routes/posts/[id].tsx', 'export default () => null')
395
- const result = auditSsg(fixture.root)
396
- const output = formatSsgAudit(result)
397
- expect(output).toContain('--json')
398
- } finally {
399
- fixture.cleanup()
400
- }
401
- })
402
- })
@@ -1,64 +0,0 @@
1
- import { transformJSX } from '../jsx'
2
-
3
- const t = (code: string) => transformJSX(code, 'input.tsx').code
4
- const hasBind = (out: string) => /\b_bindText\(|\b_bind\(/.test(out)
5
-
6
- // ── Static-text baking contract (perf-correctness regression gate) ──────────
7
- //
8
- // The compiler bakes a provably-static `{expr}` child straight into the
9
- // `_tpl()` HTML and emits NO `_bind`/`_bindText` for it. The Reactivity
10
- // Lens's `static-text` kind is a faithful RECORD of exactly this codegen
11
- // branch — it is NOT an independent oracle the emitter could disagree
12
- // with. So "the analysis proves static but codegen still binds" is
13
- // structurally impossible; the only thing that can erode this is an
14
- // `isDynamic` PRECISION regression that starts treating a static shape as
15
- // dynamic (a silent per-mount allocation + subscription leak with no
16
- // other guard) OR — the inverse correctness bug — under-wrapping a truly
17
- // reactive shape.
18
- //
19
- // This suite is self-discriminating, which IS its bisect proof: it
20
- // asserts BOTH regimes against the SAME `isDynamic` decision. An
21
- // over-broad regression fails the "baked" half; an under-broad
22
- // (correctness) regression fails the "reactive" half. Both halves are
23
- // demonstrated reachable by the empirical probe that motivated this gate
24
- // (every static shape baked; the unknown-call / signal / prop shapes
25
- // bound) — neither half passes vacuously.
26
-
27
- describe('static-text baking — provably-static children are baked, never _bind', () => {
28
- const STATIC: [string, string][] = [
29
- ['module-const string ref', `const N='hi'; export const C=()=> <div>{N}</div>`],
30
- ['string literal', `export const C=()=> <div>{'hi'}</div>`],
31
- ['number literal', `export const C=()=> <div>{42}</div>`],
32
- ['static ternary on a module const', `const F=false; export const C=()=> <div>{F ? 'a' : 'b'}</div>`],
33
- ['template literal interpolating only a const', `const N='x'; export const C=()=> <div>{\`v-\${N}\`}</div>`],
34
- ['module-const array .length', `const A=[1,2,3]; export const C=()=> <div>{A.length}</div>`],
35
- ['pure built-in call (Math.max)', `export const C=()=> <div>{Math.max(1,2)}</div>`],
36
- ['const string concat', `const A='a',B='b'; export const C=()=> <div>{A+B}</div>`],
37
- ]
38
- for (const [name, src] of STATIC) {
39
- test(`bakes (no _bind): ${name}`, () => {
40
- const out = t(src)
41
- expect(out).toContain('_tpl(')
42
- expect(hasBind(out)).toBe(false)
43
- })
44
- }
45
- })
46
-
47
- describe('static-text baking — genuinely-reactive / unprovable children DO bind (discriminator)', () => {
48
- const REACTIVE: [string, string][] = [
49
- [
50
- 'signal read',
51
- `import {signal} from '@pyreon/reactivity'; const s=signal(0); export const C=()=> <div>{s()}</div>`,
52
- ],
53
- ['prop access', `export const C=(props:any)=> <div>{props.x}</div>`],
54
- [
55
- 'unknown local call (conservatively reactive — correct: cannot prove signal-free)',
56
- `function f(){return 'z'} export const C=()=> <div>{f()}</div>`,
57
- ],
58
- ]
59
- for (const [name, src] of REACTIVE) {
60
- test(`binds: ${name}`, () => {
61
- expect(hasBind(t(src))).toBe(true)
62
- })
63
- }
64
- })