sinwan-compiler 0.4.0 → 1.1.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.
@@ -8,7 +8,7 @@
8
8
  * Nested patterns are left as snapshots. JSX spreads of the props (or rest)
9
9
  * identifier use `getSpreadProps` so native attributes keep getter functions.
10
10
  */
11
- import * as t from "@babel/types";
11
+ import * as t from "./ast/types";
12
12
  /**
13
13
  * Rewrite flat `cc` destructure and raw JSX spreads. Returns whether any
14
14
  * helper imports are required.
@@ -6,7 +6,7 @@
6
6
  * when the value changes.
7
7
  *
8
8
  * Supported reactive sources:
9
- * - `createMutable` / `createStore` from `sinwan/store`
9
+ * - `createMutable` / `createStore` from `sinway`
10
10
  * - `signal` / `computed` from `sinwan/reactivity`
11
11
  * - `useState` from `sinwan/react`
12
12
  * - `.value` reads of signals returned from other modules (`useTheme`,
@@ -20,7 +20,7 @@
20
20
  * <p>{count.value}</p> → <p>{() => count.value}</p>
21
21
  * <p>{getCount()}</p> → <p>{() => getCount()}</p>
22
22
  */
23
- import * as t from "@babel/types";
23
+ import * as t from "./ast/types";
24
24
  export interface ImportNames {
25
25
  createMutable: Set<string>;
26
26
  createStore: Set<string>;
@@ -5,6 +5,7 @@
5
5
  * Static JSX elements are extracted to module-level template strings
6
6
  * and replaced with optimized template creation calls.
7
7
  */
8
+ import { t } from "./ast";
8
9
  export interface TransformOptions {
9
10
  derivedLocals?: boolean;
10
11
  /** Enable template hoisting (default: true) */
@@ -40,6 +41,7 @@ export declare const COMPILER_TEMPLATE_SLOT_PROTOCOL: {
40
41
  encodeSlot(index: number): string;
41
42
  decodeSlot(data: string): number | null;
42
43
  };
44
+ export declare function jsxAttrName(attr: t.Node): string | undefined;
43
45
  export declare function transformJSX(code: string, filename: string, options?: TransformOptions): {
44
46
  code: string;
45
47
  map?: any;
package/docs/api.md CHANGED
@@ -29,11 +29,11 @@ See [Transform](transform.md) for wrapping rules and options.
29
29
 
30
30
  ### `autoWrapComponents(ast, filename?)`
31
31
 
32
- Mutates a Babel AST: wrap exported component-like functions with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
32
+ Mutates an ESTree AST (oxc-parser): wrap exported component-like functions (including `function X` / `const X` exported through `export { X }`) with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
33
33
 
34
34
  ### `rewriteLiveCcDestructure(ast)`
35
35
 
36
- Mutates a Babel AST: rewrite flat `cc(({ value }) => …)` params to a `props` identifier and `{...props}` spreads to `getSpreadProps(props)`. Nested destructure is left as a snapshot. `transformJSX` already runs this before reactive wrap.
36
+ Mutates an ESTree AST (oxc-parser): rewrite flat `cc(({ value }) => …)` params to a `props` identifier and `{...props}` spreads to `getSpreadProps(props)`. Nested destructure is left as a snapshot. `transformJSX` already runs this before reactive wrap.
37
37
 
38
38
  ## Analysis
39
39
 
@@ -6,6 +6,7 @@ This page is for people changing the compiler. App authors can stay on [Transfor
6
6
 
7
7
  ```
8
8
  src/
9
+ ast/ oxc parse, Path/Scope, esrap print
9
10
  index.ts Public exports
10
11
  auto-cc.ts Exported function → cc(...)
11
12
  live-cc-destructure.ts Flat cc(({ value }) => …) → props.value
@@ -16,6 +17,21 @@ src/
16
17
  exports.ts collectExportedComponents
17
18
  ```
18
19
 
20
+ ## AST pipeline
21
+
22
+ `transformJSX` and analyze parse with **oxc-parser** (`parseSync`, ESTree / TS-ESTree). The compiler parses as TSX so JSX and TypeScript are accepted in `.js`, `.jsx`, `.ts`, and `.tsx`, matching the previous Babel plugin set. Comments are copied onto `leadingComments` / `trailingComments`. A JSX `{/* comment */}` stays on its empty expression. A comment that sits inside a function or object after the last child stays on that container. Rewrite passes walk a Babel-shaped **Path/Scope** facade built on oxc `visitorKeys` (parent pointers, `getBinding`, `replaceWith`). Print is **esrap** with `esrap/languages/tsx` and double quotes.
23
+
24
+ Babel is not used. oxc-parser JS/JSX matches Acorn ESTree; TS/TSX matches TS-ESTree. Passes must treat:
25
+
26
+ - `Literal` (not `StringLiteral` / `NumericLiteral`)
27
+ - `Property` (not `ObjectProperty`)
28
+ - `ChainExpression` around optional calls/members
29
+ - `ParenthesizedExpression` (`preserveParens` defaults true) — unwrap in reactive walks, do not require Babel’s implicit paren stripping
30
+
31
+ Print is esrap, not oxc-codegen or MagicString, so mutations stay on the tree. Acorn / acorn-walk cannot own this pipeline: Acorn does not parse TSX, and acorn-walk has no bindings or JSX/TS visitor keys. Bun.Transpiler and SWC are out of scope (no AST mutation API / not the primary engine).
32
+
33
+ `derivedLocals` uses the Path/Scope facade; a Rust rewrite of passes is not required while those tests pass.
34
+
19
35
  ## Data flow
20
36
 
21
37
  ```text
package/docs/transform.md CHANGED
@@ -32,7 +32,7 @@ Exported functions that look like components are wrapped for you. You can write
32
32
 
33
33
  A candidate must be:
34
34
 
35
- - **Exported** (named or default)
35
+ - **Exported** (named or default): `export function`, `export const`, `export default`, or a top-level `function` / `const` exported through `export { X }` or `export { X as Name }` (the exported name must be uppercase)
36
36
  - **Uppercase** (or an anonymous `export default function ()`)
37
37
  - **0 or 1 parameter**
38
38
  - **Returning JSX** in that function (not only in a nested helper)
@@ -50,7 +50,9 @@ export const App = cc(function App() {
50
50
  });
51
51
  ```
52
52
 
53
- Not wrapped: lowercase helpers, generators, multi-argument functions, or functions that never return JSX. If `cc` is not imported yet, the compiler adds `import { cc } from "sinwan/component"`.
53
+ Not wrapped: lowercase helpers, generators, multi-argument functions, functions that never return JSX, type-only exports, re-exports (`export { X } from "./x"`), components that are not exported, and a `function X` exported through `export { X }` that module code reads before its declaration (for example `const Group = Object.assign(X, { Item })` above it), because turning it into `const X = cc(...)` would make that read throw. Reads inside functions, export specifiers and types do not count. A `cc` parent passes reactive props to child components as getters, so a non-exported component that receives props from one must be wrapped with `cc(...)` explicitly. If `cc` is not imported yet, the compiler adds `import { cc } from "sinwan/component"`.
54
+
55
+ Type parameters and return types are kept on the wrapped function. A circular import that reads `X` at module load before this file finishes evaluating is not detected; `export function X` has the same limitation.
54
56
 
55
57
  `cc` may be imported from `sinwan` or `sinwan/component`.
56
58
 
@@ -78,7 +80,7 @@ Tracked imports:
78
80
 
79
81
  | Module | Names |
80
82
  | ------------------- | ------------------------------ |
81
- | `sinwan/store` | `createMutable`, `createStore` |
83
+ | `sinway` | `createMutable`, `createStore` |
82
84
  | `sinwan/reactivity` | `signal`, `computed` |
83
85
  | `sinwan/react` | `useState` |
84
86
  | `sinwan/hook` | `useFetch` |
@@ -138,7 +140,10 @@ return (
138
140
 
139
141
  - Only referenced, identifier-named `const` declarations directly in a `cc` setup body (including auto-`cc` components) are candidates. Dependencies are processed in declaration order, including multiple declarators in one statement.
140
142
  - Pure read expressions are supported: member/index/optional access, arithmetic, comparisons, boolean/conditional expressions, template strings, object/array literals, and TypeScript expression annotations. Direct imported `useState` getter calls and unshadowed single-argument `Boolean`, `Number`, and `String` coercions are also supported. Property reads and coercions must be free of user-defined side effects.
141
- - Calls to arbitrary helpers, hooks, factories or methods, assignments, updates, allocations, destructuring, `let`/`var`, mutable captures, and forward/TDZ references remain eager — **unless** the callee is an imported function annotated with `/** @sinwan-pure */` (see [`@sinwan-pure` JSDoc contract](#sinwan-pure-jsdoc-contract) below). Explicit getter and `computed` initializers are unchanged. Module, event-handler, render-callback, loop and nested-block declarations are not promoted.
143
+ - Calls to arbitrary helpers, hooks, factories or methods, assignments, updates, allocations, destructuring, `let`/`var`, mutable captures, and forward/TDZ references remain eager — **unless** the callee is a function annotated with `/** @sinwan-pure */` (imported, or declared in the same file), a local zero-arity arrow getter whose expression body is itself safe (`const yearNow = () => …`, then `props.fromYear ?? yearNow() - 10`), or a built-in read on a known array or `Date` (below). See [`@sinwan-pure` JSDoc contract](#sinwan-pure-jsdoc-contract) below. A getter with a block body, side effects (`() => count++`) or a call back into itself stays eager. Explicit getter and `computed` initializers are unchanged.
144
+ - Built-in reads: `Array.from(...)`, non-mutating array reads (`map`, `filter`, `flatMap`, `flat`, `slice`, `concat`, `join`, `at`, `find`, `findIndex`, `some`, `every`, `reduce`, `includes`, `indexOf`, `entries`, `keys`, `values`) and `Date` getters (`getFullYear`, `getMonth`, `getDate`, `getDay`, `getTime`, `getHours`, `getMinutes`, `getSeconds`, `getMilliseconds`, `valueOf`) promote only on a receiver the compiler knows is an array or `Date`: an array literal, `Array.from(...)`, an array read that returns a new array, a `const` annotated `T[]` / `Array<T>` / `ReadonlyArray<T>` / `Date` or initialized from one of these, or a call to a same-file function with that return type annotation. A callback must be inline (its body is inspected) or a `@sinwan-pure` function; `items.map(track)` with an unannotated `track` stays eager. Reads on other values (`props.items.map(...)`, `props.query.filter(...)`) stay eager because the method may not be the built-in one; annotate a `const` (`const items: Item[] = props.items`) to opt in.
145
+ - A pure method read on a promoted local such as `monthList.map(...)` or `today.getFullYear()` is rewritten to `monthList().map(...)` / `today().getFullYear()`. Mutating reads such as `list.push` and `date.setFullYear` stay snapshots. A promoted local that holds a function is still called: `const close = props.onClose; close()` becomes `close()()`. Module, event-handler, render-callback, loop and nested-block declarations are not promoted.
146
+ - A reactive value passed to a parameter typed `LiveRead<T>` (`T | (() => T)`) is wrapped as `() => value`. An argument that is already a function is left alone. `createLiveState(..., props.selected)` therefore re-reads `props.selected` on each `state.value` access.
142
147
  - Bare signal/store containers still pass through unchanged. Bare component prop-bag aliases retain their ordinary JavaScript value semantics. References are resolved by lexical binding, so shadowed locals and object keys are not rewritten.
143
148
  - Every ordinary value use is called, including chained initializers, closures, event bodies, shorthand object values and setup `if` tests. A boolean does **not** become an accidentally truthy function. Setup control flow still runs once; use `Show`/`Switch` for reactive branches.
144
149
  - Variables used as JSX tag names, in type queries, or as mutation/method-call receivers are left eager. Components containing direct `eval` are excluded.
@@ -170,12 +175,12 @@ export function cn(...inputs: ClassValue[]): string {
170
175
  }
171
176
  ```
172
177
 
173
- Both `export function` and `export const` declarations are recognized. The annotation must be a block comment (`/** */`) immediately before the `export` keyword.
178
+ Both `export function` and `export const` declarations are recognized. The annotation must be a block comment (`/** */`) immediately before the `export` keyword. A non-exported function in the same file is also recognized when the comment sits directly above `function` or `const`.
174
179
 
175
180
  **What the compiler guarantees:**
176
181
 
177
182
  - When `derivedLocals: true` is enabled, calls to `@sinwan-pure`-annotated imported functions are treated as safe expressions. The containing `const` initializer is promoted to a zero-argument getter, and references inside the function arguments are rewritten (e.g. `seamInactive` → `seamInactive()`).
178
- - The compiler resolves the import path, reads the source file, parses it, and checks for `/** @sinwan-pure */` on exported declarations. Results are cached per resolved file path, so each file is read at most once per compilation process.
183
+ - The compiler resolves the import path, reads the source file, parses it, and checks for `/** @sinwan-pure */` on exported declarations. Results are cached per resolved file path and reused while that file, and any file it re-exports from, keeps its modification time, so a dev server picks up a new or removed annotation without a restart. The same applies to the `LiveRead<T>` parameter scan.
179
184
  - All import types are scanned: relative imports (`./`, `../`), bare specifiers (`clsx`, `sinwan/reactivity`), scoped packages (`@scope/utils`), and subpath imports (`@scope/utils/merge`). The compiler walks up the directory tree to find `node_modules/<pkg>`, reads `package.json`, and resolves the entry point via `exports`, `module`, `main`, `source`, or `index` files.
180
185
  - If the `resolveImport` callback is provided by the plugin, it is used for path resolution (handles tsconfig paths, bunfig aliases, workspace packages, and Bun/Vite module resolution). Otherwise, the compiler falls back to direct filesystem resolution with common extensions (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts`) and `index` files.
181
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinwan-compiler",
3
- "version": "0.4.0",
3
+ "version": "1.1.0",
4
4
  "description": "Shared compiler core for Sinwan plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,16 +37,12 @@
37
37
  "prepublishOnly": "bun run clean && bun run typecheck && bun run test && bun run build"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/babel__generator": "^7.27.0",
41
- "@types/babel__traverse": "^7.28.0",
40
+ "@oxc-project/types": "^0.150.0",
42
41
  "@types/bun": "latest"
43
42
  },
44
43
  "dependencies": {
45
- "@babel/core": "^7.29.7",
46
- "@babel/generator": "^7.29.8",
47
- "@babel/parser": "^7.29.8",
48
- "@babel/traverse": "^7.29.8",
49
- "@babel/types": "^7.29.8"
44
+ "esrap": "^2.3.7",
45
+ "oxc-parser": "^0.150.0"
50
46
  },
51
47
  "peerDependencies": {
52
48
  "typescript": ">=5.0.0 <8.0.0"