macroforge 0.1.0 → 0.1.1
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/README.md +371 -0
- package/package.json +10 -10
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# macroforge
|
|
2
|
+
|
|
3
|
+
TypeScript macro expansion engine powered by Rust and SWC.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`macroforge` is a native Node.js module that enables compile-time code generation for TypeScript through a Rust-like derive macro system. It parses TypeScript using SWC, expands macros written in Rust, and outputs transformed TypeScript code with full source mapping support.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install macroforge
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package includes pre-built binaries for:
|
|
16
|
+
- macOS (x64, arm64)
|
|
17
|
+
- Linux (x64, arm64)
|
|
18
|
+
- Windows (x64, arm64)
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Using Built-in Macros
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Derive, Debug, Clone, Eq } from "macroforge";
|
|
26
|
+
|
|
27
|
+
/** @derive(Debug, Clone, Eq) */
|
|
28
|
+
class User {
|
|
29
|
+
name: string;
|
|
30
|
+
age: number;
|
|
31
|
+
|
|
32
|
+
constructor(name: string, age: number) {
|
|
33
|
+
this.name = name;
|
|
34
|
+
this.age = age;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// After macro expansion, the class gains:
|
|
39
|
+
// - toString(): string (from Debug)
|
|
40
|
+
// - clone(): User (from Clone)
|
|
41
|
+
// - equals(other: User): boolean (from Eq)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Programmatic API
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { expandSync, NativePlugin } from "macroforge";
|
|
48
|
+
|
|
49
|
+
// One-shot expansion
|
|
50
|
+
const result = expandSync(sourceCode, "file.ts", {
|
|
51
|
+
keepDecorators: false
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(result.code); // Transformed TypeScript
|
|
55
|
+
console.log(result.diagnostics); // Any warnings/errors
|
|
56
|
+
console.log(result.metadata); // Macro IR metadata (JSON)
|
|
57
|
+
|
|
58
|
+
// Cached expansion (for language servers)
|
|
59
|
+
const plugin = new NativePlugin();
|
|
60
|
+
const cached = plugin.processFile("file.ts", sourceCode, {
|
|
61
|
+
version: "1.0.0" // Cache key
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
### Core Functions
|
|
68
|
+
|
|
69
|
+
#### `expandSync(code, filepath, options?)`
|
|
70
|
+
|
|
71
|
+
Expands macros in TypeScript code synchronously.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
function expandSync(
|
|
75
|
+
code: string,
|
|
76
|
+
filepath: string,
|
|
77
|
+
options?: ExpandOptions
|
|
78
|
+
): ExpandResult;
|
|
79
|
+
|
|
80
|
+
interface ExpandOptions {
|
|
81
|
+
keepDecorators?: boolean; // Keep @derive decorators in output (default: false)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ExpandResult {
|
|
85
|
+
code: string; // Transformed TypeScript
|
|
86
|
+
types?: string; // Generated .d.ts content
|
|
87
|
+
metadata?: string; // Macro IR as JSON
|
|
88
|
+
diagnostics: MacroDiagnostic[]; // Warnings and errors
|
|
89
|
+
sourceMapping?: SourceMappingResult; // Position mapping data
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `transformSync(code, filepath)`
|
|
94
|
+
|
|
95
|
+
Lower-level transform that returns additional metadata.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
function transformSync(code: string, filepath: string): TransformResult;
|
|
99
|
+
|
|
100
|
+
interface TransformResult {
|
|
101
|
+
code: string;
|
|
102
|
+
map?: string; // Source map (not yet implemented)
|
|
103
|
+
types?: string; // Generated declarations
|
|
104
|
+
metadata?: string;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### `checkSyntax(code, filepath)`
|
|
109
|
+
|
|
110
|
+
Validates TypeScript syntax without macro expansion.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
function checkSyntax(code: string, filepath: string): SyntaxCheckResult;
|
|
114
|
+
|
|
115
|
+
interface SyntaxCheckResult {
|
|
116
|
+
ok: boolean;
|
|
117
|
+
error?: string;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### `parseImportSources(code, filepath)`
|
|
122
|
+
|
|
123
|
+
Extracts import information from TypeScript code.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
function parseImportSources(code: string, filepath: string): ImportSourceResult[];
|
|
127
|
+
|
|
128
|
+
interface ImportSourceResult {
|
|
129
|
+
local: string; // Local identifier name
|
|
130
|
+
module: string; // Module specifier
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Classes
|
|
135
|
+
|
|
136
|
+
#### `NativePlugin`
|
|
137
|
+
|
|
138
|
+
Stateful plugin with caching for language server integration.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
class NativePlugin {
|
|
142
|
+
constructor();
|
|
143
|
+
|
|
144
|
+
// Process file with version-based caching
|
|
145
|
+
processFile(
|
|
146
|
+
filepath: string,
|
|
147
|
+
code: string,
|
|
148
|
+
options?: ProcessFileOptions
|
|
149
|
+
): ExpandResult;
|
|
150
|
+
|
|
151
|
+
// Get position mapper for a cached file
|
|
152
|
+
getMapper(filepath: string): NativeMapper | null;
|
|
153
|
+
|
|
154
|
+
// Map diagnostics from expanded to original positions
|
|
155
|
+
mapDiagnostics(filepath: string, diags: JsDiagnostic[]): JsDiagnostic[];
|
|
156
|
+
|
|
157
|
+
// Logging utilities
|
|
158
|
+
log(message: string): void;
|
|
159
|
+
setLogFile(path: string): void;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `NativeMapper` / `PositionMapper`
|
|
164
|
+
|
|
165
|
+
Maps positions between original and expanded code.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
class NativeMapper {
|
|
169
|
+
constructor(mapping: SourceMappingResult);
|
|
170
|
+
|
|
171
|
+
isEmpty(): boolean;
|
|
172
|
+
originalToExpanded(pos: number): number;
|
|
173
|
+
expandedToOriginal(pos: number): number | null;
|
|
174
|
+
generatedBy(pos: number): string | null;
|
|
175
|
+
mapSpanToOriginal(start: number, length: number): SpanResult | null;
|
|
176
|
+
mapSpanToExpanded(start: number, length: number): SpanResult;
|
|
177
|
+
isInGenerated(pos: number): boolean;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Built-in Decorators
|
|
182
|
+
|
|
183
|
+
| Decorator | Description |
|
|
184
|
+
|-----------|-------------|
|
|
185
|
+
| `@Derive(...features)` | Class decorator that triggers macro expansion |
|
|
186
|
+
| `@Debug` | Generates `toString(): string` method |
|
|
187
|
+
| `@Clone` | Generates `clone(): T` method |
|
|
188
|
+
| `@Eq` | Generates `equals(other: T): boolean` method |
|
|
189
|
+
|
|
190
|
+
## Writing Custom Macros
|
|
191
|
+
|
|
192
|
+
Custom macros are written in Rust and compiled to native Node.js addons. See the [playground/macro](../../playground/macro) directory for examples.
|
|
193
|
+
|
|
194
|
+
### Minimal Macro Crate
|
|
195
|
+
|
|
196
|
+
**Cargo.toml:**
|
|
197
|
+
```toml
|
|
198
|
+
[package]
|
|
199
|
+
name = "my-macros"
|
|
200
|
+
version = "0.1.0"
|
|
201
|
+
edition = "2024"
|
|
202
|
+
|
|
203
|
+
[lib]
|
|
204
|
+
crate-type = ["cdylib"]
|
|
205
|
+
|
|
206
|
+
[dependencies]
|
|
207
|
+
macroforge_ts = "0.1.0"
|
|
208
|
+
napi = { version = "3.5.2", features = ["napi8", "compat-mode"] }
|
|
209
|
+
napi-derive = "3.3.3"
|
|
210
|
+
|
|
211
|
+
[build-dependencies]
|
|
212
|
+
napi-build = "2.3.1"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**src/lib.rs:**
|
|
216
|
+
```rust
|
|
217
|
+
use macroforge_ts::ts_macro_derive::ts_macro_derive;
|
|
218
|
+
use macroforge_ts::ts_quote::body;
|
|
219
|
+
use macroforge_ts::ts_syn::{
|
|
220
|
+
Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
#[ts_macro_derive(
|
|
224
|
+
JSON,
|
|
225
|
+
description = "Generates toJSON() returning a plain object"
|
|
226
|
+
)]
|
|
227
|
+
pub fn derive_json(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
228
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
229
|
+
|
|
230
|
+
match &input.data {
|
|
231
|
+
Data::Class(class) => {
|
|
232
|
+
Ok(body! {
|
|
233
|
+
toJSON(): Record<string, unknown> {
|
|
234
|
+
return {
|
|
235
|
+
{#for field in class.field_names()}
|
|
236
|
+
@{field}: this.@{field},
|
|
237
|
+
{/for}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
_ => Err(MacroforgeError::new(
|
|
243
|
+
input.decorator_span(),
|
|
244
|
+
"@derive(JSON) only works on classes",
|
|
245
|
+
)),
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Template Syntax
|
|
251
|
+
|
|
252
|
+
The `ts_template!` and `body!` macros support:
|
|
253
|
+
|
|
254
|
+
| Syntax | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `@{expr}` | Interpolate Rust expression as identifier/code |
|
|
257
|
+
| `{#for x in iter}...{/for}` | Loop over iterables |
|
|
258
|
+
| `{%let name = expr}` | Local variable binding |
|
|
259
|
+
| `{#if cond}...{/if}` | Conditional blocks |
|
|
260
|
+
|
|
261
|
+
### Re-exported Crates
|
|
262
|
+
|
|
263
|
+
`macroforge_ts` re-exports everything needed for macro development:
|
|
264
|
+
|
|
265
|
+
```rust
|
|
266
|
+
// All available via macroforge_ts::*
|
|
267
|
+
pub extern crate ts_syn; // AST types, parsing
|
|
268
|
+
pub extern crate ts_quote; // Code generation templates
|
|
269
|
+
pub extern crate ts_macro_derive; // #[ts_macro_derive] attribute
|
|
270
|
+
pub extern crate inventory; // Macro registration
|
|
271
|
+
pub extern crate serde_json; // Serialization
|
|
272
|
+
pub extern crate napi; // Node.js bindings
|
|
273
|
+
pub extern crate napi_derive; // NAPI proc-macros
|
|
274
|
+
|
|
275
|
+
// SWC modules
|
|
276
|
+
pub use ts_syn::swc_core;
|
|
277
|
+
pub use ts_syn::swc_common;
|
|
278
|
+
pub use ts_syn::swc_ecma_ast;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Integration
|
|
282
|
+
|
|
283
|
+
### Vite Plugin
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// vite.config.ts
|
|
287
|
+
import macroforge from "@macroforge/vite-plugin";
|
|
288
|
+
|
|
289
|
+
export default defineConfig({
|
|
290
|
+
plugins: [
|
|
291
|
+
macroforge({
|
|
292
|
+
typesOutputDir: ".macroforge/types",
|
|
293
|
+
metadataOutputDir: ".macroforge/meta",
|
|
294
|
+
generateTypes: true,
|
|
295
|
+
emitMetadata: true,
|
|
296
|
+
}),
|
|
297
|
+
],
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### TypeScript Plugin
|
|
302
|
+
|
|
303
|
+
Add to `tsconfig.json` for IDE support:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"compilerOptions": {
|
|
308
|
+
"plugins": [
|
|
309
|
+
{
|
|
310
|
+
"name": "@macroforge/typescript-plugin"
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Debug API
|
|
318
|
+
|
|
319
|
+
For debugging macro registration:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import {
|
|
323
|
+
__macroforgeGetManifest,
|
|
324
|
+
__macroforgeGetMacroNames,
|
|
325
|
+
__macroforgeIsMacroPackage,
|
|
326
|
+
__macroforgeDebugDescriptors,
|
|
327
|
+
__macroforgeDebugLookup,
|
|
328
|
+
} from "macroforge";
|
|
329
|
+
|
|
330
|
+
// Get all registered macros
|
|
331
|
+
const manifest = __macroforgeGetManifest();
|
|
332
|
+
console.log(manifest.macros);
|
|
333
|
+
|
|
334
|
+
// Check if current package exports macros
|
|
335
|
+
console.log(__macroforgeIsMacroPackage());
|
|
336
|
+
|
|
337
|
+
// List macro names
|
|
338
|
+
console.log(__macroforgeGetMacroNames());
|
|
339
|
+
|
|
340
|
+
// Debug lookup
|
|
341
|
+
console.log(__macroforgeDebugLookup("@my/macros", "JSON"));
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Architecture
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
┌─────────────────────────────────────────────────────────┐
|
|
348
|
+
│ Node.js / Vite │
|
|
349
|
+
├─────────────────────────────────────────────────────────┤
|
|
350
|
+
│ NAPI-RS Bindings │
|
|
351
|
+
├─────────────────────────────────────────────────────────┤
|
|
352
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
|
|
353
|
+
│ │ ts_syn │ │ ts_quote │ │ts_macro_derive│ │
|
|
354
|
+
│ │ (parsing) │ │ (templating) │ │ (proc-macro) │ │
|
|
355
|
+
│ └─────────────┘ └──────────────┘ └───────────────┘ │
|
|
356
|
+
├─────────────────────────────────────────────────────────┤
|
|
357
|
+
│ SWC Core │
|
|
358
|
+
│ (TypeScript parsing & codegen) │
|
|
359
|
+
└─────────────────────────────────────────────────────────┘
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Performance
|
|
363
|
+
|
|
364
|
+
- **Thread-safe expansion**: Each expansion runs in an isolated thread with a 32MB stack to handle deep AST recursion
|
|
365
|
+
- **Caching**: `NativePlugin` caches expansion results by file version
|
|
366
|
+
- **Binary search**: Position mapping uses O(log n) lookups
|
|
367
|
+
- **Zero-copy parsing**: SWC's arena allocator minimizes allocations
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "macroforge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "TypeScript macro expansion engine powered by Rust and SWC",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/rymskip/macroforge.git"
|
|
9
|
+
"url": "git+https://github.com/rymskip/macroforge-ts.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"typescript",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"author": "macroforge contributors",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"bugs": {
|
|
21
|
-
"url": "https://github.com/rymskip/macroforge/issues"
|
|
21
|
+
"url": "https://github.com/rymskip/macroforge-ts/issues"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/rymskip/macroforge#readme",
|
|
23
|
+
"homepage": "https://github.com/rymskip/macroforge-ts#readme",
|
|
24
24
|
"napi": {
|
|
25
25
|
"binaryName": "macroforge",
|
|
26
26
|
"packageName": "@macroforge/bin",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"node": ">= 18"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@macroforge/bin-darwin-x64": "0.1.
|
|
45
|
-
"@macroforge/bin-darwin-arm64": "0.1.
|
|
46
|
-
"@macroforge/bin-linux-x64-gnu": "0.1.
|
|
47
|
-
"@macroforge/bin-linux-arm64-gnu": "0.1.
|
|
48
|
-
"@macroforge/bin-win32-x64-msvc": "0.1.
|
|
49
|
-
"@macroforge/bin-win32-arm64-msvc": "0.1.
|
|
44
|
+
"@macroforge/bin-darwin-x64": "0.1.1",
|
|
45
|
+
"@macroforge/bin-darwin-arm64": "0.1.1",
|
|
46
|
+
"@macroforge/bin-linux-x64-gnu": "0.1.1",
|
|
47
|
+
"@macroforge/bin-linux-arm64-gnu": "0.1.1",
|
|
48
|
+
"@macroforge/bin-win32-x64-msvc": "0.1.1",
|
|
49
|
+
"@macroforge/bin-win32-arm64-msvc": "0.1.1"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"artifacts": "napi artifacts --npm-dir npm"
|