@pyreon/compiler 0.18.0 → 0.20.0

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 (41) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/index.js +2081 -1262
  3. package/lib/types/index.d.ts +310 -125
  4. package/package.json +14 -12
  5. package/src/defer-inline.ts +397 -157
  6. package/src/index.ts +14 -2
  7. package/src/jsx.ts +784 -19
  8. package/src/load-native.ts +1 -0
  9. package/src/manifest.ts +280 -0
  10. package/src/pyreon-intercept.ts +164 -0
  11. package/src/react-intercept.ts +59 -0
  12. package/src/reactivity-lens.ts +190 -0
  13. package/src/tests/backend-parity-r7-r9.test.ts +91 -0
  14. package/src/tests/backend-prop-derived-callback-divergence.test.ts +74 -0
  15. package/src/tests/collapse-bail-census.test.ts +245 -0
  16. package/src/tests/collapse-key-source-hygiene.test.ts +88 -0
  17. package/src/tests/defer-inline.test.ts +209 -21
  18. package/src/tests/detector-tag-consistency.test.ts +2 -0
  19. package/src/tests/element-valued-const-child.test.ts +61 -0
  20. package/src/tests/falsy-child-characterization.test.ts +48 -0
  21. package/src/tests/malformed-input-resilience.test.ts +50 -0
  22. package/src/tests/manifest-snapshot.test.ts +55 -0
  23. package/src/tests/native-equivalence.test.ts +104 -3
  24. package/src/tests/partial-collapse-detector.test.ts +121 -0
  25. package/src/tests/partial-collapse-emit.test.ts +104 -0
  26. package/src/tests/partial-collapse-robustness.test.ts +53 -0
  27. package/src/tests/prop-derived-shadow.test.ts +96 -0
  28. package/src/tests/pure-call-reactive-args.test.ts +50 -0
  29. package/src/tests/pyreon-intercept.test.ts +189 -0
  30. package/src/tests/r13-callback-stmt-equivalence.test.ts +58 -0
  31. package/src/tests/r14-ssr-mode-parity.test.ts +51 -0
  32. package/src/tests/r15-elemconst-propderived.test.ts +47 -0
  33. package/src/tests/r19-defer-inline-robust.test.ts +54 -0
  34. package/src/tests/r20-backend-equivalence-sweep.test.ts +50 -0
  35. package/src/tests/react-intercept.test.ts +50 -2
  36. package/src/tests/reactivity-lens.test.ts +170 -0
  37. package/src/tests/rocketstyle-collapse.test.ts +208 -0
  38. package/src/tests/signal-autocall-shadow.test.ts +86 -0
  39. package/src/tests/sourcemap-fidelity.test.ts +77 -0
  40. package/src/tests/static-text-baking.test.ts +64 -0
  41. package/src/tests/transform-state-isolation.test.ts +49 -0
@@ -5,41 +5,44 @@
5
5
  * Rewrites:
6
6
  *
7
7
  * import { Modal } from './Modal'
8
- * <Defer when={open()}><Modal /></Defer>
8
+ * <Defer when={open()}><Modal title="hi" /></Defer>
9
9
  *
10
10
  * into:
11
11
  *
12
- * <Defer when={open()} chunk={() => import('./Modal').then(m => ({ default: m.Modal }))}>
13
- * {C => <C />}
12
+ * <Defer when={open()} chunk={() => import('./Modal').then((__m) => ({ default: __m.Modal }))}>
13
+ * {(__C) => <__C title="hi" />}
14
14
  * </Defer>
15
15
  *
16
16
  * The static `import { Modal } from './Modal'` is removed when `Modal` is
17
17
  * referenced ONLY inside the Defer subtree — otherwise Rolldown would
18
18
  * bundle the module statically and the dynamic import becomes a no-op.
19
19
  *
