@macroforge/mcp-server 0.1.17
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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/docs-loader.d.ts +30 -0
- package/dist/tools/docs-loader.d.ts.map +1 -0
- package/dist/tools/docs-loader.js +112 -0
- package/dist/tools/docs-loader.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +348 -0
- package/dist/tools/index.js.map +1 -0
- package/docs/api/api-overview.md +75 -0
- package/docs/api/expand-sync.md +121 -0
- package/docs/api/native-plugin.md +106 -0
- package/docs/api/position-mapper.md +127 -0
- package/docs/api/transform-sync.md +98 -0
- package/docs/builtin-macros/clone.md +180 -0
- package/docs/builtin-macros/debug.md +222 -0
- package/docs/builtin-macros/default.md +192 -0
- package/docs/builtin-macros/deserialize.md +662 -0
- package/docs/builtin-macros/hash.md +205 -0
- package/docs/builtin-macros/macros-overview.md +169 -0
- package/docs/builtin-macros/ord.md +258 -0
- package/docs/builtin-macros/partial-eq.md +306 -0
- package/docs/builtin-macros/partial-ord.md +268 -0
- package/docs/builtin-macros/serialize.md +321 -0
- package/docs/concepts/architecture.md +139 -0
- package/docs/concepts/derive-system.md +173 -0
- package/docs/concepts/how-macros-work.md +124 -0
- package/docs/custom-macros/custom-overview.md +84 -0
- package/docs/custom-macros/rust-setup.md +146 -0
- package/docs/custom-macros/ts-macro-derive.md +307 -0
- package/docs/custom-macros/ts-quote.md +696 -0
- package/docs/getting-started/first-macro.md +120 -0
- package/docs/getting-started/installation.md +110 -0
- package/docs/integration/cli.md +207 -0
- package/docs/integration/configuration.md +116 -0
- package/docs/integration/integration-overview.md +51 -0
- package/docs/integration/typescript-plugin.md +96 -0
- package/docs/integration/vite-plugin.md +126 -0
- package/docs/language-servers/ls-overview.md +47 -0
- package/docs/language-servers/svelte-ls.md +80 -0
- package/docs/language-servers/zed-extensions.md +84 -0
- package/docs/sections.json +258 -0
- package/package.json +48 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# How Macros Work
|
|
2
|
+
|
|
3
|
+
*Macroforge performs compile-time code generation by parsing your TypeScript, expanding macros, and outputting transformed code. This happens before your code runs, resulting in zero runtime overhead.*
|
|
4
|
+
|
|
5
|
+
## Compile-Time Expansion
|
|
6
|
+
|
|
7
|
+
Unlike runtime solutions that use reflection or proxies, Macroforge expands macros at compile time:
|
|
8
|
+
|
|
9
|
+
1. **Parse**: Your TypeScript code is parsed into an AST using SWC
|
|
10
|
+
|
|
11
|
+
2. **Find**: Macroforge finds `@derive` decorators and their associated items
|
|
12
|
+
|
|
13
|
+
3. **Expand**: Each macro generates new code based on the class structure
|
|
14
|
+
|
|
15
|
+
4. **Output**: The transformed TypeScript is written out, ready for normal compilation
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Your source code
|
|
19
|
+
/** @derive(Debug) */
|
|
20
|
+
class User {
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// After macro expansion
|
|
25
|
+
class User {
|
|
26
|
+
name: string;
|
|
27
|
+
|
|
28
|
+
toString(): string {
|
|
29
|
+
return \`User { name: \${this.name} }\`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Zero Runtime Overhead
|
|
35
|
+
|
|
36
|
+
Because code generation happens at compile time, there's no:
|
|
37
|
+
|
|
38
|
+
- Runtime reflection or metadata
|
|
39
|
+
|
|
40
|
+
- Proxy objects or wrappers
|
|
41
|
+
|
|
42
|
+
- Additional dependencies in your bundle
|
|
43
|
+
|
|
44
|
+
- Performance cost at runtime
|
|
45
|
+
|
|
46
|
+
The generated code is plain TypeScript that compiles to efficient JavaScript.
|
|
47
|
+
|
|
48
|
+
## Source Mapping
|
|
49
|
+
|
|
50
|
+
Macroforge tracks the relationship between your source code and the expanded output. This means:
|
|
51
|
+
|
|
52
|
+
- Errors in generated code point back to your source
|
|
53
|
+
|
|
54
|
+
- Debugging works correctly
|
|
55
|
+
|
|
56
|
+
- IDE features like "go to definition" work as expected
|
|
57
|
+
|
|
58
|
+
> **Note:**
|
|
59
|
+
> The TypeScript plugin uses source mapping to show errors at the `@derive` decorator position, not in the generated code.
|
|
60
|
+
|
|
61
|
+
## Execution Flow
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
┌─────────────────────────────────────────────────────────┐
|
|
65
|
+
│ Your Source Code │
|
|
66
|
+
│ (with @derive decorators) │
|
|
67
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
┌─────────────────────────────────────────────────────────┐
|
|
71
|
+
│ SWC Parser │
|
|
72
|
+
│ (TypeScript → AST) │
|
|
73
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
┌─────────────────────────────────────────────────────────┐
|
|
77
|
+
│ Macro Expansion Engine │
|
|
78
|
+
│ - Finds @derive decorators │
|
|
79
|
+
│ - Runs registered macros │
|
|
80
|
+
│ - Generates new AST nodes │
|
|
81
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
82
|
+
│
|
|
83
|
+
▼
|
|
84
|
+
┌─────────────────────────────────────────────────────────┐
|
|
85
|
+
│ Code Generator │
|
|
86
|
+
│ (AST → TypeScript) │
|
|
87
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
88
|
+
│
|
|
89
|
+
▼
|
|
90
|
+
┌─────────────────────────────────────────────────────────┐
|
|
91
|
+
│ Expanded TypeScript │
|
|
92
|
+
│ (ready for normal compilation) │
|
|
93
|
+
└─────────────────────────────────────────────────────────┘
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Integration Points
|
|
97
|
+
|
|
98
|
+
Macroforge integrates at two key points:
|
|
99
|
+
|
|
100
|
+
### IDE (TypeScript Plugin)
|
|
101
|
+
|
|
102
|
+
The TypeScript plugin intercepts language server calls to provide:
|
|
103
|
+
|
|
104
|
+
- Diagnostics that reference your source, not expanded code
|
|
105
|
+
|
|
106
|
+
- Completions for generated methods
|
|
107
|
+
|
|
108
|
+
- Hover information showing what macros generate
|
|
109
|
+
|
|
110
|
+
### Build (Vite Plugin)
|
|
111
|
+
|
|
112
|
+
The Vite plugin runs macro expansion during the build process:
|
|
113
|
+
|
|
114
|
+
- Transforms files before they reach the TypeScript compiler
|
|
115
|
+
|
|
116
|
+
- Generates type declaration files (.d.ts)
|
|
117
|
+
|
|
118
|
+
- Produces metadata for debugging
|
|
119
|
+
|
|
120
|
+
## Next Steps
|
|
121
|
+
|
|
122
|
+
- [Learn about the derive system]({base}/docs/concepts/derive-system)
|
|
123
|
+
|
|
124
|
+
- [Explore the architecture]({base}/docs/concepts/architecture)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Custom Macros
|
|
2
|
+
|
|
3
|
+
*Macroforge allows you to create custom derive macros in Rust. Your macros have full access to the class AST and can generate any TypeScript code.*
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Custom macros are written in Rust and compiled to native Node.js addons. The process involves:
|
|
8
|
+
|
|
9
|
+
1. Creating a Rust crate with NAPI bindings
|
|
10
|
+
|
|
11
|
+
2. Defining macro functions with `#[ts_macro_derive]`
|
|
12
|
+
|
|
13
|
+
3. Using `ts_quote` to generate TypeScript code
|
|
14
|
+
|
|
15
|
+
4. Building and publishing as an npm package
|
|
16
|
+
|
|
17
|
+
## Quick Example
|
|
18
|
+
|
|
19
|
+
```rust
|
|
20
|
+
use macroforge_ts::ts_macro_derive::ts_macro_derive;
|
|
21
|
+
use macroforge_ts::ts_quote::body;
|
|
22
|
+
use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input};
|
|
23
|
+
|
|
24
|
+
#[ts_macro_derive(
|
|
25
|
+
JSON,
|
|
26
|
+
description = "Generates toJSON() returning a plain object"
|
|
27
|
+
)]
|
|
28
|
+
pub fn derive_json(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
29
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
30
|
+
|
|
31
|
+
match &input.data {
|
|
32
|
+
Data::Class(class) => {
|
|
33
|
+
Ok(body! {
|
|
34
|
+
toJSON(): Record<string, unknown> {
|
|
35
|
+
return {
|
|
36
|
+
{#for field in class.field_names()}
|
|
37
|
+
@{field}: this.@{field},
|
|
38
|
+
{/for}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
_ => Err(MacroforgeError::new(
|
|
44
|
+
input.decorator_span(),
|
|
45
|
+
"@derive(JSON) only works on classes",
|
|
46
|
+
)),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Using Custom Macros
|
|
52
|
+
|
|
53
|
+
Once your macro package is published, users can import and use it:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
/** import macro { JSON } from "@my/macros"; */
|
|
57
|
+
|
|
58
|
+
/** @derive(JSON) */
|
|
59
|
+
class User {
|
|
60
|
+
name: string;
|
|
61
|
+
age: number;
|
|
62
|
+
|
|
63
|
+
constructor(name: string, age: number) {
|
|
64
|
+
this.name = name;
|
|
65
|
+
this.age = age;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const user = new User("Alice", 30);
|
|
70
|
+
console.log(user.toJSON()); // { name: "Alice", age: 30 }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
>
|
|
74
|
+
> The `import macro` comment tells Macroforge which package provides the macro.
|
|
75
|
+
|
|
76
|
+
## Getting Started
|
|
77
|
+
|
|
78
|
+
Follow these guides to create your own macros:
|
|
79
|
+
|
|
80
|
+
- [Set up a Rust macro crate]({base}/docs/custom-macros/rust-setup)
|
|
81
|
+
|
|
82
|
+
- [Use the ts_macro_derive attribute]({base}/docs/custom-macros/ts-macro-derive)
|
|
83
|
+
|
|
84
|
+
- [Learn the ts_quote template syntax]({base}/docs/custom-macros/ts-quote)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Rust Setup
|
|
2
|
+
|
|
3
|
+
*Create a new Rust crate that will contain your custom macros. This crate compiles to a native Node.js addon.*
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Rust toolchain (1.70 or later)
|
|
8
|
+
|
|
9
|
+
- Node.js 18 or later
|
|
10
|
+
|
|
11
|
+
- NAPI-RS CLI: `npm install -g @napi-rs/cli`
|
|
12
|
+
|
|
13
|
+
## Create the Project
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Create a new directory
|
|
17
|
+
mkdir my-macros
|
|
18
|
+
cd my-macros
|
|
19
|
+
|
|
20
|
+
# Initialize with NAPI-RS
|
|
21
|
+
napi new --platform --name my-macros
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configure Cargo.toml
|
|
25
|
+
|
|
26
|
+
Update your `Cargo.toml` with the required dependencies:
|
|
27
|
+
|
|
28
|
+
`Cargo.toml`
|
|
29
|
+
```toml
|
|
30
|
+
[package]
|
|
31
|
+
name = "my-macros"
|
|
32
|
+
version = "0.1.0"
|
|
33
|
+
edition = "2021"
|
|
34
|
+
|
|
35
|
+
[lib]
|
|
36
|
+
crate-type = ["cdylib"]
|
|
37
|
+
|
|
38
|
+
[dependencies]
|
|
39
|
+
macroforge_ts = "0.1"
|
|
40
|
+
napi = { version = "3", features = ["napi8", "compat-mode"] }
|
|
41
|
+
napi-derive = "3"
|
|
42
|
+
|
|
43
|
+
[build-dependencies]
|
|
44
|
+
napi-build = "2"
|
|
45
|
+
|
|
46
|
+
[profile.release]
|
|
47
|
+
lto = true
|
|
48
|
+
strip = true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Create build.rs
|
|
52
|
+
|
|
53
|
+
`build.rs`
|
|
54
|
+
```rust
|
|
55
|
+
fn main() {
|
|
56
|
+
napi_build::setup();
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Create src/lib.rs
|
|
61
|
+
|
|
62
|
+
`src/lib.rs`
|
|
63
|
+
```rust
|
|
64
|
+
use macroforge_ts::ts_macro_derive::ts_macro_derive;
|
|
65
|
+
use macroforge_ts::ts_quote::body;
|
|
66
|
+
use macroforge_ts::ts_syn::{
|
|
67
|
+
Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
#[ts_macro_derive(
|
|
71
|
+
JSON,
|
|
72
|
+
description = "Generates toJSON() returning a plain object"
|
|
73
|
+
)]
|
|
74
|
+
pub fn derive_json(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
75
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
76
|
+
|
|
77
|
+
match &input.data {
|
|
78
|
+
Data::Class(class) => {
|
|
79
|
+
Ok(body! {
|
|
80
|
+
toJSON(): Record<string, unknown> {
|
|
81
|
+
return {
|
|
82
|
+
{#for field in class.field_names()}
|
|
83
|
+
@{field}: this.@{field},
|
|
84
|
+
{/for}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
_ => Err(MacroforgeError::new(
|
|
90
|
+
input.decorator_span(),
|
|
91
|
+
"@derive(JSON) only works on classes",
|
|
92
|
+
)),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Create package.json
|
|
98
|
+
|
|
99
|
+
`package.json`
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"name": "@my-org/macros",
|
|
103
|
+
"version": "0.1.0",
|
|
104
|
+
"main": "index.js",
|
|
105
|
+
"types": "index.d.ts",
|
|
106
|
+
"napi": {
|
|
107
|
+
"name": "my-macros",
|
|
108
|
+
"triples": {
|
|
109
|
+
"defaults": true
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"files": [
|
|
113
|
+
"index.js",
|
|
114
|
+
"index.d.ts",
|
|
115
|
+
"*.node"
|
|
116
|
+
],
|
|
117
|
+
"scripts": {
|
|
118
|
+
"build": "napi build --release",
|
|
119
|
+
"prepublishOnly": "napi build --release"
|
|
120
|
+
},
|
|
121
|
+
"devDependencies": {
|
|
122
|
+
"@napi-rs/cli": "^3.0.0-alpha.0"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Build the Package
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Build the native addon
|
|
131
|
+
npm run build
|
|
132
|
+
|
|
133
|
+
# This creates:
|
|
134
|
+
# - index.js (JavaScript bindings)
|
|
135
|
+
# - index.d.ts (TypeScript types)
|
|
136
|
+
# - *.node (native binary)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
>
|
|
140
|
+
> For cross-platform builds, use GitHub Actions with the NAPI-RS CI template.
|
|
141
|
+
|
|
142
|
+
## Next Steps
|
|
143
|
+
|
|
144
|
+
- [Learn the ts_macro_derive attribute]({base}/docs/custom-macros/ts-macro-derive)
|
|
145
|
+
|
|
146
|
+
- [Master the ts_quote template syntax]({base}/docs/custom-macros/ts-quote)
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# ts_macro_derive
|
|
2
|
+
|
|
3
|
+
*The `#[ts_macro_derive]` attribute is a Rust procedural macro that registers your function as a Macroforge derive macro.*
|
|
4
|
+
|
|
5
|
+
## Basic Syntax
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
use macroforge_ts::ts_macro_derive::ts_macro_derive;
|
|
9
|
+
use macroforge_ts::ts_syn::{TsStream, MacroforgeError};
|
|
10
|
+
|
|
11
|
+
#[ts_macro_derive(MacroName)]
|
|
12
|
+
pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
13
|
+
// Macro implementation
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Attribute Options
|
|
18
|
+
|
|
19
|
+
### Name (Required)
|
|
20
|
+
|
|
21
|
+
The first argument is the macro name that users will reference in `@derive()`:
|
|
22
|
+
|
|
23
|
+
```rust
|
|
24
|
+
#[ts_macro_derive(JSON)] // Users write: @derive(JSON)
|
|
25
|
+
pub fn derive_json(...)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Description
|
|
29
|
+
|
|
30
|
+
Provides documentation for the macro:
|
|
31
|
+
|
|
32
|
+
```rust
|
|
33
|
+
#[ts_macro_derive(
|
|
34
|
+
JSON,
|
|
35
|
+
description = "Generates toJSON() returning a plain object"
|
|
36
|
+
)]
|
|
37
|
+
pub fn derive_json(...)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Attributes
|
|
41
|
+
|
|
42
|
+
Declare which field-level decorators your macro accepts:
|
|
43
|
+
|
|
44
|
+
```rust
|
|
45
|
+
#[ts_macro_derive(
|
|
46
|
+
Debug,
|
|
47
|
+
description = "Generates toString()",
|
|
48
|
+
attributes(debug) // Allows @debug({ ... }) on fields
|
|
49
|
+
)]
|
|
50
|
+
pub fn derive_debug(...)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
>
|
|
54
|
+
> Declared attributes become available as `@attributeName({ options })` decorators in TypeScript.
|
|
55
|
+
|
|
56
|
+
## Function Signature
|
|
57
|
+
|
|
58
|
+
```rust
|
|
59
|
+
pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| `input: TsStream`
|
|
63
|
+
| Token stream containing the class/interface AST
|
|
64
|
+
|
|
65
|
+
| `Result<TsStream, MacroforgeError>`
|
|
66
|
+
| Returns generated code or an error with source location
|
|
67
|
+
|
|
68
|
+
## Parsing Input
|
|
69
|
+
|
|
70
|
+
Use `parse_ts_macro_input!` to convert the token stream:
|
|
71
|
+
|
|
72
|
+
```rust
|
|
73
|
+
use macroforge_ts::ts_syn::{DeriveInput, Data, parse_ts_macro_input};
|
|
74
|
+
|
|
75
|
+
#[ts_macro_derive(MyMacro)]
|
|
76
|
+
pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
77
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
78
|
+
|
|
79
|
+
// Access class data
|
|
80
|
+
match &input.data {
|
|
81
|
+
Data::Class(class) => {
|
|
82
|
+
let class_name = input.name();
|
|
83
|
+
let fields = class.fields();
|
|
84
|
+
// ...
|
|
85
|
+
}
|
|
86
|
+
Data::Interface(interface) => {
|
|
87
|
+
// Handle interfaces
|
|
88
|
+
}
|
|
89
|
+
Data::Enum(_) => {
|
|
90
|
+
// Handle enums (if supported)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## DeriveInput Structure
|
|
97
|
+
|
|
98
|
+
```rust
|
|
99
|
+
struct DeriveInput {
|
|
100
|
+
pub ident: Ident, // The type name
|
|
101
|
+
pub span: SpanIR, // Span of the type definition
|
|
102
|
+
pub attrs: Vec<Attribute>, // Decorators (excluding @derive)
|
|
103
|
+
pub data: Data, // The parsed type data
|
|
104
|
+
pub context: MacroContextIR, // Macro context with spans
|
|
105
|
+
|
|
106
|
+
// Helper methods
|
|
107
|
+
fn name(&self) -> &str; // Get the type name
|
|
108
|
+
fn decorator_span(&self) -> SpanIR; // Span of @derive decorator
|
|
109
|
+
fn as_class(&self) -> Option<&DataClass>;
|
|
110
|
+
fn as_interface(&self) -> Option<&DataInterface>;
|
|
111
|
+
fn as_enum(&self) -> Option<&DataEnum>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
enum Data {
|
|
115
|
+
Class(DataClass),
|
|
116
|
+
Interface(DataInterface),
|
|
117
|
+
Enum(DataEnum),
|
|
118
|
+
TypeAlias(DataTypeAlias),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl DataClass {
|
|
122
|
+
fn fields(&self) -> &[FieldIR];
|
|
123
|
+
fn methods(&self) -> &[MethodSigIR];
|
|
124
|
+
fn field_names(&self) -> impl Iterator<Item = &str>;
|
|
125
|
+
fn field(&self, name: &str) -> Option<&FieldIR>;
|
|
126
|
+
fn body_span(&self) -> SpanIR; // For inserting code into class body
|
|
127
|
+
fn type_params(&self) -> &[String]; // Generic type parameters
|
|
128
|
+
fn heritage(&self) -> &[String]; // extends/implements clauses
|
|
129
|
+
fn is_abstract(&self) -> bool;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
impl DataInterface {
|
|
133
|
+
fn fields(&self) -> &[InterfaceFieldIR];
|
|
134
|
+
fn methods(&self) -> &[InterfaceMethodIR];
|
|
135
|
+
fn field_names(&self) -> impl Iterator<Item = &str>;
|
|
136
|
+
fn field(&self, name: &str) -> Option<&InterfaceFieldIR>;
|
|
137
|
+
fn body_span(&self) -> SpanIR;
|
|
138
|
+
fn type_params(&self) -> &[String];
|
|
139
|
+
fn heritage(&self) -> &[String]; // extends clauses
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
impl DataEnum {
|
|
143
|
+
fn variants(&self) -> &[EnumVariantIR];
|
|
144
|
+
fn variant_names(&self) -> impl Iterator<Item = &str>;
|
|
145
|
+
fn variant(&self, name: &str) -> Option<&EnumVariantIR>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
impl DataTypeAlias {
|
|
149
|
+
fn body(&self) -> &TypeBody;
|
|
150
|
+
fn type_params(&self) -> &[String];
|
|
151
|
+
fn is_union(&self) -> bool;
|
|
152
|
+
fn is_object(&self) -> bool;
|
|
153
|
+
fn as_union(&self) -> Option<&[TypeMember]>;
|
|
154
|
+
fn as_object(&self) -> Option<&[InterfaceFieldIR]>;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Accessing Field Data
|
|
159
|
+
|
|
160
|
+
### Class Fields (FieldIR)
|
|
161
|
+
|
|
162
|
+
```rust
|
|
163
|
+
struct FieldIR {
|
|
164
|
+
pub name: String, // Field name
|
|
165
|
+
pub span: SpanIR, // Field span
|
|
166
|
+
pub ts_type: String, // TypeScript type annotation
|
|
167
|
+
pub optional: bool, // Whether field has ?
|
|
168
|
+
pub readonly: bool, // Whether field is readonly
|
|
169
|
+
pub visibility: Visibility, // Public, Protected, Private
|
|
170
|
+
pub decorators: Vec<DecoratorIR>, // Field decorators
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Interface Fields (InterfaceFieldIR)
|
|
175
|
+
|
|
176
|
+
```rust
|
|
177
|
+
struct InterfaceFieldIR {
|
|
178
|
+
pub name: String,
|
|
179
|
+
pub span: SpanIR,
|
|
180
|
+
pub ts_type: String,
|
|
181
|
+
pub optional: bool,
|
|
182
|
+
pub readonly: bool,
|
|
183
|
+
pub decorators: Vec<DecoratorIR>,
|
|
184
|
+
// Note: No visibility field (interfaces are always public)
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Enum Variants (EnumVariantIR)
|
|
189
|
+
|
|
190
|
+
```rust
|
|
191
|
+
struct EnumVariantIR {
|
|
192
|
+
pub name: String,
|
|
193
|
+
pub span: SpanIR,
|
|
194
|
+
pub value: EnumValue, // Auto, String(String), or Number(f64)
|
|
195
|
+
pub decorators: Vec<DecoratorIR>,
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Decorator Structure
|
|
200
|
+
|
|
201
|
+
```rust
|
|
202
|
+
struct DecoratorIR {
|
|
203
|
+
pub name: String, // e.g., "serde"
|
|
204
|
+
pub args_src: String, // Raw args text, e.g., "skip, rename: 'id'"
|
|
205
|
+
pub span: SpanIR,
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
>
|
|
210
|
+
> To check for decorators, iterate through `field.decorators` and check `decorator.name`. For parsing options, you can write helper functions like the built-in macros do.
|
|
211
|
+
|
|
212
|
+
## Adding Imports
|
|
213
|
+
|
|
214
|
+
If your macro generates code that requires imports, use the `add_import` method on `TsStream`:
|
|
215
|
+
|
|
216
|
+
```rust
|
|
217
|
+
// Add an import to be inserted at the top of the file
|
|
218
|
+
let mut output = body! {
|
|
219
|
+
validate(): ValidationResult {
|
|
220
|
+
return validateFields(this);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// This will add: import { validateFields, ValidationResult } from "my-validation-lib";
|
|
225
|
+
output.add_import("validateFields", "my-validation-lib");
|
|
226
|
+
output.add_import("ValidationResult", "my-validation-lib");
|
|
227
|
+
|
|
228
|
+
Ok(output)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
>
|
|
232
|
+
> Imports are automatically deduplicated. If the same import already exists in the file, it won't be added again.
|
|
233
|
+
|
|
234
|
+
## Returning Errors
|
|
235
|
+
|
|
236
|
+
Use `MacroforgeError` to report errors with source locations:
|
|
237
|
+
|
|
238
|
+
```rust
|
|
239
|
+
#[ts_macro_derive(ClassOnly)]
|
|
240
|
+
pub fn class_only(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
241
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
242
|
+
|
|
243
|
+
match &input.data {
|
|
244
|
+
Data::Class(_) => {
|
|
245
|
+
// Generate code...
|
|
246
|
+
Ok(body! { /* ... */ })
|
|
247
|
+
}
|
|
248
|
+
_ => Err(MacroforgeError::new(
|
|
249
|
+
input.decorator_span(),
|
|
250
|
+
"@derive(ClassOnly) can only be used on classes",
|
|
251
|
+
)),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Complete Example
|
|
257
|
+
|
|
258
|
+
```rust
|
|
259
|
+
use macroforge_ts::ts_macro_derive::ts_macro_derive;
|
|
260
|
+
use macroforge_ts::ts_quote::body;
|
|
261
|
+
use macroforge_ts::ts_syn::{
|
|
262
|
+
Data, DeriveInput, FieldIR, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Helper function to check if a field has a decorator
|
|
266
|
+
fn has_decorator(field: &FieldIR, name: &str) -> bool {
|
|
267
|
+
field.decorators.iter().any(|d| d.name.eq_ignore_ascii_case(name))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#[ts_macro_derive(
|
|
271
|
+
Validate,
|
|
272
|
+
description = "Generates a validate() method",
|
|
273
|
+
attributes(validate)
|
|
274
|
+
)]
|
|
275
|
+
pub fn derive_validate(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
276
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
277
|
+
|
|
278
|
+
match &input.data {
|
|
279
|
+
Data::Class(class) => {
|
|
280
|
+
let validations: Vec<_> = class.fields()
|
|
281
|
+
.iter()
|
|
282
|
+
.filter(|f| has_decorator(f, "validate"))
|
|
283
|
+
.collect();
|
|
284
|
+
|
|
285
|
+
Ok(body! {
|
|
286
|
+
validate(): string[] {
|
|
287
|
+
const errors: string[] = [];
|
|
288
|
+
{#for field in validations}
|
|
289
|
+
if (!this.@{field.name}) {
|
|
290
|
+
errors.push("@{field.name} is required");
|
|
291
|
+
}
|
|
292
|
+
{/for}
|
|
293
|
+
return errors;
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
_ => Err(MacroforgeError::new(
|
|
298
|
+
input.decorator_span(),
|
|
299
|
+
"@derive(Validate) only works on classes",
|
|
300
|
+
)),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Next Steps
|
|
306
|
+
|
|
307
|
+
- [Learn the ts_quote template syntax]({base}/docs/custom-macros/ts-quote)
|