@macroforge/mcp-server 0.1.39 → 0.1.42
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/docs/api/api-overview.md +32 -32
- package/docs/api/expand-sync.md +30 -30
- package/docs/api/native-plugin.md +38 -38
- package/docs/api/position-mapper.md +18 -18
- package/docs/api/transform-sync.md +31 -31
- package/docs/builtin-macros/default.md +6 -6
- package/docs/builtin-macros/macros-overview.md +84 -84
- package/docs/builtin-macros/partial-ord.md +18 -20
- package/docs/concepts/architecture.md +16 -16
- package/docs/concepts/derive-system.md +89 -68
- package/docs/concepts/how-macros-work.md +13 -12
- package/docs/custom-macros/custom-overview.md +36 -36
- package/docs/custom-macros/rust-setup.md +71 -71
- package/docs/custom-macros/ts-macro-derive.md +167 -167
- package/docs/custom-macros/ts-quote.md +347 -347
- package/docs/getting-started/first-macro.md +57 -55
- package/docs/getting-started/installation.md +34 -35
- package/docs/integration/cli.md +43 -43
- package/docs/integration/configuration.md +41 -41
- package/docs/integration/integration-overview.md +4 -4
- package/docs/integration/mcp-server.md +22 -22
- package/docs/integration/svelte-preprocessor.md +87 -87
- package/docs/integration/typescript-plugin.md +23 -24
- package/docs/integration/vite-plugin.md +40 -40
- package/docs/language-servers/svelte.md +15 -16
- package/docs/language-servers/zed.md +14 -15
- package/package.json +2 -2
|
@@ -3,122 +3,122 @@
|
|
|
3
3
|
## Overview
|
|
4
4
|
| Macro | Generates | Description |
|
|
5
5
|
| --- | --- | --- |
|
|
6
|
-
| [
|
|
7
|
-
| [
|
|
8
|
-
| [
|
|
9
|
-
| [
|
|
10
|
-
| [
|
|
11
|
-
| [
|
|
12
|
-
| [
|
|
13
|
-
| [
|
|
14
|
-
| [
|
|
6
|
+
| [Debug](../docs/builtin-macros/debug) | toString(): string | Human-readable string representation |
|
|
7
|
+
| [Clone](../docs/builtin-macros/clone) | clone(): T | Creates a deep copy of the object |
|
|
8
|
+
| [Default](../docs/builtin-macros/default) | static default(): T | Creates an instance with default values |
|
|
9
|
+
| [Hash](../docs/builtin-macros/hash) | hashCode(): number | Generates a hash code for the object |
|
|
10
|
+
| [PartialEq](../docs/builtin-macros/partial-eq) | equals(other: T): boolean | Value equality comparison |
|
|
11
|
+
| [Ord](../docs/builtin-macros/ord) | compare(other: T): number | Total ordering comparison (-1, 0, 1) |
|
|
12
|
+
| [PartialOrd](../docs/builtin-macros/partial-ord) | partialCompare(other: T): number | null | Partial ordering comparison |
|
|
13
|
+
| [Serialize](../docs/builtin-macros/serialize) | toJSON(): Record<string, unknown> | JSON serialization with type handling |
|
|
14
|
+
| [Deserialize](../docs/builtin-macros/deserialize) | static fromJSON(data: unknown): T | JSON deserialization with validation |
|
|
15
15
|
## Using Built-in Macros
|
|
16
|
-
Built-in macros don't require imports. Just use them with
|
|
16
|
+
Built-in macros don't require imports. Just use them with <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@derive</code>:
|
|
17
17
|
```
|
|
18
|
-
|
|
19
|
-
class
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
/** @derive(Debug, Clone, PartialEq) */
|
|
19
|
+
class User {
|
|
20
|
+
name: string;
|
|
21
|
+
age: number;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
constructor(name: string, age: number) {
|
|
24
|
+
this.name = name;
|
|
25
|
+
this.age = age;
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
``` ## Interface Support
|
|
29
|
-
All built-in macros work with interfaces. For interfaces, methods are generated as functions in a namespace with the same name, using
|
|
29
|
+
All built-in macros work with interfaces. For interfaces, methods are generated as functions in a namespace with the same name, using <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">self</code> as the first parameter:
|
|
30
30
|
```
|
|
31
|
-
|
|
32
|
-
interface
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
/** @derive(Debug, Clone, PartialEq) */
|
|
32
|
+
interface Point {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
// Generated namespace:
|
|
38
|
+
// namespace Point {
|
|
39
|
+
// export function toString(self: Point): string { ... }
|
|
40
|
+
// export function clone(self: Point): Point { ... }
|
|
41
|
+
// export function equals(self: Point, other: Point): boolean { ... }
|
|
42
|
+
// export function hashCode(self: Point): number { ... }
|
|
43
|
+
// }
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const point: Point = { x: 10, y: 20 };
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
console.log(Point.toString(point));
|
|
49
|
-
const
|
|
50
|
-
console.log(Point.equals(point
|
|
47
|
+
// Use the namespace functions
|
|
48
|
+
console.log(Point.toString(point)); // "Point { x: 10, y: 20 }"
|
|
49
|
+
const copy = Point.clone(point); // { x: 10, y: 20 }
|
|
50
|
+
console.log(Point.equals(point, copy)); // true
|
|
51
51
|
``` ## Enum Support
|
|
52
52
|
All built-in macros work with enums. For enums, methods are generated as functions in a namespace with the same name:
|
|
53
53
|
```
|
|
54
|
-
|
|
55
|
-
enum
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
/** @derive(Debug, Clone, PartialEq, Serialize, Deserialize) */
|
|
55
|
+
enum Status {
|
|
56
|
+
Active = "active",
|
|
57
|
+
Inactive = "inactive",
|
|
58
|
+
Pending = "pending",
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
// Generated namespace:
|
|
62
|
+
// namespace Status {
|
|
63
|
+
// export function toString(value: Status): string { ... }
|
|
64
|
+
// export function clone(value: Status): Status { ... }
|
|
65
|
+
// export function equals(a: Status, b: Status): boolean { ... }
|
|
66
|
+
// export function hashCode(value: Status): number { ... }
|
|
67
|
+
// export function toJSON(value: Status): string | number { ... }
|
|
68
|
+
// export function fromJSON(data: unknown): Status { ... }
|
|
69
|
+
// }
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
console.log(Status.toString(Status.Active));
|
|
73
|
-
console.log(Status.equals(Status.Active
|
|
74
|
-
const
|
|
75
|
-
const
|
|
71
|
+
// Use the namespace functions
|
|
72
|
+
console.log(Status.toString(Status.Active)); // "Status.Active"
|
|
73
|
+
console.log(Status.equals(Status.Active, Status.Active)); // true
|
|
74
|
+
const json = Status.toJSON(Status.Pending); // "pending"
|
|
75
|
+
const parsed = Status.fromJSON("active"); // Status.Active
|
|
76
76
|
``` ## Type Alias Support
|
|
77
77
|
All built-in macros work with type aliases. For object type aliases, field-aware methods are generated in a namespace:
|
|
78
78
|
```
|
|
79
|
-
|
|
80
|
-
type
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
/** @derive(Debug, Clone, PartialEq, Serialize, Deserialize) */
|
|
80
|
+
type Point = {
|
|
81
|
+
x: number;
|
|
82
|
+
y: number;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
// Generated namespace:
|
|
86
|
+
// namespace Point {
|
|
87
|
+
// export function toString(value: Point): string { ... }
|
|
88
|
+
// export function clone(value: Point): Point { ... }
|
|
89
|
+
// export function equals(a: Point, b: Point): boolean { ... }
|
|
90
|
+
// export function hashCode(value: Point): number { ... }
|
|
91
|
+
// export function toJSON(value: Point): Record<string, unknown> { ... }
|
|
92
|
+
// export function fromJSON(data: unknown): Point { ... }
|
|
93
|
+
// }
|
|
94
94
|
|
|
95
|
-
const
|
|
96
|
-
console.log(Point.toString(point));
|
|
97
|
-
const
|
|
98
|
-
console.log(Point.equals(point
|
|
95
|
+
const point: Point = { x: 10, y: 20 };
|
|
96
|
+
console.log(Point.toString(point)); // "Point { x: 10, y: 20 }"
|
|
97
|
+
const copy = Point.clone(point); // { x: 10, y: 20 }
|
|
98
|
+
console.log(Point.equals(point, copy)); // true
|
|
99
99
|
``` Union type aliases also work, using JSON-based implementations:
|
|
100
100
|
```
|
|
101
|
-
|
|
102
|
-
type
|
|
101
|
+
/** @derive(Debug, PartialEq) */
|
|
102
|
+
type ApiStatus = "loading" | "success" | "error";
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
console.log(ApiStatus.toString(status));
|
|
106
|
-
console.log(ApiStatus.equals("success"
|
|
104
|
+
const status: ApiStatus = "success";
|
|
105
|
+
console.log(ApiStatus.toString(status)); // "ApiStatus(\\"success\\")"
|
|
106
|
+
console.log(ApiStatus.equals("success", "success")); // true
|
|
107
107
|
``` ## Combining Macros
|
|
108
108
|
All macros can be used together. They don't conflict and each generates independent methods:
|
|
109
109
|
```
|
|
110
|
-
const
|
|
110
|
+
const user = new User("Alice", 30);
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
// Debug
|
|
113
113
|
console.log(user.toString());
|
|
114
|
-
|
|
114
|
+
// "User { name: Alice, age: 30 }"
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
console.log(copy.name);
|
|
116
|
+
// Clone
|
|
117
|
+
const copy = user.clone();
|
|
118
|
+
console.log(copy.name); // "Alice"
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
console.log(user.equals(copy));
|
|
120
|
+
// Eq
|
|
121
|
+
console.log(user.equals(copy)); // true
|
|
122
122
|
``` ## Detailed Documentation
|
|
123
123
|
Each macro has its own options and behaviors:
|
|
124
124
|
- [**Debug**](../docs/builtin-macros/debug) - Customizable field renaming and skipping
|
|
@@ -70,32 +70,30 @@ class Temperature {
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
```typescript after
|
|
73
|
-
import { Option } from 'macroforge/utils';
|
|
74
|
-
|
|
75
73
|
class Temperature {
|
|
76
74
|
value: number | null;
|
|
77
75
|
unit: string;
|
|
78
76
|
|
|
79
|
-
static compareTo(a: Temperature, b: Temperature):
|
|
77
|
+
static compareTo(a: Temperature, b: Temperature): number | null {
|
|
80
78
|
return temperaturePartialCompare(a, b);
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
export function temperaturePartialCompare(a: Temperature, b: Temperature):
|
|
85
|
-
if (a === b) return
|
|
82
|
+
export function temperaturePartialCompare(a: Temperature, b: Temperature): number | null {
|
|
83
|
+
if (a === b) return 0;
|
|
86
84
|
const cmp0 = (() => {
|
|
87
85
|
if (typeof (a.value as any)?.compareTo === 'function') {
|
|
88
86
|
const optResult = (a.value as any).compareTo(b.value);
|
|
89
|
-
return
|
|
87
|
+
return optResult === null ? null : optResult;
|
|
90
88
|
}
|
|
91
89
|
return a.value === b.value ? 0 : null;
|
|
92
90
|
})();
|
|
93
|
-
if (cmp0 === null) return
|
|
94
|
-
if (cmp0 !== 0) return
|
|
91
|
+
if (cmp0 === null) return null;
|
|
92
|
+
if (cmp0 !== 0) return cmp0;
|
|
95
93
|
const cmp1 = a.unit.localeCompare(b.unit);
|
|
96
|
-
if (cmp1 === null) return
|
|
97
|
-
if (cmp1 !== 0) return
|
|
98
|
-
return
|
|
94
|
+
if (cmp1 === null) return null;
|
|
95
|
+
if (cmp1 !== 0) return cmp1;
|
|
96
|
+
return 0;
|
|
99
97
|
}
|
|
100
98
|
```
|
|
101
99
|
|
|
@@ -106,26 +104,26 @@ class Temperature {
|
|
|
106
104
|
value: number | null;
|
|
107
105
|
unit: string;
|
|
108
106
|
|
|
109
|
-
static compareTo(a: Temperature, b: Temperature):
|
|
107
|
+
static compareTo(a: Temperature, b: Temperature): number | null {
|
|
110
108
|
return temperaturePartialCompare(a, b);
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
export function temperaturePartialCompare(a: Temperature, b: Temperature):
|
|
115
|
-
if (a === b) return
|
|
112
|
+
export function temperaturePartialCompare(a: Temperature, b: Temperature): number | null {
|
|
113
|
+
if (a === b) return 0;
|
|
116
114
|
const cmp0 = (() => {
|
|
117
115
|
if (typeof (a.value as any)?.compareTo === 'function') {
|
|
118
116
|
const optResult = (a.value as any).compareTo(b.value);
|
|
119
|
-
return
|
|
117
|
+
return optResult === null ? null : optResult;
|
|
120
118
|
}
|
|
121
119
|
return a.value === b.value ? 0 : null;
|
|
122
120
|
})();
|
|
123
|
-
if (cmp0 === null) return
|
|
124
|
-
if (cmp0 !== 0) return
|
|
121
|
+
if (cmp0 === null) return null;
|
|
122
|
+
if (cmp0 !== 0) return cmp0;
|
|
125
123
|
const cmp1 = a.unit.localeCompare(b.unit);
|
|
126
|
-
if (cmp1 === null) return
|
|
127
|
-
if (cmp1 !== 0) return
|
|
128
|
-
return
|
|
124
|
+
if (cmp1 === null) return null;
|
|
125
|
+
if (cmp1 !== 0) return cmp1;
|
|
126
|
+
return 0;
|
|
129
127
|
}
|
|
130
128
|
```
|
|
131
129
|
|
|
@@ -13,37 +13,37 @@
|
|
|
13
13
|
- Parsing utilities for macro input
|
|
14
14
|
- Derive input structures (class fields, decorators, etc.)
|
|
15
15
|
### macroforge_ts_quote
|
|
16
|
-
Template-based code generation similar to Rust's
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- Control flow:
|
|
16
|
+
Template-based code generation similar to Rust's <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">quote!</code>:
|
|
17
|
+
- <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_template!</code> - Generate TypeScript code from templates
|
|
18
|
+
- <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">body!</code> - Generate class body members
|
|
19
|
+
- Control flow: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"{#for}"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"{#if}"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"{$let}"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
20
20
|
### macroforge_ts_macros
|
|
21
21
|
The procedural macro attribute for defining derive macros:
|
|
22
|
-
-
|
|
22
|
+
- <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">#[<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_macro_derive<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(Name)]</code> attribute
|
|
23
23
|
- Automatic registration with the macro system
|
|
24
24
|
- Error handling and span tracking
|
|
25
25
|
### NAPI-RS Bindings
|
|
26
26
|
Bridges Rust and Node.js:
|
|
27
|
-
- Exposes
|
|
28
|
-
- Provides the
|
|
27
|
+
- Exposes <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">expandSync</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">transformSync</code>, etc.
|
|
28
|
+
- Provides the <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">NativePlugin</code> class for caching
|
|
29
29
|
- Handles data marshaling between Rust and JavaScript
|
|
30
30
|
## Data Flow
|
|
31
31
|
<div class="w-full max-w-md border border-border rounded-lg bg-card p-4 text-center shadow-sm"><div class="font-semibold text-foreground">1. Source Code TypeScript with @derive <div class="font-semibold text-foreground">2. NAPI-RS receives JavaScript string <div class="font-semibold text-foreground">3. SWC Parser parses to AST <div class="font-semibold text-foreground">4. Macro Expander finds @derive decorators <div class="font-semibold text-foreground">5. For Each Macro extract data, run macro, generate AST nodes <div class="font-semibold text-foreground">6. Merge generated nodes into AST <div class="font-semibold text-foreground">7. SWC Codegen generates source code <div class="font-semibold text-foreground">8. Return to JavaScript with source mapping ## Performance Characteristics
|
|
32
32
|
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
33
|
-
- **Caching**:
|
|
33
|
+
- **Caching**: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">NativePlugin</code> caches results by file version
|
|
34
34
|
- **Binary search**: Position mapping uses O(log n) lookups
|
|
35
35
|
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
36
36
|
## Re-exported Crates
|
|
37
|
-
For custom macro development,
|
|
37
|
+
For custom macro development, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">macroforge_ts</code> re-exports everything you need:
|
|
38
38
|
```
|
|
39
|
-
|
|
40
|
-
use
|
|
41
|
-
use
|
|
39
|
+
// Convenient re-exports for macro development
|
|
40
|
+
use macroforge_ts::macros::{ts_macro_derive, body, ts_template, above, below, signature};
|
|
41
|
+
use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input};
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
use
|
|
45
|
-
use
|
|
46
|
-
use
|
|
43
|
+
// Also available: raw crate access and SWC modules
|
|
44
|
+
use macroforge_ts::swc_core;
|
|
45
|
+
use macroforge_ts::swc_common;
|
|
46
|
+
use macroforge_ts::swc_ecma_ast;
|
|
47
47
|
``` ## Next Steps
|
|
48
48
|
- [Write custom macros](../../docs/custom-macros)
|
|
49
49
|
- [Explore the API reference](../../docs/api)
|
|
@@ -1,125 +1,146 @@
|
|
|
1
1
|
# The Derive System
|
|
2
|
-
*The derive system is inspired by Rust's derive macros. It allows you to automatically implement common patterns by annotating your classes with
|
|
2
|
+
*The derive system is inspired by Rust's derive macros. It allows you to automatically implement common patterns by annotating your classes with <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@derive</code>.*
|
|
3
3
|
## Syntax Reference
|
|
4
4
|
Macroforge uses JSDoc comments for all macro annotations. This ensures compatibility with standard TypeScript tooling.
|
|
5
5
|
### The @derive Statement
|
|
6
|
-
The
|
|
6
|
+
The <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@derive</code> decorator triggers macro expansion on a class or interface:
|
|
7
7
|
**Source:**
|
|
8
8
|
```
|
|
9
9
|
/** @derive(Debug) */
|
|
10
|
-
class MyClass
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
class MyClass {
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
13
|
``` Syntax rules:
|
|
14
|
-
- Must be inside a JSDoc comment (
|
|
14
|
+
- Must be inside a JSDoc comment (<code class="shiki-inline"><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/** */</code>)
|
|
15
15
|
- Must appear immediately before the class/interface declaration
|
|
16
|
-
- Multiple macros can be comma-separated:
|
|
17
|
-
- Multiple
|
|
16
|
+
- Multiple macros can be comma-separated: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">derive<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(<span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">A<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, <span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">B<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, <span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">C<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</code>
|
|
17
|
+
- Multiple <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@derive</code> statements can be stacked
|
|
18
18
|
**Source:**
|
|
19
19
|
```
|
|
20
20
|
/** @derive(Debug, Clone) */
|
|
21
|
-
class User
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
class User {
|
|
22
|
+
name: string;
|
|
23
|
+
email: string;
|
|
24
|
+
}
|
|
25
25
|
``` ### The import macro Statement
|
|
26
|
-
To use macros from external packages, you must declare them with
|
|
26
|
+
To use macros from external packages, you must declare them with <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> macro</code>:
|
|
27
27
|
```
|
|
28
|
-
|
|
28
|
+
/** import macro { MacroName } from "package-name"; */
|
|
29
29
|
``` Syntax rules:
|
|
30
|
-
- Must be inside a JSDoc comment (
|
|
30
|
+
- Must be inside a JSDoc comment (<code class="shiki-inline"><span class="line"><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/** */</code>)
|
|
31
31
|
- Can appear anywhere in the file (typically at the top)
|
|
32
|
-
- Multiple macros can be imported:
|
|
32
|
+
- Multiple macros can be imported: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> macro { A, B } <span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "pkg"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</code>
|
|
33
33
|
- Multiple import statements can be used for different packages
|
|
34
34
|
```
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
/** import macro { JSON, Validate } from "@my/macros"; */
|
|
36
|
+
/** import macro { Builder } from "@other/macros"; */
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
class
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/** @derive(JSON, Validate, Builder) */
|
|
39
|
+
class User {
|
|
40
|
+
name: string;
|
|
41
|
+
email: string;
|
|
42
42
|
}
|
|
43
43
|
``` **Built-in macros Built-in macros (Debug, Clone, Default, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize) do not require an import statement. ### Field Attributes
|
|
44
44
|
Macros can define field-level attributes to customize behavior per field:
|
|
45
|
-
|
|
45
|
+
**<div><div class="flex items-center justify-between gap-2 px-4 py-2 bg-muted rounded-t-lg border border-b-0 border-border">
|
|
46
|
+
**Before:**
|
|
46
47
|
```
|
|
47
48
|
/** @derive(Debug, Serialize) */
|
|
48
|
-
class User
|
|
49
|
-
/** @debug(
|
|
50
|
-
/** @serde(
|
|
49
|
+
class User {
|
|
50
|
+
/** @debug({ rename: "userId" }) */
|
|
51
|
+
/** @serde({ rename: "user_id" }) */
|
|
51
52
|
id: number;
|
|
52
53
|
|
|
53
54
|
name: string;
|
|
54
55
|
|
|
55
|
-
/** @debug(
|
|
56
|
-
/** @serde(
|
|
56
|
+
/** @debug({ skip: true }) */
|
|
57
|
+
/** @serde({ skip: true }) */
|
|
57
58
|
password: string;
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```
|
|
60
|
+
metadata: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
``` <div class="flex items-center justify-between gap-2 px-4 py-2 bg-muted rounded-t-lg border border-b-0 border-border">
|
|
63
63
|
**After:**
|
|
64
64
|
```
|
|
65
|
-
import
|
|
65
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
66
66
|
|
|
67
|
-
class User
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
id: number;
|
|
67
|
+
class User {
|
|
68
|
+
id: number;
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
name: string;
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
password: string;
|
|
72
|
+
password: string;
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
metadata: Record<string, unknown>;
|
|
74
|
+
metadata: Record<string, unknown>;
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
/** Serializes a value to a JSON string.
|
|
76
|
+
static toString(value: User): string {
|
|
77
|
+
return userToString(value);
|
|
78
|
+
}
|
|
79
|
+
/** Serializes a value to a JSON string.
|
|
85
80
|
@param value - The value to serialize
|
|
86
81
|
@returns JSON string representation with cycle detection metadata */
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/** @internal Serializes with an existing context for nested/cyclic object graphs.
|
|
83
|
+
static serialize(value: User): string {
|
|
84
|
+
return userSerialize(value);
|
|
85
|
+
}
|
|
86
|
+
/** @internal Serializes with an existing context for nested/cyclic object graphs.
|
|
92
87
|
@param value - The value to serialize
|
|
93
88
|
@param ctx - The serialization context */
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
static serializeWithContext(value: User, ctx: SerializeContext): Record<string, unknown> {
|
|
91
|
+
return userSerializeWithContext(value, ctx);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
99
94
|
|
|
100
|
-
export function userToString(value: User): string
|
|
95
|
+
export function userToString(value: User): string {
|
|
96
|
+
const parts: string[] = [];
|
|
97
|
+
parts.push('userId: ' + value.id);
|
|
98
|
+
parts.push('name: ' + value.name);
|
|
99
|
+
parts.push('metadata: ' + value.metadata);
|
|
100
|
+
return 'User { ' + parts.join(', ') + ' }';
|
|
101
|
+
}
|
|
101
102
|
|
|
102
103
|
/** Serializes a value to a JSON string.
|
|
103
104
|
@param value - The value to serialize
|
|
104
|
-
@returns JSON string representation with cycle detection metadata */export function userSerialize(
|
|
105
|
+
@returns JSON string representation with cycle detection metadata */ export function userSerialize(
|
|
106
|
+
value: User
|
|
107
|
+
): string {
|
|
108
|
+
const ctx = SerializeContext.create();
|
|
109
|
+
return JSON.stringify(userSerializeWithContext(value, ctx));
|
|
110
|
+
} /** @internal Serializes with an existing context for nested/cyclic object graphs.
|
|
105
111
|
@param value - The value to serialize
|
|
106
|
-
@param ctx - The serialization context */
|
|
112
|
+
@param ctx - The serialization context */
|
|
113
|
+
export function userSerializeWithContext(
|
|
114
|
+
value: User,
|
|
115
|
+
ctx: SerializeContext
|
|
116
|
+
): Record<string, unknown> {
|
|
117
|
+
const existingId = ctx.getId(value);
|
|
118
|
+
if (existingId !== undefined) {
|
|
119
|
+
return { __ref: existingId };
|
|
120
|
+
}
|
|
121
|
+
const __id = ctx.register(value);
|
|
122
|
+
const result: Record<string, unknown> = { __type: 'User', __id };
|
|
123
|
+
result['user_id'] = value.id;
|
|
124
|
+
result['name'] = value.name;
|
|
125
|
+
result['metadata'] = value.metadata;
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
107
128
|
``` Syntax rules:
|
|
108
129
|
- Must be inside a JSDoc comment immediately before the field
|
|
109
|
-
- Options use object literal syntax:
|
|
110
|
-
- Boolean options:
|
|
111
|
-
- String options:
|
|
130
|
+
- Options use object literal syntax: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">attr<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ key: value })</code>
|
|
131
|
+
- Boolean options: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">attr<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ skip: <span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> })</code>
|
|
132
|
+
- String options: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">attr<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ rename: <span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"newName"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> })</code>
|
|
112
133
|
- Multiple attributes can be on separate lines or combined
|
|
113
134
|
Common field attributes by macro:
|
|
114
135
|
| Macro | Attribute | Options |
|
|
115
136
|
| --- | --- | --- |
|
|
116
|
-
| Debug |
|
|
117
|
-
| Clone |
|
|
118
|
-
| Serialize/Deserialize |
|
|
119
|
-
| Hash |
|
|
120
|
-
| PartialEq/Ord |
|
|
137
|
+
| Debug | @debug | skip, rename |
|
|
138
|
+
| Clone | @clone | skip, clone_with |
|
|
139
|
+
| Serialize/Deserialize | @serde | skip, rename, flatten, default |
|
|
140
|
+
| Hash | @hash | skip |
|
|
141
|
+
| PartialEq/Ord | @eq, @ord | skip |
|
|
121
142
|
## How It Works
|
|
122
|
-
1. **Declaration**: You write
|
|
143
|
+
1. **Declaration**: You write <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">derive<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(MacroName)</code> before a class
|
|
123
144
|
2. **Discovery**: Macroforge finds all derive decorators in your code
|
|
124
145
|
3. **Expansion**: Each named macro receives the class AST and generates code
|
|
125
146
|
4. **Injection**: Generated methods/properties are added to the class
|
|
@@ -130,7 +151,7 @@ export function userToString(value: User): string {const parts: string[]= []; pa
|
|
|
130
151
|
- **Enums**: Macros generate namespace functions for enum values
|
|
131
152
|
- **Type aliases**: Both object types and union types are supported
|
|
132
153
|
## Built-in vs Custom Macros
|
|
133
|
-
Macroforge comes with built-in macros that work out of the box. You can also create custom macros in Rust and use them via the
|
|
154
|
+
Macroforge comes with built-in macros that work out of the box. You can also create custom macros in Rust and use them via the <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> macro</code> statement.
|
|
134
155
|
| Type | Import Required | Examples |
|
|
135
156
|
| --- | --- | --- |
|
|
136
157
|
| Built-in | No | Debug, Clone, Default, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize |
|