sinwan-compiler 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -8,3 +8,5 @@
8
8
  * Writes `.sinwan/reactive-props.json` by default.
9
9
  */
10
10
  export declare function runAnalyzeCli(args?: string[]): void;
11
+ /** Runs the CLI only when this module is the process entry point. */
12
+ export declare function runCliIfMain(isMain?: boolean, args?: string[]): void;
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  import {
4
- runAnalyzeCli
5
- } from "./chunk-w580fdrf.js";
4
+ runAnalyzeCli2,
5
+ runCliIfMain2
6
+ } from "./chunk-m4rk009c.js";
6
7
  export {
7
- runAnalyzeCli
8
+ runAnalyzeCli2 as runAnalyzeCli,
9
+ runCliIfMain2 as runCliIfMain
8
10
  };
package/dist/index.js CHANGED
@@ -1,23 +1,23 @@
1
1
  // @bun
2
2
  import {
3
- AnalyzerCache,
4
- analyze,
5
- analyzeModule,
6
- analyzeProject,
7
- autoWrapComponents,
8
- buildFullScope,
9
- collectComponentCallGraph,
10
- collectComponentFunctions,
11
- computeLocalScopes,
12
- containsReactiveValue,
13
- getAllPropNames,
14
- isReactiveValue,
15
- loadMetadata,
16
- propagateReactiveProps,
17
- runAnalyzeCli,
18
- trackReactiveImports,
19
- wrapReactiveExpressions
20
- } from "./chunk-w580fdrf.js";
3
+ autoWrapComponents2,
4
+ analyzeModule2,
5
+ analyzeProject2,
6
+ AnalyzerCache2,
7
+ analyze2,
8
+ loadMetadata2,
9
+ trackReactiveImports2,
10
+ getAllPropNames2,
11
+ isReactiveValue2,
12
+ containsReactiveValue2,
13
+ collectComponentFunctions2,
14
+ computeLocalScopes2,
15
+ collectComponentCallGraph2,
16
+ buildFullScope2,
17
+ propagateReactiveProps2,
18
+ wrapReactiveExpressions2,
19
+ runAnalyzeCli2
20
+ } from "./chunk-m4rk009c.js";
21
21
 
22
22
  // src/transform.ts
23
23
  import { parse } from "@babel/parser";
