@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.
- package/package.json +11 -13
- package/src/defer-inline.ts +0 -686
- package/src/event-names.ts +0 -65
- package/src/index.ts +0 -61
- package/src/island-audit.ts +0 -675
- package/src/jsx.ts +0 -2792
- package/src/load-native.ts +0 -156
- package/src/lpih.ts +0 -270
- package/src/manifest.ts +0 -280
- package/src/project-scanner.ts +0 -214
- package/src/pyreon-intercept.ts +0 -1029
- package/src/react-intercept.ts +0 -1217
- package/src/reactivity-lens.ts +0 -190
- package/src/ssg-audit.ts +0 -513
- package/src/test-audit.ts +0 -435
- package/src/tests/backend-parity-r7-r9.test.ts +0 -91
- package/src/tests/backend-prop-derived-callback-divergence.test.ts +0 -74
- package/src/tests/collapse-bail-census.test.ts +0 -330
- package/src/tests/collapse-key-source-hygiene.test.ts +0 -88
- package/src/tests/component-child-no-wrap.test.ts +0 -204
- package/src/tests/defer-inline.test.ts +0 -387
- package/src/tests/depth-stress.test.ts +0 -16
- package/src/tests/detector-tag-consistency.test.ts +0 -101
- package/src/tests/dynamic-collapse-detector.test.ts +0 -164
- package/src/tests/dynamic-collapse-emit.test.ts +0 -192
- package/src/tests/dynamic-collapse-scan.test.ts +0 -111
- package/src/tests/element-valued-const-child.test.ts +0 -61
- package/src/tests/falsy-child-characterization.test.ts +0 -48
- package/src/tests/island-audit.test.ts +0 -524
- package/src/tests/jsx.test.ts +0 -2908
- package/src/tests/load-native.test.ts +0 -53
- package/src/tests/lpih.test.ts +0 -404
- package/src/tests/malformed-input-resilience.test.ts +0 -50
- package/src/tests/manifest-snapshot.test.ts +0 -55
- package/src/tests/native-equivalence.test.ts +0 -924
- package/src/tests/partial-collapse-detector.test.ts +0 -121
- package/src/tests/partial-collapse-emit.test.ts +0 -104
- package/src/tests/partial-collapse-robustness.test.ts +0 -53
- package/src/tests/project-scanner.test.ts +0 -269
- package/src/tests/prop-derived-shadow.test.ts +0 -96
- package/src/tests/pure-call-reactive-args.test.ts +0 -50
- package/src/tests/pyreon-intercept.test.ts +0 -816
- package/src/tests/r13-callback-stmt-equivalence.test.ts +0 -58
- package/src/tests/r14-ssr-mode-parity.test.ts +0 -51
- package/src/tests/r15-elemconst-propderived.test.ts +0 -47
- package/src/tests/r19-defer-inline-robust.test.ts +0 -54
- package/src/tests/r20-backend-equivalence-sweep.test.ts +0 -50
- package/src/tests/react-intercept.test.ts +0 -1104
- package/src/tests/reactivity-lens.test.ts +0 -170
- package/src/tests/rocketstyle-collapse.test.ts +0 -208
- package/src/tests/runtime/control-flow.test.ts +0 -159
- package/src/tests/runtime/dom-properties.test.ts +0 -138
- package/src/tests/runtime/events.test.ts +0 -301
- package/src/tests/runtime/harness.ts +0 -94
- package/src/tests/runtime/pr-352-shapes.test.ts +0 -121
- package/src/tests/runtime/reactive-props.test.ts +0 -81
- package/src/tests/runtime/signals.test.ts +0 -129
- package/src/tests/runtime/whitespace.test.ts +0 -106
- package/src/tests/signal-autocall-shadow.test.ts +0 -86
- package/src/tests/sourcemap-fidelity.test.ts +0 -77
- package/src/tests/ssg-audit.test.ts +0 -402
- package/src/tests/static-text-baking.test.ts +0 -64
- package/src/tests/test-audit.test.ts +0 -549
- 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
|
-
})
|