20
- * Scope of v1 (this file):
21
- * - Single Defer element per file (no nested handling — bail otherwise).
22
- * - Children: exactly ONE JSXElement, self-closing, capitalised name
23
- * (component reference), no props. Props or multiple children leave
24
- * the Defer untransformed (user must use the explicit `chunk` form).
25
- * - Imports: named OR default. Namespace imports (`import * as Mod`)
26
- * and destructured-renamed (`{ X as Y }`) not handled in v1.
20
+ * Scope (v2 post #587 + this PR):
21
+ * - Multiple Defer elements per file: each rewritten independently.
22
+ * - Children: exactly ONE JSXElement, capitalised name (component
23
+ * reference). Self-closing OR with children. **Props ARE allowed**
24
+ * (post-v2) and pass through unchanged into the render-prop body
25
+ * closure capture works naturally because the render-prop arrow
26
+ * captures the surrounding lexical scope.
27
+ * - Multiple non-whitespace children → bail with a warning.
28
+ * User must use the explicit `chunk` form with a render-prop.
29
+ * - Imports: default, named, **renamed** (`{ X as Y }`). Namespace
30
+ * imports (`* as M` + `<M.X />`) NOT supported — bail with a warning.
31
+ * - **Multi-specifier imports** (`{ A, B } from './x'`): only the
32
+ * binding used in Defer is removed; siblings stay intact.
27
33
  * - Triggers (`when={...}`, `on="visible"`, `on="idle"`) pass through.
28
34
  * - Other props on `<Defer>` (e.g. `fallback`) pass through.
29
35
  *
30
36
  * The transform is intentionally conservative — anything unusual leaves
31
- * the source unchanged + emits a warning. v2 follow-ups can relax these
32
- * constraints with closure-capture handling, namespace imports, etc.
33
- *
34
- * Pipeline: this runs BEFORE `transformJSX()` in the vite plugin. The
35
- * output is still JSX — `transformJSX` then converts it to `h()` /
36
- * `_tpl()` calls as usual.
37
+ * the source unchanged + emits a warning. Pipeline: runs BEFORE
38
+ * `transformJSX()` in the vite plugin. The output is still JSX —
39
+ * `transformJSX` then converts to runtime calls as usual.
37
40
  */
38
41
  interface DeferInlineWarning {
39
42
  message: string;
40
43
  line: number;
41
44
  column: number;
42
- code: 'defer-inline/multiple-children' | 'defer-inline/non-component-child' | 'defer-inline/child-has-props' | 'defer-inline/import-not-found' | 'defer-inline/import-used-elsewhere' | 'defer-inline/unsupported-import-shape';
45
+ code: 'defer-inline/multiple-children' | 'defer-inline/non-component-child' | 'defer-inline/import-not-found' | 'defer-inline/import-used-elsewhere' | 'defer-inline/unsupported-import-shape';
43
46
  }
44
47
  interface DeferInlineResult {
45
48
  /** Transformed source — same as input when no transform applied. */
@@ -49,23 +52,6 @@ interface DeferInlineResult {
49
52
  /** Soft warnings for cases the transform deliberately skipped. */
50
53
  warnings: DeferInlineWarning[];
51
54
  }
52
- /**
53
- * Main entry. Returns the (possibly transformed) source plus the list
54
- * of warnings for cases the transform deliberately skipped.
55
- *
56
- * Bails (returns input unchanged with `changed: false`) when:
57
- * - No `<Defer>` JSX element appears in the file (fast path).
58
- * - The file fails to parse (syntax error — let downstream handle).
59
- * - No `<Defer>` matches the inline-eligible shape.
60
- *
61
- * Per-Defer skips with a warning:
62
- * - Multiple children → user must use render-prop form
63
- * - Child has props → user must use render-prop form
64
- * - Child name isn't imported → can't resolve the chunk source
65
- * - Child binding is used outside the Defer subtree → can't remove
66
- * the static import (dynamic import would be a no-op via Rolldown's
67
- * same-module dedup)
68
- */
69
55
  declare function transformDeferInline(code: string, filename?: string): DeferInlineResult;
70
56
  //#endregion
71
57
  //#region src/jsx.d.ts
@@ -98,6 +84,22 @@ declare function transformDeferInline(code: string, filename?: string): DeferInl
98
84
  *
99
85
  * Implementation: Rust native binary (napi-rs) when available, JS fallback via oxc-parser.
100
86
  */
87
+ /**
88
+ * V3 source map shape returned by the JS backend. Structurally exactly
89
+ * magic-string's `SourceMap` (a valid V3 map plus `.toString()`/`.toUrl()`),
90
+ * declared locally so `TransformResult` carries no hard type dependency on
91
+ * magic-string's exported types.
92
+ */
93
+ interface GeneratedSourceMap {
94
+ version: number;
95
+ file?: string;
96
+ sources: string[];
97
+ sourcesContent?: (string | null)[];
98
+ names: string[];
99
+ mappings: string;
100
+ toString(): string;
101
+ toUrl(): string;
102
+ }
101
103
  interface CompilerWarning {
102
104
  /** Warning message */
103
105
  message: string;
@@ -108,6 +110,34 @@ interface CompilerWarning {
108
110
  /** Warning code for filtering */
109
111
  code: 'signal-call-in-jsx' | 'missing-key-on-for' | 'signal-in-static-prop' | 'circular-prop-derived';
110
112
  }
113
+ /**
114
+ * Reactivity-lens kinds. Each is a RECORD of a codegen decision the compiler
115
+ * already made — never an approximation. Positive claims (`reactive*`) are
116
+ * emitted ONLY where the compiler provably wrapped/tracked the span; absence
117
+ * of a span is "not asserted", never an implicit static claim. `static-text`
118
+ * is the high-precision negative: the literal `else` branch of the
119
+ * reactive-vs-static text decision (the "this `{x}` is baked once / dead"
120
+ * footgun signal when the author expected reactivity).
121
+ */
122
+ type ReactivityKind = 'reactive' | 'reactive-prop' | 'reactive-attr' | 'static-text' | 'hoisted-static';
123
+ interface ReactivitySpan {
124
+ /** Source byte offset (start) of the spanned expression in the INPUT. */
125
+ start: number;
126
+ /** Source byte offset (end). */
127
+ end: number;
128
+ /** 1-based start line. */
129
+ line: number;
130
+ /** 0-based start column. */
131
+ column: number;
132
+ /** 1-based end line. */
133
+ endLine: number;
134
+ /** 0-based end column. */
135
+ endColumn: number;
136
+ /** Which codegen decision this span records. */
137
+ kind: ReactivityKind;
138
+ /** Human-readable, editor-facing one-liner explaining the decision. */
139
+ detail: string;
140
+ }
111
141
  interface TransformResult {
112
142
  /** Transformed source code (JSX preserved, only expression containers modified) */
113
143
  code: string;
@@ -115,6 +145,22 @@ interface TransformResult {
115
145
  usesTemplates?: boolean;
116
146
  /** Compiler warnings for common mistakes */
117
147
  warnings: CompilerWarning[];
148
+ /**
149
+ * Source map (V3) for the transform — present on the JS backend whenever a
150
+ * transformation actually occurred. `undefined` when nothing changed (the
151
+ * emitted code is byte-identical to the input, so no remapping is needed)
152
+ * and on the native backend (a Rust-side map is a scoped follow-up). The
153
+ * object is magic-string's `SourceMap`: it is a valid V3 map AND has
154
+ * `.toString()` / `.toUrl()`, so Vite/Rollup consume it directly.
155
+ */
156
+ map?: GeneratedSourceMap;
157
+ /**
158
+ * Reactivity-lens spans — populated ONLY when `TransformOptions.reactivityLens`
159
+ * is `true`. Additive: codegen output is byte-identical whether or not this is
160
+ * collected. Each span is a faithful record of a reactivity decision the
161
+ * compiler made for that source range. See {@link ReactivitySpan}.
162
+ */
163
+ reactivityLens?: ReactivitySpan[];
118
164
  }
119
165
  interface TransformOptions {
120
166
  /**
@@ -136,101 +182,84 @@ interface TransformOptions {
136
182
  * // {count} in JSX → {() => count()}
137
183
  */
138
184
  knownSignals?: string[];
185
+ /**
186
+ * Collect the {@link ReactivitySpan} sidecar (`TransformResult.reactivityLens`).
187
+ * Default `false`. Purely additive — the emitted `code` is byte-identical
188
+ * whether this is on or off (asserted by the compiler equivalence tests).
189
+ * The lens records reactivity decisions the compiler ALREADY makes for
190
+ * codegen; it never runs a second analysis pass.
191
+ */
192
+ reactivityLens?: boolean;
193
+ /**
194
+ * P0 — compile-time rocketstyle wrapper collapse. OFF unless the Vite
195
+ * plugin supplies this (opt-in `pyreon({ collapse: true })`). The plugin
196
+ * scans the module's imports for collapsible component candidates,
197
+ * SSR-resolves each literal-prop call site once (real component, light
198
+ * + dark), and passes the resolved `sites` map keyed by
199
+ * {@link rocketstyleCollapseKey}. The compiler only DETECTS the
200
+ * collapsible shape (bail catalogue — every dimension prop a string
201
+ * literal, no spread, static-text children) and EMITS the collapsed
202
+ * `_rsCollapse` call + the once-per-module rule injection; it never
203
+ * runs the rocketstyle chain itself (RFC decision 2).
204
+ */
205
+ collapseRocketstyle?: {
206
+ /** Component names imported into this module that MAY collapse. */candidates: Set<string>; /** key → resolved emission data (absent ⇒ bail, keep normal mount). */
207
+ sites: Map<string, {
208
+ templateHtml: string;
209
+ lightClass: string;
210
+ darkClass: string;
211
+ rules: string[];
212
+ ruleKey: string;
213
+ }>; /** Live mode accessor to thread for dual-emit (RFC decision 1). */
214
+ mode: {
215
+ name: string;
216
+ source: string;
217
+ }; /** Module specifier for `_rsCollapse`. Default `@pyreon/runtime-dom`. */
218
+ runtimeDomSource?: string; /** Module specifier for the styler `sheet`. Default `@pyreon/styler`. */
219
+ stylerSource?: string;
220
+ };
139
221
  }
140
- declare function transformJSX(code: string, filename?: string, options?: TransformOptions): TransformResult;
141
- /** JS fallback implementation — used when the native binary isn't available. */
142
- declare function transformJSX_JS(code: string, filename?: string, options?: TransformOptions): TransformResult;
143
- //#endregion
144
- //#region src/project-scanner.d.ts
145
222
  /**
146
- * Project scanner extracts route, component, and island information from source files.
223
+ * Canonical key for a collapsible rocketstyle call site. The Vite plugin
224
+ * computes this when it resolves a site; the compiler recomputes the
225
+ * IDENTICAL key from the JSX node to look the resolution up. Stable
226
+ * ordering of props so attribute order in source doesn't change the key.
147
227
  */
148
- interface RouteInfo {
149
- path: string;
150
- name?: string | undefined;
151
- component?: string | undefined;
152
- hasLoader: boolean;
153
- hasGuard: boolean;
154
- params: string[];
155
- }
156
- interface ComponentInfo {
157
- name: string;
158
- file: string;
159
- hasSignals: boolean;
160
- signalNames: string[];
161
- props: string[];
162
- }
163
- interface IslandInfo {
164
- name: string;
165
- file: string;
166
- hydrate: string;
167
- }
168
- interface ProjectContext {
169
- framework: 'pyreon';
170
- version: string;
171
- generatedAt: string;
172
- routes: RouteInfo[];
173
- components: ComponentInfo[];
174
- islands: IslandInfo[];
175
- }
176
- declare function generateContext(cwd: string): ProjectContext;
177
- //#endregion
178
- //#region src/react-intercept.d.ts
228
+ declare function rocketstyleCollapseKey(componentName: string, props: Record<string, string>, childrenText: string): string;
179
229
  /**
180
- * React Pattern Interceptor detects React/Vue patterns in code and provides
181
- * structured diagnostics with exact fix suggestions for AI-assisted migration.
182
- *
183
- * Two modes:
184
- * - `detectReactPatterns(code)` — returns diagnostics only (non-destructive)
185
- * - `migrateReactCode(code)` — applies auto-fixes and returns transformed code
186
- *
187
- * Designed for three consumers:
188
- * 1. Compiler pre-pass (warnings during build)
189
- * 2. CLI `pyreon doctor` (project-wide scanning)
190
- * 3. MCP server `migrate_react` / `validate` tools (AI agent integration)
230
+ * A collapsible call site found by {@link scanCollapsibleSites}.
231
+ * `componentName` is the LOCAL JSX tag (post-import-alias) — it MUST be
232
+ * what `rocketstyleCollapseKey` is computed from on BOTH sides so the
233
+ * plugin's resolved `sites` map keys match the compiler's lookups.
191
234
  */
192
- type ReactDiagnosticCode = 'react-import' | 'react-dom-import' | 'react-router-import' | 'use-state' | 'use-effect-mount' | 'use-effect-deps' | 'use-effect-no-deps' | 'use-memo' | 'use-callback' | 'use-ref-dom' | 'use-ref-box' | 'use-reducer' | 'use-layout-effect' | 'memo-wrapper' | 'forward-ref' | 'class-name-prop' | 'html-for-prop' | 'on-change-input' | 'dangerously-set-inner-html' | 'dot-value-signal' | 'array-map-jsx' | 'key-on-for-child' | 'create-context-import' | 'use-context-import';
193
- interface ReactDiagnostic {
194
- /** Machine-readable code for filtering and programmatic handling */
195
- code: ReactDiagnosticCode;
196
- /** Human-readable message explaining the issue */
197
- message: string;
198
- /** 1-based line number */
199
- line: number;
200
- /** 0-based column */
201
- column: number;
202
- /** The code as written */
203
- current: string;
204
- /** The suggested Pyreon equivalent */
205
- suggested: string;
206
- /** Whether migrateReactCode can auto-fix this */
207
- fixable: boolean;
208
- }
209
- interface MigrationChange {
210
- type: 'replace' | 'remove' | 'add';
211
- line: number;
212
- description: string;
213
- }
214
- interface MigrationResult {
215
- /** Transformed source code */
216
- code: string;
217
- /** All detected patterns (including unfixable ones) */
218
- diagnostics: ReactDiagnostic[];
219
- /** Description of changes applied */
220
- changes: MigrationChange[];
221
- }
222
- declare function detectReactPatterns(code: string, filename?: string): ReactDiagnostic[];
223
- declare function migrateReactCode(code: string, filename?: string): MigrationResult;
224
- /** Fast regex check — returns true if code likely contains React patterns worth analyzing */
225
- declare function hasReactPatterns(code: string): boolean;
226
- interface ErrorDiagnosis {
227
- cause: string;
228
- fix: string;
229
- fixCode?: string | undefined;
230
- related?: string | undefined;
235
+ interface CollapsibleSite {
236
+ /** Local JSX tag name (the key + the compiler's detection use this). */
237
+ componentName: string;
238
+ /** Module specifier the component was imported from (for the resolver). */
239
+ source: string;
240
+ /** Imported binding name at `source` (may differ from local if aliased). */
241
+ importedName: string;
242
+ /** Literal string-valued props (the only shape the slice collapses). */
243
+ props: Record<string, string>;
244
+ /** Static text children (trimmed; empty ⇒ none). */
245
+ childrenText: string;
246
+ /** `rocketstyleCollapseKey(componentName, props, childrenText)`. */
247
+ key: string;
231
248
  }
232
- /** Diagnose an error message and return structured fix information */
233
- declare function diagnoseError(error: string): ErrorDiagnosis | null;
249
+ /**
250
+ * Pure detector finds every collapsible rocketstyle call site in a
251
+ * module. Used by `@pyreon/vite-plugin` to know which (component, props,
252
+ * text) tuples to SSR-resolve. The bail catalogue here MUST stay
253
+ * byte-identical to `tryRocketstyleCollapse`'s (RFC decision 3): a
254
+ * candidate PascalCase tag whose import source is in `collapsibleSources`,
255
+ * every attr a plain string literal (no spread, no `{expr}`, no boolean
256
+ * attr), children empty or static text only. A consistency test asserts
257
+ * the keys this produces equal the keys the compiler looks up.
258
+ */
259
+ declare function scanCollapsibleSites(code: string, filename: string, collapsibleSources: Set<string>): CollapsibleSite[];
260
+ declare function transformJSX(code: string, filename?: string, options?: TransformOptions): TransformResult;
261
+ /** JS fallback implementation — used when the native binary isn't available. */
262
+ declare function transformJSX_JS(code: string, filename?: string, options?: TransformOptions): TransformResult;
234
263
  //#endregion
235
264
  //#region src/pyreon-intercept.d.ts
236
265
  /**
@@ -249,6 +278,12 @@ declare function diagnoseError(error: string): ErrorDiagnosis | null;
249
278
  * the component signature; reading is captured once
250
279
  * and loses reactivity. Access `props.foo` instead
251
280
  * or use `splitProps(props, [...])`.
281
+ * - `props-destructured-body` — `const { foo } = props` written
282
+ * SYNCHRONOUSLY in a component body — the body-scope
283
+ * companion to `props-destructured`. Same capture-
284
+ * once death; nested-function destructures (handler
285
+ * / effect / returned accessor) are NOT flagged
286
+ * (they re-read `props` per invocation).
252
287
  * - `process-dev-gate` — `typeof process !== 'undefined' &&
253
288
  * process.env.NODE_ENV !== 'production'` is dead
254
289
  * code in real Vite browser bundles. Use
@@ -304,7 +339,7 @@ declare function diagnoseError(error: string): ErrorDiagnosis | null;
304
339
  * 2. CLI `pyreon doctor`
305
340
  * 3. MCP server `validate` tool
306
341
  */
307
- type PyreonDiagnosticCode = 'for-missing-by' | 'for-with-key' | 'props-destructured' | 'process-dev-gate' | 'empty-theme' | 'raw-add-event-listener' | 'raw-remove-event-listener' | 'date-math-random-id' | 'on-click-undefined' | 'signal-write-as-call' | 'static-return-null-conditional' | 'as-unknown-as-vnodechild' | 'island-never-with-registry-entry';
342
+ type PyreonDiagnosticCode = 'for-missing-by' | 'for-with-key' | 'props-destructured' | 'props-destructured-body' | 'process-dev-gate' | 'empty-theme' | 'raw-add-event-listener' | 'raw-remove-event-listener' | 'date-math-random-id' | 'on-click-undefined' | 'signal-write-as-call' | 'static-return-null-conditional' | 'as-unknown-as-vnodechild' | 'island-never-with-registry-entry' | 'query-options-as-function';
308
343
  interface PyreonDiagnostic {
309
344
  /** Machine-readable code for filtering + programmatic handling */
310
345
  code: PyreonDiagnosticCode;
@@ -325,6 +360,156 @@ declare function detectPyreonPatterns(code: string, filename?: string): PyreonDi
325
360
  /** Fast regex pre-filter — returns true if the code is worth a full AST walk. */
326
361
  declare function hasPyreonPatterns(code: string): boolean;
327
362
  //#endregion
363
+ //#region src/reactivity-lens.d.ts
364
+ /** A footgun finding adds `'footgun'` to the structural codegen kinds. */
365
+ type ReactivityFindingKind = ReactivityKind | 'footgun';
366
+ interface ReactivityFinding {
367
+ /** Structural codegen decision, or `'footgun'` for a detected anti-pattern. */
368
+ kind: ReactivityFindingKind;
369
+ /** 1-based line. */
370
+ line: number;
371
+ /** 0-based column. */
372
+ column: number;
373
+ /** 1-based end line. */
374
+ endLine: number;
375
+ /** 0-based end column. */
376
+ endColumn: number;
377
+ /** Editor-facing one-liner. For footguns, the detector's message. */
378
+ detail: string;
379
+ /**
380
+ * For `'footgun'` findings: the static-detector code (e.g.
381
+ * `props-destructured`) so the editor surface can deep-link the
382
+ * anti-pattern catalogue. Absent for structural findings.
383
+ */
384
+ code?: PyreonDiagnosticCode;
385
+ /** For `'footgun'` findings: whether a mechanical auto-fix is safe. */
386
+ fixable?: boolean;
387
+ }
388
+ interface AnalyzeReactivityResult {
389
+ /** Sorted (line, column) findings — structural facts + footguns merged. */
390
+ findings: ReactivityFinding[];
391
+ /**
392
+ * Raw compiler spans (pre-merge), kept so the drift gate can assert the
393
+ * lens kind faithfully records the codegen decision without re-deriving.
394
+ */
395
+ spans: ReactivitySpan[];
396
+ }
397
+ /**
398
+ * Analyze a source file's reactivity. Pure, side-effect-free, deterministic.
399
+ *
400
+ * @param code Source text (`.tsx` / `.jsx` / `.ts`).
401
+ * @param filename Used only for parse-mode (`tsx` vs `jsx`) detection.
402
+ * @param options `knownSignals` is forwarded to the compiler so
403
+ * cross-module imported signals are auto-call-aware.
404
+ *
405
+ * @example
406
+ * const { findings } = analyzeReactivity(
407
+ * `function C(){ const {x}=props; return <div>{count()}</div> }`,
408
+ * )
409
+ * // → footgun(props-destructured) on `{x}`, reactive on `count()`
410
+ */
411
+ declare function analyzeReactivity(code: string, filename?: string, options?: {
412
+ knownSignals?: string[];
413
+ }): AnalyzeReactivityResult;
414
+ /**
415
+ * Render an annotated source view for CLI / debugging — every analyzed line
416
+ * followed by its reactivity findings. Not the production surface (that's the
417
+ * LSP inlay hints); this is the spike's "can you see reactivity flow" probe
418
+ * and a stable diff target for tests.
419
+ */
420
+ declare function formatReactivityLens(code: string, result: AnalyzeReactivityResult): string;
421
+ //#endregion
422
+ //#region src/project-scanner.d.ts
423
+ /**
424
+ * Project scanner — extracts route, component, and island information from source files.
425
+ */
426
+ interface RouteInfo {
427
+ path: string;
428
+ name?: string | undefined;
429
+ component?: string | undefined;
430
+ hasLoader: boolean;
431
+ hasGuard: boolean;
432
+ params: string[];
433
+ }
434
+ interface ComponentInfo {
435
+ name: string;
436
+ file: string;
437
+ hasSignals: boolean;
438
+ signalNames: string[];
439
+ props: string[];
440
+ }
441
+ interface IslandInfo {
442
+ name: string;
443
+ file: string;
444
+ hydrate: string;
445
+ }
446
+ interface ProjectContext {
447
+ framework: 'pyreon';
448
+ version: string;
449
+ generatedAt: string;
450
+ routes: RouteInfo[];
451
+ components: ComponentInfo[];
452
+ islands: IslandInfo[];
453
+ }
454
+ declare function generateContext(cwd: string): ProjectContext;
455
+ //#endregion
456
+ //#region src/react-intercept.d.ts
457
+ /**
458
+ * React Pattern Interceptor — detects React/Vue patterns in code and provides
459
+ * structured diagnostics with exact fix suggestions for AI-assisted migration.
460
+ *
461
+ * Two modes:
462
+ * - `detectReactPatterns(code)` — returns diagnostics only (non-destructive)
463
+ * - `migrateReactCode(code)` — applies auto-fixes and returns transformed code
464
+ *
465
+ * Designed for three consumers:
466
+ * 1. Compiler pre-pass (warnings during build)
467
+ * 2. CLI `pyreon doctor` (project-wide scanning)
468
+ * 3. MCP server `migrate_react` / `validate` tools (AI agent integration)
469
+ */
470
+ type ReactDiagnosticCode = 'react-import' | 'react-dom-import' | 'react-router-import' | 'use-state' | 'use-effect-mount' | 'use-effect-deps' | 'use-effect-no-deps' | 'use-memo' | 'use-callback' | 'use-ref-dom' | 'use-ref-box' | 'use-reducer' | 'use-layout-effect' | 'memo-wrapper' | 'forward-ref' | 'class-name-prop' | 'html-for-prop' | 'on-change-input' | 'dangerously-set-inner-html' | 'dot-value-signal' | 'array-map-jsx' | 'key-on-for-child' | 'create-context-import' | 'use-context-import';
471
+ interface ReactDiagnostic {
472
+ /** Machine-readable code for filtering and programmatic handling */
473
+ code: ReactDiagnosticCode;
474
+ /** Human-readable message explaining the issue */
475
+ message: string;
476
+ /** 1-based line number */
477
+ line: number;
478
+ /** 0-based column */
479
+ column: number;
480
+ /** The code as written */
481
+ current: string;
482
+ /** The suggested Pyreon equivalent */
483
+ suggested: string;
484
+ /** Whether migrateReactCode can auto-fix this */
485
+ fixable: boolean;
486
+ }
487
+ interface MigrationChange {
488
+ type: 'replace' | 'remove' | 'add';
489
+ line: number;
490
+ description: string;
491
+ }
492
+ interface MigrationResult {
493
+ /** Transformed source code */
494
+ code: string;
495
+ /** All detected patterns (including unfixable ones) */
496
+ diagnostics: ReactDiagnostic[];
497
+ /** Description of changes applied */
498
+ changes: MigrationChange[];
499
+ }
500
+ declare function detectReactPatterns(code: string, filename?: string): ReactDiagnostic[];
501
+ declare function migrateReactCode(code: string, filename?: string): MigrationResult;
502
+ /** Fast regex check — returns true if code likely contains React patterns worth analyzing */
503
+ declare function hasReactPatterns(code: string): boolean;
504
+ interface ErrorDiagnosis {
505
+ cause: string;
506
+ fix: string;
507
+ fixCode?: string | undefined;
508
+ related?: string | undefined;
509
+ }
510
+ /** Diagnose an error message and return structured fix information */
511
+ declare function diagnoseError(error: string): ErrorDiagnosis | null;
512
+ //#endregion
328
513
  //#region src/test-audit.d.ts
329
514
  type AuditRisk = 'high' | 'medium' | 'low';
330
515
  interface TestAuditEntry {
@@ -457,5 +642,5 @@ interface SsgAuditFormatOptions {
457
642
  }
458
643
  declare function formatSsgAudit(result: SsgAuditResult, _options?: SsgAuditFormatOptions): string;
459
644
  //#endregion
460
- export { type AuditFormatOptions, type AuditRisk, type CompilerWarning, type ComponentInfo, type DeferInlineResult, type DeferInlineWarning, type ErrorDiagnosis, type IslandAuditFormatOptions, type IslandAuditResult, type IslandFinding, type IslandFindingCode, type IslandInfo, type IslandLocation, type MigrationChange, type MigrationResult, type ProjectContext, type PyreonDiagnostic, type PyreonDiagnosticCode, type ReactDiagnostic, type ReactDiagnosticCode, type RouteInfo, type SsgAuditFormatOptions, type SsgAuditResult, type SsgFinding, type SsgFindingCode, type SsgLocation, type TestAuditEntry, type TestAuditResult, type TransformResult, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, transformDeferInline, transformJSX, transformJSX_JS };
645
+ export { type AnalyzeReactivityResult, type AuditFormatOptions, type AuditRisk, type CollapsibleSite, type CompilerWarning, type ComponentInfo, type DeferInlineResult, type DeferInlineWarning, type ErrorDiagnosis, type IslandAuditFormatOptions, type IslandAuditResult, type IslandFinding, type IslandFindingCode, type IslandInfo, type IslandLocation, type MigrationChange, type MigrationResult, type ProjectContext, type PyreonDiagnostic, type PyreonDiagnosticCode, type ReactDiagnostic, type ReactDiagnosticCode, type ReactivityFinding, type ReactivityFindingKind, type ReactivityKind, type ReactivitySpan, type RouteInfo, type SsgAuditFormatOptions, type SsgAuditResult, type SsgFinding, type SsgFindingCode, type SsgLocation, type TestAuditEntry, type TestAuditResult, type TransformResult, analyzeReactivity, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatReactivityLens, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, rocketstyleCollapseKey, scanCollapsibleSites, transformDeferInline, transformJSX, transformJSX_JS };
461
646
  //# sourceMappingURL=index2.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/compiler",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Template and JSX compiler for Pyreon",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/compiler#readme",
6
6
  "bugs": {
@@ -44,22 +44,24 @@
44
44
  "prepublishOnly": "bun run build"
45
45
  },
46
46
  "dependencies": {
47
+ "magic-string": "^0.30.21",
47
48
  "oxc-parser": "^0.129.0"
48
49
  },
49
50
  "optionalDependencies": {
50
- "@pyreon/compiler-darwin-arm64": "workspace:^",
51
- "@pyreon/compiler-darwin-x64": "workspace:^",
52
- "@pyreon/compiler-linux-arm64-gnu": "workspace:^",
53
- "@pyreon/compiler-linux-arm64-musl": "workspace:^",
54
- "@pyreon/compiler-linux-x64-gnu": "workspace:^",
55
- "@pyreon/compiler-linux-x64-musl": "workspace:^",
56
- "@pyreon/compiler-win32-x64-msvc": "workspace:^"
51
+ "@pyreon/compiler-darwin-arm64": "^0.20.0",
52
+ "@pyreon/compiler-darwin-x64": "^0.20.0",
53
+ "@pyreon/compiler-linux-arm64-gnu": "^0.20.0",
54
+ "@pyreon/compiler-linux-arm64-musl": "^0.20.0",
55
+ "@pyreon/compiler-linux-x64-gnu": "^0.20.0",
56
+ "@pyreon/compiler-linux-x64-musl": "^0.20.0",
57
+ "@pyreon/compiler-win32-x64-msvc": "^0.20.0"
57
58
  },
58
59
  "devDependencies": {
59
- "@pyreon/core": "^0.18.0",
60
- "@pyreon/reactivity": "^0.18.0",
61
- "@pyreon/runtime-dom": "^0.18.0",
62
- "@pyreon/test-utils": "^0.13.5",
60
+ "@pyreon/core": "^0.20.0",
61
+ "@pyreon/manifest": "0.13.1",
62
+ "@pyreon/reactivity": "^0.20.0",
63
+ "@pyreon/runtime-dom": "^0.20.0",
64
+ "@pyreon/test-utils": "^0.13.7",
63
65
  "happy-dom": "^20.8.3"
64
66
  },
65
67
  "peerDependencies": {