@@ -183,7 +183,18 @@ function elementToHtml(node, slots, path, filename, counter) {
183
183
  if (attrName === "children")
184
184
  continue;
185
185
  if (attrName === "ref") {
186
- throw new Error("Cannot hoist element with ref");
186
+ if (attr.value?.type === "JSXExpressionContainer") {
187
+ const expr = attr.value.expression;
188
+ if (expr.type !== "JSXEmptyExpression") {
189
+ slots.push({
190
+ path: [...path],
191
+ type: "ref",
192
+ name: "ref",
193
+ expr
194
+ });
195
+ }
196
+ }
197
+ continue;
187
198
  }
188
199
  if (attr.value?.type === "JSXExpressionContainer") {
189
200
  const expr = attr.value.expression;
@@ -281,8 +292,8 @@ function transformJSX(code, filename, options = {}) {
281
292
  plugins: ["jsx", "typescript"],
282
293
  sourceFilename: filename
283
294
  });
284
- autoWrapComponents(ast, filename);
285
- wrapReactiveExpressions(ast, {
295
+ autoWrapComponents2(ast, filename);
296
+ wrapReactiveExpressions2(ast, {
286
297
  explicitBindings: options.explicitBindings,
287
298
  analyze: options.analyze,
288
299
  analyzeMetadata: options.analyzeMetadata,
@@ -330,11 +341,11 @@ function transformJSX(code, filename, options = {}) {
330
341
  ], t.stringLiteral("sinwan/renderer"));
331
342
  ast.program.body.unshift(bindingImport);
332
343
  }
333
- const result2 = generate(ast, {
344
+ const result = generate(ast, {
334
345
  sourceMaps: true,
335
346
  sourceFileName: filename
336
347
  });
337
- return { code: result2.code, map: result2.map };
348
+ return { code: result.code, map: result.map };
338
349
  }
339
350
  const templateDecls = templates.map(({ id, template }) => t.variableDeclaration("const", [
340
351
  t.variableDeclarator(t.identifier(id), t.objectExpression([
@@ -416,24 +427,24 @@ function collectExportedComponents(code, filename) {
416
427
  return found;
417
428
  }
418
429
  export {
419
- AnalyzerCache,
430
+ AnalyzerCache2 as AnalyzerCache,
420
431
  COMPILER_TEMPLATE_SLOT_PROTOCOL,
421
- analyze,
422
- analyzeModule,
423
- analyzeProject,
424
- autoWrapComponents,
425
- buildFullScope,
426
- collectComponentCallGraph,
427
- collectComponentFunctions,
432
+ analyze2 as analyze,
433
+ analyzeModule2 as analyzeModule,
434
+ analyzeProject2 as analyzeProject,
435
+ autoWrapComponents2 as autoWrapComponents,
436
+ buildFullScope2 as buildFullScope,
437
+ collectComponentCallGraph2 as collectComponentCallGraph,
438
+ collectComponentFunctions2 as collectComponentFunctions,
428
439
  collectExportedComponents,
429
- computeLocalScopes,
430
- containsReactiveValue,
431
- getAllPropNames,
432
- isReactiveValue,
433
- loadMetadata,
434
- propagateReactiveProps,
435
- runAnalyzeCli,
436
- trackReactiveImports,
440
+ computeLocalScopes2 as computeLocalScopes,
441
+ containsReactiveValue2 as containsReactiveValue,
442
+ getAllPropNames2 as getAllPropNames,
443
+ isReactiveValue2 as isReactiveValue,
444
+ loadMetadata2 as loadMetadata,
445
+ propagateReactiveProps2 as propagateReactiveProps,
446
+ runAnalyzeCli2 as runAnalyzeCli,
447
+ trackReactiveImports2 as trackReactiveImports,
437
448
  transformJSX,
438
- wrapReactiveExpressions
449
+ wrapReactiveExpressions2 as wrapReactiveExpressions
439
450
  };
@@ -59,6 +59,7 @@ export interface ReactiveScope {
59
59
  bindings: Map<string, Binding>;
60
60
  }
61
61
  export declare function getAllPropNames(params: t.Node | undefined): Set<string>;
62
+ export declare function resolveLocalObjectLiteral(fn: t.Function, name: string): t.ObjectExpression | null;
62
63
  export declare function isReactiveValue(node: t.Node, scope: ReactiveScope): boolean;
63
64
  export declare function containsReactiveValue(expr: t.Expression, scope: ReactiveScope): boolean;
64
65
  export interface ComponentExpressionInfo {
@@ -66,6 +67,9 @@ export interface ComponentExpressionInfo {
66
67
  componentName: string | null;
67
68
  attributeName: string | null;
68
69
  }
70
+ export declare function getComponentExpressionInfo(exprPath: {
71
+ parentPath?: unknown;
72
+ }): ComponentExpressionInfo;
69
73
  export declare function collectComponentFunctions(ast: t.Node, ccNames: Set<string>): {
70
74
  functions: Set<t.Function>;
71
75
  names: Map<string, t.Function>;
@@ -35,9 +35,9 @@ export interface TransformOptions {
35
35
  * in sync.
36
36
  */
37
37
  export declare const COMPILER_TEMPLATE_SLOT_PROTOCOL: {
38
- readonly slotPrefix: "s";
39
- readonly encodeSlot: (index: number) => string;
40
- readonly decodeSlot: (data: string) => number | null;
38
+ slotPrefix: string;
39
+ encodeSlot(index: number): string;
40
+ decodeSlot(data: string): number | null;
41
41
  };
42
42
  export declare function transformJSX(code: string, filename: string, options?: TransformOptions): {
43
43
  code: string;
package/docs/README.md CHANGED
@@ -1,26 +1,45 @@
1
- # sinwan-compiler
1
+ # Compiler overview
2
2
 
3
- The shared compiler package for SinwanJS. It provides the JSX transform, the project-wide reactive-prop analyzer, and the dev/HMR cache used by both the Bun and Vite plugins.
3
+ `sinwan-compiler` is the shared engine behind the Bun and Vite plugins. It has three jobs: rewrite JSX, decide which props are reactive, and keep that decision cheap in dev.
4
4
 
5
- ## What it does
5
+ If you are building a Sinwan app, start with [Plugins](plugins.md). If you are changing the compiler, start with [Architecture](architecture.md).
6
6
 
7
- - **JSX transform**: Converts reactive JSX expressions into lazy zero-arity functions so the Sinwan runtime can update the DOM efficiently.
8
- - **Cross-file analysis**: Determines which component props are reactive across the whole project, allowing the transform to avoid wrapping static props in effects.
9
- - **Dev cache**: Maintains an incremental, persistent cache for Bun and Vite so dev startup and HMR are fast.
7
+ ## Pipeline
10
8
 
11
- ## Packages
9
+ 1. **Auto-`cc`** — exported functions that look like components (`App`, `Card`, …) are wrapped with `cc(...)` so the rest of the pipeline sees the same components the runtime will.
10
+ 2. **Reactive wrap** — JSX expressions that read signals, stores, `useState`, or `useFetch` become `() => …` (or explicit binding helpers when that mode is on).
11
+ 3. **Template hoist** — static native-element trees become `_$createTemplate(...)` calls with slots for dynamic bits (`child`, `attr`, `event`, `ref`).
12
+ 4. **Analyze (optional)** — a project-wide pass marks which *exported* component props are actually reactive, so static strings are not wrapped.
12
13
 
13
- - `src/transform.ts` — JSX transform and reactive expression wrapping.
14
- - `src/analyze.ts` — `analyze`, `analyzeProject`, `AnalyzerCache`, and import resolution.
15
- - `src/reactive-wrap.ts` — Helpers for detecting reactive sources, collecting component call graphs, and propagating reactivity.
16
- - `src/cli.ts` — `sinwan-compiler analyze` CLI.
17
- - `src/index.ts` — Public package exports.
14
+ ```text
15
+ source.tsx
16
+ │
17
+ ▼
18
+ autoWrapComponents() plain `export function App()` → cc(App)
19
+ │
20
+ ▼
21
+ wrapReactiveExpressions() {count.value} → {() => count.value}
22
+ │
23
+ ▼
24
+ hoist templates <div class="card">…</div> → _$tmpl_N
25
+ │
26
+ ▼
27
+ generated module
28
+ ```
18
29
 
19
- ## Documentation
30
+ ## When analysis runs
20
31
 
21
- - [`analyzer.md`](analyzer.md) — Cross-file reactive-prop analyzer and cache.
22
- - [`transform.md`](transform.md) — JSX transform and wrapping rules.
23
- - [`cli.md`](cli.md) — Command-line usage.
24
- - [`plugins.md`](plugins.md) — Using the compiler with Bun and Vite.
25
- - [`api.md`](api.md) — Public API reference.
26
- - [`architecture.md`](architecture.md) — Internal design and data flow.
32
+ | Mode | Who runs it | What the transform receives |
33
+ | ---- | ----------- | --------------------------- |
34
+ | Dev | Plugin `AnalyzerCache` | In-memory `analyzeMetadata` after each file update |
35
+ | Production | CLI / `analyze()` | JSON file via `analyze: "./.sinwan/reactive-props.json"` |
36
+ | Off | Nobody | Conservative wrapping (exported props treated as reactive) |
37
+
38
+ ## Guides
39
+
40
+ - [Transform](transform.md) — what gets wrapped, auto-`cc`, hoisting, built-in components
41
+ - [Analyzer](analyzer.md) — call graph, spreads, workspaces, cache
42
+ - [CLI](cli.md) — `sinwan analyze`
43
+ - [Plugins](plugins.md) — wiring Bun and Vite
44
+ - [API](api.md) — exported functions and types
45
+ - [Architecture](architecture.md) — data structures and propagation
package/docs/analyzer.md CHANGED
@@ -1,16 +1,18 @@
1
- # Cross-file reactive-prop analyzer
1
+ # Cross-file analyzer
2
2
 
3
- The analyzer determines which props of an exported component are reactive. It runs over the whole project (or a cached subset) and produces a map of `filePath → exportName → Set<propName>`.
3
+ The analyzer answers: **for each exported component, which props actually change?** The transform uses that map so a static `title="Hello"` does not become an effect.
4
+
5
+ Result shape: `filePath → exportName → Set<propName>`.
4
6
 
5
7
  ## How it works
6
8
 
7
- 1. **Parse** every `.tsx/.ts/.jsx/.js` file into a Babel AST.
8
- 2. **Track imports** of reactive sources (`createMutable`, `createStore`, `signal`, `computed`, `useState`) and the component factory `cc`.
9
- 3. **Collect component functions** defined with `cc(...)`.
10
- 4. **Build a call graph** of local and imported JSX calls (`<Child ... />`).
11
- 5. **Propagate reactivity** bottom-up through the call graph using a fixed-point algorithm.
9
+ 1. Scan `.tsx` / `.ts` / `.jsx` / `.js` (or the files you pass in).
10
+ 2. Auto-wrap the same exported components the transform would wrap.
11
+ 3. Track reactive imports and `cc(...)`.
12
+ 4. Build a call graph: local `<Child />` and imported `<Child />`.
13
+ 5. Propagate reactivity until the sets stop growing.
12
14
 
13
- A prop is reactive for a given component if any of its callers passes a value that reads a reactive source.
15
+ A prop is reactive if **any** caller passes a value that reads a reactive source (including through another component).
14
16
 
15
17
  ## Example
16
18
 
@@ -23,10 +25,12 @@ export const Child = cc(({ title }) => <h1>{title}</h1>);
23
25
  import { cc } from "sinwan/component";
24
26
  import { signal } from "sinwan/reactivity";
25
27
  import { Child } from "./Child";
26
- const Parent = cc(() => <Child title={signal.value} />);
27
- ```
28
28
 
29
- The analyzer produces:
29
+ const Parent = cc(() => {
30
+ const title = signal("Hello");
31
+ return <Child title={title.value} />;
32
+ });
33
+ ```
30
34
 
31
35
  ```json
32
36
  {
@@ -34,64 +38,71 @@ The analyzer produces:
34
38
  }
35
39
  ```
36
40
 
37
- ## Workspace packages
41
+ If every caller passed `title="Hello"`, `Child` would be `[]` and the transform would not wrap `title` inside `Child`.
38
42
 
39
- Cross-file analysis works for local monorepo packages. The analyzer discovers workspace packages from a `package.json` `workspaces` field, a `pnpm-workspace.yaml`, or explicit package paths. Only packages that are actually imported by the project are analyzed.
43
+ Default exports are stored under the name `"default"`.
40
44
 
41
- ```ts
42
- analyzeProject({
43
- root: "./apps/web",
44
- // From a workspace file
45
- workspaces: "../../package.json",
46
- // From a pnpm workspace
47
- workspaces: "../../pnpm-workspace.yaml",
48
- // Explicit package paths or globs
49
- workspaces: ["../../packages/sinwan-ui", "../../packages/shared"],
50
- // Or combine both
51
- workspaces: {
52
- file: "../../package.json",
53
- include: ["../../packages/sinwan-ui"],
54
- },
55
- });
56
- ```
45
+ ## Spreads
46
+
47
+ The analyzer is precise when it can see the object:
57
48
 
58
- Package imports are resolved to files inside the declared package, typically under the package `src` directory (or the directory of the `source` field in `package.json`). Subpath imports such as `@sinwan/ui/Button` map to `packages/sinwan-ui/src/Button.tsx`.
49
+ ```tsx
50
+ <Child {...{ title: "Hello", count: s.value }} />
51
+ // only `count` is reactive
59
52
 
53
+ const props = { title: "Hello", count: s.value };
54
+ <Child {...props} />
55
+ // same, if `props` is that object literal in the same function
56
+ ```
60
57
 
58
+ String keys work (`{ "title": s.value }`). If the spread is an unknown value (`{...rest}` from parameters), **every known prop of the callee**, including `children`, is marked reactive.
61
59
 
62
60
  ## Import resolution
63
61
 
64
- The analyzer resolves imports in this order:
62
+ Lookups run in this order:
63
+
64
+ 1. `tsconfig.json` `paths` (when a config is passed or `tsconfig.json` exists next to `root`)
65
+ 2. `bunfig.toml` aliases (`[install] alias = { … }` or `[install.alias]`)
66
+ 3. Workspace packages (see below)
67
+ 4. Relative imports (`./Child`, `./ui` → `ui.tsx` or `ui/index.tsx`)
68
+
69
+ You can replace that with `resolve(source, fromFile)`.
70
+
71
+ `AnalyzerCache` also resolves **in-memory** modules first (HMR can update a file before it hits disk), including `./ui` → `ui/index.tsx` already in the cache.
65
72
 
66
- 1. `tsconfig.json` `paths` (if configured).
67
- 2. `bunfig.toml` aliases (if configured or auto-detected).
68
- 3. Workspace packages (if configured).
69
- 4. Relative imports with the configured extensions.
73
+ ## Workspaces
70
74
 
71
- You can also provide a custom resolver:
75
+ Only packages that the app actually imports are pulled in.
72
76
 
73
77
  ```ts
74
78
  analyzeProject({
75
- root: "/project",
76
- resolve: (source, fromFile) => {
77
- // return absolute path or null
78
- },
79
+ root: "./apps/web",
80
+ workspaces: "../../package.json", // npm/bun `workspaces`
81
+ // or "../../pnpm-workspace.yaml"
82
+ // or ["../../packages/ui"]
83
+ // or { file: "../../package.json", include: ["../../packages/ui"] }
79
84
  });
80
85
  ```
81
86
 
82
- ## Spread props
87
+ `@scope/ui/Button` maps to `packages/ui/src/Button.tsx` (or the directory of `package.json` `"source"`).
88
+
89
+ ## Cache (`AnalyzerCache`)
90
+
91
+ Dev plugins call `update(file, code)` on each transform. The cache:
92
+
93
+ - Re-analyzes that file
94
+ - Walks importers **and** imported modules
95
+ - Writes `cachePath` when you set one
96
+
97
+ `remove(file)` is for deletes / HMR unlinks. It drops the module and **recomputes both**:
83
98
 
84
- Spread props are handled precisely when possible:
99
+ - files that imported it (parents)
100
+ - files it imported (children — their props may become static again)
85
101
 
86
- - `<Child {...{ title: "Hello", count: signal.value }} />` is treated as two named props; only `count` becomes reactive.
87
- - `<Child {...props} />` is also resolved precisely if `props` is a local variable initialized with an object literal in the same function:
88
- ```tsx
89
- const props = { title: "Hello", count: signal.value };
90
- return <Child {...props} />;
91
- ```
92
- - Spreads of function parameters or other unknown runtime objects fall back to the conservative rule: all known props of the callee are marked reactive.
102
+ If `cachePath` exists, the constructor restores it. Function identities are start/end offsets. If a file changed so those spans no longer match, that module is skipped (no throw); the next `update` rebuilds it.
93
103
 
94
- ## Limitations
104
+ ## Conservative rules
95
105
 
96
- - Unknown runtime objects (`{...props}` where `props` is not a local object literal) are conservatively treated as reactive.
97
- - Named slot props and `children` are regular props; they are reactive only when the passed value is reactive.
106
+ - Unknown spreads are conservative (all callee props).
107
+ - `children` is a normal prop.
108
+ - Without metadata, the transform treats **exported** component props as reactive so a parent in another file cannot silently go stale.
package/docs/api.md CHANGED
@@ -1,25 +1,41 @@
1
1
  # API reference
2
2
 
3
- ## `transformJSX(code, filename, options?)`
3
+ Import from `sinwan-compiler`. Types below match the package exports.
4
4
 
5
- Transforms a single JSX/TSX file.
5
+ ## Transform
6
+
7
+ ### `transformJSX(code, filename, options?)`
8
+
9
+ Rewrite one module. Returns `{ code: string; map?: unknown }`.
6
10
 
7
11
  ```ts
8
12
  import { transformJSX } from "sinwan-compiler";
9
13
 
10
- const result = transformJSX(code, filename, {
14
+ const { code, map } = transformJSX(source, "src/App.tsx", {
11
15
  hoist: true,
16
+ dev: false,
12
17
  explicitBindings: false,
13
18
  analyze: "./.sinwan/reactive-props.json",
14
- analyzeMetadata: new Map(),
19
+ analyzeMetadata: cache.reactiveProps,
20
+ resolveImport: (source, fromFile) => /* absolute path or null */,
15
21
  });
16
22
  ```
17
23
 
18
- Returns `{ code: string; map?: any }`.
24
+ See [Transform](transform.md) for wrapping rules and options.
25
+
26
+ ### `COMPILER_TEMPLATE_SLOT_PROTOCOL`
27
+
28
+ `{ slotPrefix, encodeSlot(index), decodeSlot(data) }`. Markers look like `s:0`. Keep this aligned with the Sinwan runtime protocol.
29
+
30
+ ### `autoWrapComponents(ast, filename?)`
19
31
 
20
- ## `analyze(options)`
32
+ Mutates a Babel AST: wrap exported component-like functions with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
21
33
 
22
- Production entry point. Analyzes the project and writes metadata to disk.
34
+ ## Analysis
35
+
36
+ ### `analyze(options)`
37
+
38
+ Scan a project and write JSON to `outFile`.
23
39
 
24
40
  ```ts
25
41
  import { analyze } from "sinwan-compiler";
@@ -27,58 +43,93 @@ import { analyze } from "sinwan-compiler";
27
43
  analyze({
28
44
  root: "./src",
29
45
  outFile: "./.sinwan/reactive-props.json",
46
+ extensions: [".tsx", ".ts", ".jsx", ".js"], // default
30
47
  tsConfigPath: "./tsconfig.json",
31
48
  bunfigPath: "./bunfig.toml",
32
49
  workspaces: "../../package.json",
50
+ resolve: (source, fromFile) => null,
33
51
  });
34
52
  ```
35
53
 
36
- ## `analyzeProject(options)`
54
+ Missing `tsconfig.json` / `bunfig.toml` next to `root` are skipped unless you pass the paths explicitly.
55
+
56
+ ### `analyzeProject(options)`
37
57
 
38
- Programmatic analyzer. Returns the full project analysis without writing to disk.
58
+ Same scan, no disk write. Returns `{ modules, reactiveProps }`.
39
59
 
40
60
  ```ts
41
61
  import { analyzeProject } from "sinwan-compiler";
42
62
 
43
63
  const project = analyzeProject({
44
64
  root: "./src",
45
- files: { "/project/A.tsx": "..." },
65
+ files: { "/tmp/A.tsx": "export const A = () => <div/>" }, // optional in-memory map
46
66
  tsConfigPath: "./tsconfig.json",
67
+ workspaces: { file: "../../package.json", include: ["../../packages/ui"] },
47
68
  });
48
69
 
49
- project.reactiveProps.get("/project/A.tsx")?.get("Component");
70
+ project.reactiveProps.get("/tmp/A.tsx")?.get("A"); // Set<string> | undefined
50
71
  ```
51
72
 
52
- ## `AnalyzerCache`
73
+ ### `analyzeModule(code, filePath)`
74
+
75
+ Analyze one file. Used by the cache and tests.
76
+
77
+ ### `loadMetadata(filePath)`
53
78
 
54
- Incremental analyzer for dev/HMR.
79
+ Read analyzer JSON into `Map<file, Map<exportName, Set<prop>>>`. Missing file → empty map.
80
+
81
+ ### `loadWorkspacePackages(workspaces, root)`
82
+
83
+ Resolve workspace directories from a file, a list of globs/paths, or `{ file, include }`.
84
+
85
+ ### `AnalyzerCache`
86
+
87
+ Incremental graph for plugins.
55
88
 
56
89
  ```ts
57
90
  import { AnalyzerCache } from "sinwan-compiler";
58
91
 
59
92
  const cache = new AnalyzerCache({
60
- root: "./src",
93
+ root: process.cwd(),
61
94
  tsConfigPath: "./tsconfig.json",
62
95
  bunfigPath: "./bunfig.toml",
63
96
  workspaces: "../../package.json",
64
97
  cachePath: "./.sinwan/cache.json",
98
+ resolve: (source, fromFile) => null, // optional
65
99
  });
66
100
 
67
- cache.update(filePath, newCode);
68
- const metadata = cache.reactiveProps;
101
+ cache.update(absPath, source);
102
+ cache.remove(absPath); // also recomputes modules this file imported
103
+ cache.save(); // no-op without cachePath
104
+
105
+ cache.reactiveProps; // Map<file, Map<export, Set<prop>>>
106
+ cache.modules;
69
107
  ```
70
108
 
71
- ### Methods
109
+ `restore` runs in the constructor when `cachePath` exists. Stale function spans skip that module instead of throwing.
110
+
111
+ ## CLI helpers
112
+
113
+ ### `runAnalyzeCli(args?)`
114
+
115
+ Parse `analyze [root] [outFile] [--tsconfig] [--bunfig] [--workspaces]` and call `analyze()`. Defaults to `process.argv.slice(2)`. Unknown commands write to stderr and `process.exit(1)`.
116
+
117
+ ## Other exports
72
118
 
73
- - `update(filePath, code)` — re-analyze a file and recompute affected files.
74
- - `remove(filePath)` — remove a file and recompute affected files.
75
- - `save()` — persist the cache to `cachePath`.
76
- - `restore()` — load the cache from `cachePath` (called automatically if `cachePath` exists).
119
+ | Name | Role |
120
+ | ---- | ---- |
121
+ | `collectExportedComponents(code)` | File-local exported component names (no `cc` required) |
122
+ | `wrapReactiveExpressions(ast, options?)` | Reactive wrap without hoisting |
123
+ | `trackReactiveImports(ast)` | Import-name sets for signals, `cc`, `useFetch`, … |
124
+ | `collectComponentFunctions(ast, ccNames)` | `cc(...)` functions, names, and exports |
125
+ | `computeLocalScopes(ast, names)` | Per-function reactive bindings |
126
+ | `collectComponentCallGraph(ast, names)` | Local JSX call sites |
127
+ | `propagateReactiveProps(scopes, graph, fns)` | Fixed-point prop sets |
128
+ | `buildFullScope(scopes, fn, reactiveProps)` | Local bindings plus reactive props |
129
+ | `containsReactiveValue(expr, scope)` | Whether an expression reads a reactive value |
130
+ | `isReactiveValue(node, scope)` | Single-node check |
131
+ | `getAllPropNames(params)` | Prop names from a component parameter (`*` for a bare identifier) |
77
132
 
78
133
  ## Types
79
134
 
80
- - `CallSite` — local component call site.
81
- - `ImportedCallSite` — cross-file component call site.
82
- - `ModuleAnalysis` — analysis of a single module.
83
- - `ProjectAnalysis` — analysis of the whole project.
84
- - `WorkspacesConfig` — workspace file path, explicit package paths/globs, or both. Used to enable cross-package analysis in monorepos.
135
+ `AnalyzeOptions`, `WorkspacesConfig`, `ModuleAnalysis`, `ProjectAnalysis`, `ImportInfo`, `ImportedCallSite`, `CallSite`, `ReactiveScope`, `Binding`, `ImportNames`, `ComponentExpressionInfo`, `ExportedComponent`, `TransformOptions`.