@macroforge/mcp-server 0.1.37 → 0.1.39
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 +13 -13
- package/docs/api/expand-sync.md +8 -8
- package/docs/api/native-plugin.md +15 -15
- package/docs/api/position-mapper.md +6 -6
- package/docs/api/transform-sync.md +11 -11
- package/docs/builtin-macros/clone.md +43 -23
- package/docs/builtin-macros/debug.md +50 -18
- package/docs/builtin-macros/default.md +79 -28
- package/docs/builtin-macros/deserialize/cycleforward-reference-support.md +11 -0
- package/docs/builtin-macros/deserialize/example.md +1625 -0
- package/docs/builtin-macros/deserialize/overview.md +15 -10
- package/docs/builtin-macros/deserialize/union-type-deserialization.md +27 -0
- package/docs/builtin-macros/deserialize/validation.md +34 -0
- package/docs/builtin-macros/deserialize.md +1608 -23
- package/docs/builtin-macros/hash.md +87 -20
- package/docs/builtin-macros/macros-overview.md +40 -40
- package/docs/builtin-macros/ord.md +56 -31
- package/docs/builtin-macros/partial-eq/example.md +526 -0
- package/docs/builtin-macros/partial-eq/overview.md +39 -0
- package/docs/builtin-macros/partial-eq.md +184 -26
- package/docs/builtin-macros/partial-ord.md +68 -30
- package/docs/builtin-macros/serialize/example.md +139 -0
- package/docs/builtin-macros/serialize/overview.md +32 -0
- package/docs/builtin-macros/serialize/type-specific-serialization.md +22 -0
- package/docs/builtin-macros/serialize.md +130 -28
- package/docs/concepts/architecture.md +2 -2
- package/docs/concepts/derive-system.md +25 -39
- package/docs/concepts/how-macros-work.md +8 -4
- package/docs/custom-macros/custom-overview.md +23 -23
- package/docs/custom-macros/rust-setup.md +31 -31
- package/docs/custom-macros/ts-macro-derive.md +107 -107
- package/docs/custom-macros/ts-quote.md +226 -226
- package/docs/getting-started/first-macro.md +38 -28
- package/docs/getting-started/installation.md +15 -15
- package/docs/integration/cli.md +9 -9
- package/docs/integration/configuration.md +16 -16
- package/docs/integration/mcp-server.md +6 -6
- package/docs/integration/svelte-preprocessor.md +40 -41
- package/docs/integration/typescript-plugin.md +13 -12
- package/docs/integration/vite-plugin.md +12 -12
- package/docs/language-servers/zed.md +1 -1
- package/docs/sections.json +88 -2
- package/package.json +2 -2
|
@@ -34,55 +34,55 @@
|
|
|
34
34
|
let class_name = "User";
|
|
35
35
|
let method = "toString";
|
|
36
36
|
|
|
37
|
-
let code = ts_template!
|
|
38
|
-
|
|
37
|
+
let code = ts_template! {
|
|
38
|
+
@{class_name}.prototype.@{method} = function() {
|
|
39
39
|
return "User instance";
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
42
|
``` **Generates:**
|
|
43
43
|
```
|
|
44
|
-
User.prototype.toString = function ()
|
|
44
|
+
User.prototype.toString = function () {
|
|
45
45
|
return "User instance";
|
|
46
|
-
|
|
46
|
+
};
|
|
47
47
|
``` ## Identifier Concatenation: `{| content |}`
|
|
48
48
|
When you need to build identifiers dynamically (like `getUser`, `setName`), use the ident block syntax. Everything inside `{| |}` is concatenated without spaces:
|
|
49
49
|
```
|
|
50
50
|
let field_name = "User";
|
|
51
51
|
|
|
52
|
-
let code = ts_template!
|
|
53
|
-
function
|
|
54
|
-
return this
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
let code = ts_template! {
|
|
53
|
+
function {|get@{field_name}|}() {
|
|
54
|
+
return this.@{field_name.to_lowercase()};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
57
|
``` **Generates:**
|
|
58
58
|
```
|
|
59
|
-
function getUser()
|
|
59
|
+
function getUser() {
|
|
60
60
|
return this.user;
|
|
61
|
-
|
|
61
|
+
}
|
|
62
62
|
``` Without ident blocks, `@{}` always adds a space after for readability. Use `{| |}` when you explicitly want concatenation:
|
|
63
63
|
```
|
|
64
64
|
let name = "Status";
|
|
65
65
|
|
|
66
66
|
// With space (default behavior)
|
|
67
|
-
ts_template!
|
|
67
|
+
ts_template! { namespace @{name} } // → "namespace Status"
|
|
68
68
|
|
|
69
69
|
// Without space (ident block)
|
|
70
|
-
ts_template!
|
|
70
|
+
ts_template! { {|namespace@{name}|} } // → "namespaceStatus"
|
|
71
71
|
``` Multiple interpolations can be combined:
|
|
72
72
|
```
|
|
73
73
|
let entity = "user";
|
|
74
74
|
let action = "create";
|
|
75
75
|
|
|
76
|
-
ts_template!
|
|
76
|
+
ts_template! { {|@{entity}_@{action}|} } // → "user_create"
|
|
77
77
|
``` ## Comments: `{> "..." <}` and `{>> "..." <<}`
|
|
78
78
|
Since Rust's tokenizer strips whitespace before macros see them, use string literals to preserve exact spacing in comments:
|
|
79
79
|
### Block Comments
|
|
80
80
|
Use `{> "comment" <}` for block comments:
|
|
81
81
|
```
|
|
82
|
-
let code = ts_template!
|
|
83
|
-
|
|
82
|
+
let code = ts_template! {
|
|
83
|
+
{> "This is a block comment" <}
|
|
84
84
|
const x = 42;
|
|
85
|
-
|
|
85
|
+
};
|
|
86
86
|
``` **Generates:**
|
|
87
87
|
```
|
|
88
88
|
/* This is a block comment */
|
|
@@ -90,45 +90,45 @@ const x = 42;
|
|
|
90
90
|
``` ### Doc Comments (JSDoc)
|
|
91
91
|
Use `{>> "doc" <<}` for JSDoc comments:
|
|
92
92
|
```
|
|
93
|
-
let code = ts_template!
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
function greet(name: string): string
|
|
93
|
+
let code = ts_template! {
|
|
94
|
+
{>> "@param {string} name - The user's name" <<}
|
|
95
|
+
{>> "@returns {string} A greeting message" <<}
|
|
96
|
+
function greet(name: string): string {
|
|
97
97
|
return "Hello, " + name;
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
100
|
``` **Generates:**
|
|
101
101
|
```
|
|
102
|
-
/** @param
|
|
103
|
-
/** @returns
|
|
104
|
-
function greet(name: string): string
|
|
102
|
+
/** @param {string} name - The user's name */
|
|
103
|
+
/** @returns {string} A greeting message */
|
|
104
|
+
function greet(name: string): string {
|
|
105
105
|
return "Hello, " + name;
|
|
106
|
-
|
|
106
|
+
}
|
|
107
107
|
``` ### Comments with Interpolation
|
|
108
108
|
Use `format!()` or similar to build dynamic comment strings:
|
|
109
109
|
```
|
|
110
110
|
let param_name = "userId";
|
|
111
111
|
let param_type = "number";
|
|
112
|
-
let comment = format!("@param
|
|
112
|
+
let comment = format!("@param {{{}}} {} - The user ID", param_type, param_name);
|
|
113
113
|
|
|
114
|
-
let code = ts_template!
|
|
115
|
-
|
|
116
|
-
function getUser(userId: number)
|
|
117
|
-
|
|
114
|
+
let code = ts_template! {
|
|
115
|
+
{>> @{comment} <<}
|
|
116
|
+
function getUser(userId: number) {}
|
|
117
|
+
};
|
|
118
118
|
``` **Generates:**
|
|
119
119
|
```
|
|
120
|
-
/** @param
|
|
121
|
-
function getUser(userId: number)
|
|
120
|
+
/** @param {number} userId - The user ID */
|
|
121
|
+
function getUser(userId: number) {}
|
|
122
122
|
``` ## String Interpolation: `"text @{expr}"`
|
|
123
123
|
Interpolation works automatically inside string literals - no `format!()` needed:
|
|
124
124
|
```
|
|
125
125
|
let name = "World";
|
|
126
126
|
let count = 42;
|
|
127
127
|
|
|
128
|
-
let code = ts_template!
|
|
129
|
-
console.log("Hello
|
|
130
|
-
console.log("Count:
|
|
131
|
-
|
|
128
|
+
let code = ts_template! {
|
|
129
|
+
console.log("Hello @{name}!");
|
|
130
|
+
console.log("Count: @{count}, doubled: @{count * 2}");
|
|
131
|
+
};
|
|
132
132
|
``` **Generates:**
|
|
133
133
|
```
|
|
134
134
|
console.log("Hello World!");
|
|
@@ -137,9 +137,9 @@ console.log("Count: 42, doubled: 84");
|
|
|
137
137
|
```
|
|
138
138
|
let field = "username";
|
|
139
139
|
|
|
140
|
-
let code = ts_template!
|
|
141
|
-
throw new Error("Invalid
|
|
142
|
-
|
|
140
|
+
let code = ts_template! {
|
|
141
|
+
throw new Error("Invalid @{field.to_uppercase()}");
|
|
142
|
+
};
|
|
143
143
|
``` ## Backtick Template Literals: `"'^...^'"`
|
|
144
144
|
For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through `$${}` for JS interpolation:
|
|
145
145
|
```
|
|
@@ -160,145 +160,145 @@ let code = ts_template! {
|
|
|
160
160
|
};
|
|
161
161
|
``` **Generates:**
|
|
162
162
|
```
|
|
163
|
-
`Hello
|
|
163
|
+
`Hello ${this.name}, you are a User`
|
|
164
164
|
``` ## Conditionals: `{#if}...{/if}`
|
|
165
165
|
Basic conditional:
|
|
166
166
|
```
|
|
167
167
|
let needs_validation = true;
|
|
168
168
|
|
|
169
|
-
let code = ts_template!
|
|
170
|
-
function save()
|
|
171
|
-
|
|
169
|
+
let code = ts_template! {
|
|
170
|
+
function save() {
|
|
171
|
+
{#if needs_validation}
|
|
172
172
|
if (!this.isValid()) return false;
|
|
173
|
-
|
|
173
|
+
{/if}
|
|
174
174
|
return this.doSave();
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
177
|
``` ### If-Else
|
|
178
178
|
```
|
|
179
179
|
let has_default = true;
|
|
180
180
|
|
|
181
|
-
let code = ts_template!
|
|
182
|
-
|
|
181
|
+
let code = ts_template! {
|
|
182
|
+
{#if has_default}
|
|
183
183
|
return defaultValue;
|
|
184
|
-
|
|
184
|
+
{:else}
|
|
185
185
|
throw new Error("No default");
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
{/if}
|
|
187
|
+
};
|
|
188
188
|
``` ### If-Else-If Chains
|
|
189
189
|
```
|
|
190
190
|
let level = 2;
|
|
191
191
|
|
|
192
|
-
let code = ts_template!
|
|
193
|
-
|
|
192
|
+
let code = ts_template! {
|
|
193
|
+
{#if level == 1}
|
|
194
194
|
console.log("Level 1");
|
|
195
|
-
|
|
195
|
+
{:else if level == 2}
|
|
196
196
|
console.log("Level 2");
|
|
197
|
-
|
|
197
|
+
{:else}
|
|
198
198
|
console.log("Other level");
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
{/if}
|
|
200
|
+
};
|
|
201
201
|
``` ## Pattern Matching: `{#if let}`
|
|
202
202
|
Use `if let` for pattern matching on `Option`, `Result`, or other Rust enums:
|
|
203
203
|
```
|
|
204
|
-
let maybe_name: Option
|
|
204
|
+
let maybe_name: Option<&str> = Some("Alice");
|
|
205
205
|
|
|
206
|
-
let code = ts_template!
|
|
207
|
-
|
|
208
|
-
console.log("Hello,
|
|
209
|
-
|
|
206
|
+
let code = ts_template! {
|
|
207
|
+
{#if let Some(name) = maybe_name}
|
|
208
|
+
console.log("Hello, @{name}!");
|
|
209
|
+
{:else}
|
|
210
210
|
console.log("Hello, anonymous!");
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
{/if}
|
|
212
|
+
};
|
|
213
213
|
``` **Generates:**
|
|
214
214
|
```
|
|
215
215
|
console.log("Hello, Alice!");
|
|
216
216
|
``` This is useful when working with optional values from your IR:
|
|
217
217
|
```
|
|
218
|
-
let code = ts_template!
|
|
219
|
-
|
|
220
|
-
this
|
|
221
|
-
|
|
222
|
-
this
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
let code = ts_template! {
|
|
219
|
+
{#if let Some(default_val) = field.default_value}
|
|
220
|
+
this.@{field.name} = @{default_val};
|
|
221
|
+
{:else}
|
|
222
|
+
this.@{field.name} = undefined;
|
|
223
|
+
{/if}
|
|
224
|
+
};
|
|
225
225
|
``` ## Match Expressions: `{#match}`
|
|
226
226
|
Use `match` for exhaustive pattern matching:
|
|
227
227
|
```
|
|
228
|
-
enum Visibility
|
|
228
|
+
enum Visibility { Public, Private, Protected }
|
|
229
229
|
let visibility = Visibility::Public;
|
|
230
230
|
|
|
231
|
-
let code = ts_template!
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
let code = ts_template! {
|
|
232
|
+
{#match visibility}
|
|
233
|
+
{:case Visibility::Public}
|
|
234
234
|
public
|
|
235
|
-
|
|
235
|
+
{:case Visibility::Private}
|
|
236
236
|
private
|
|
237
|
-
|
|
237
|
+
{:case Visibility::Protected}
|
|
238
238
|
protected
|
|
239
|
-
|
|
239
|
+
{/match}
|
|
240
240
|
field: string;
|
|
241
|
-
|
|
241
|
+
};
|
|
242
242
|
``` **Generates:**
|
|
243
243
|
```
|
|
244
244
|
public field: string;
|
|
245
245
|
``` ### Match with Value Extraction
|
|
246
246
|
```
|
|
247
|
-
let result: Result
|
|
247
|
+
let result: Result<i32, &str> = Ok(42);
|
|
248
248
|
|
|
249
|
-
let code = ts_template!
|
|
250
|
-
const value =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
throw new Error("
|
|
255
|
-
|
|
256
|
-
|
|
249
|
+
let code = ts_template! {
|
|
250
|
+
const value = {#match result}
|
|
251
|
+
{:case Ok(val)}
|
|
252
|
+
@{val}
|
|
253
|
+
{:case Err(msg)}
|
|
254
|
+
throw new Error("@{msg}")
|
|
255
|
+
{/match};
|
|
256
|
+
};
|
|
257
257
|
``` ### Match with Wildcard
|
|
258
258
|
```
|
|
259
259
|
let count = 5;
|
|
260
260
|
|
|
261
|
-
let code = ts_template!
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
let code = ts_template! {
|
|
262
|
+
{#match count}
|
|
263
|
+
{:case 0}
|
|
264
264
|
console.log("none");
|
|
265
|
-
|
|
265
|
+
{:case 1}
|
|
266
266
|
console.log("one");
|
|
267
|
-
|
|
267
|
+
{:case _}
|
|
268
268
|
console.log("many");
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
{/match}
|
|
270
|
+
};
|
|
271
271
|
``` ## Iteration: `{#for}`
|
|
272
272
|
```
|
|
273
273
|
let fields = vec!["name", "email", "age"];
|
|
274
274
|
|
|
275
|
-
let code = ts_template!
|
|
276
|
-
function toJSON()
|
|
277
|
-
const result =
|
|
278
|
-
|
|
279
|
-
result
|
|
280
|
-
|
|
275
|
+
let code = ts_template! {
|
|
276
|
+
function toJSON() {
|
|
277
|
+
const result = {};
|
|
278
|
+
{#for field in fields}
|
|
279
|
+
result.@{field} = this.@{field};
|
|
280
|
+
{/for}
|
|
281
281
|
return result;
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
284
|
``` **Generates:**
|
|
285
285
|
```
|
|
286
|
-
function toJSON()
|
|
287
|
-
const result =
|
|
286
|
+
function toJSON() {
|
|
287
|
+
const result = {};
|
|
288
288
|
result.name = this.name;
|
|
289
289
|
result.email = this.email;
|
|
290
290
|
result.age = this.age;
|
|
291
291
|
return result;
|
|
292
|
-
|
|
292
|
+
}
|
|
293
293
|
``` ### Tuple Destructuring in Loops
|
|
294
294
|
```
|
|
295
295
|
let items = vec![("user", "User"), ("post", "Post")];
|
|
296
296
|
|
|
297
|
-
let code = ts_template!
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
297
|
+
let code = ts_template! {
|
|
298
|
+
{#for (key, class_name) in items}
|
|
299
|
+
const @{key} = new @{class_name}();
|
|
300
|
+
{/for}
|
|
301
|
+
};
|
|
302
302
|
``` ### Nested Iterations
|
|
303
303
|
```
|
|
304
304
|
let classes = vec![
|
|
@@ -306,40 +306,40 @@ let classes = vec![
|
|
|
306
306
|
("Post", vec!["title", "content"]),
|
|
307
307
|
];
|
|
308
308
|
|
|
309
|
-
ts_template!
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
309
|
+
ts_template! {
|
|
310
|
+
{#for (class_name, fields) in classes}
|
|
311
|
+
@{class_name}.prototype.toJSON = function() {
|
|
312
|
+
return {
|
|
313
|
+
{#for field in fields}
|
|
314
|
+
@{field}: this.@{field},
|
|
315
|
+
{/for}
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
{/for}
|
|
319
|
+
}
|
|
320
320
|
``` ## While Loops: `{#while}`
|
|
321
321
|
Use `while` for loops that need to continue until a condition is false:
|
|
322
322
|
```
|
|
323
323
|
let items = get_items();
|
|
324
324
|
let mut idx = 0;
|
|
325
325
|
|
|
326
|
-
let code = ts_template!
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
console.log("Item
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
326
|
+
let code = ts_template! {
|
|
327
|
+
{$let mut i = 0}
|
|
328
|
+
{#while i < items.len()}
|
|
329
|
+
console.log("Item @{i}");
|
|
330
|
+
{$do i += 1}
|
|
331
|
+
{/while}
|
|
332
|
+
};
|
|
333
333
|
``` ### While-Let Pattern Matching
|
|
334
334
|
Use `while let` for iterating with pattern matching, similar to `if let`:
|
|
335
335
|
```
|
|
336
336
|
let mut items = vec!["a", "b", "c"].into_iter();
|
|
337
337
|
|
|
338
|
-
let code = ts_template!
|
|
339
|
-
|
|
340
|
-
console.log("
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
let code = ts_template! {
|
|
339
|
+
{#while let Some(item) = items.next()}
|
|
340
|
+
console.log("@{item}");
|
|
341
|
+
{/while}
|
|
342
|
+
};
|
|
343
343
|
``` **Generates:**
|
|
344
344
|
```
|
|
345
345
|
console.log("a");
|
|
@@ -347,45 +347,45 @@ console.log("b");
|
|
|
347
347
|
console.log("c");
|
|
348
348
|
``` This is especially useful when working with iterators or consuming optional values:
|
|
349
349
|
```
|
|
350
|
-
let code = ts_template!
|
|
351
|
-
|
|
352
|
-
result
|
|
353
|
-
|
|
354
|
-
|
|
350
|
+
let code = ts_template! {
|
|
351
|
+
{#while let Some(next_field) = remaining_fields.pop()}
|
|
352
|
+
result.@{next_field.name} = this.@{next_field.name};
|
|
353
|
+
{/while}
|
|
354
|
+
};
|
|
355
355
|
``` ## Local Constants: `{$let}`
|
|
356
356
|
Define local variables within the template scope:
|
|
357
357
|
```
|
|
358
358
|
let items = vec![("user", "User"), ("post", "Post")];
|
|
359
359
|
|
|
360
|
-
let code = ts_template!
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
console.log("Processing
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
360
|
+
let code = ts_template! {
|
|
361
|
+
{#for (key, class_name) in items}
|
|
362
|
+
{$let upper = class_name.to_uppercase()}
|
|
363
|
+
console.log("Processing @{upper}");
|
|
364
|
+
const @{key} = new @{class_name}();
|
|
365
|
+
{/for}
|
|
366
|
+
};
|
|
367
367
|
``` This is useful for computing derived values inside loops without cluttering the Rust code.
|
|
368
368
|
## Mutable Variables: `{$let mut}`
|
|
369
369
|
When you need to modify a variable within the template (e.g., in a `while` loop), use `{$let mut}`:
|
|
370
370
|
```
|
|
371
|
-
let code = ts_template!
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
console.log("Item
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
console.log("Total:
|
|
378
|
-
|
|
371
|
+
let code = ts_template! {
|
|
372
|
+
{$let mut count = 0}
|
|
373
|
+
{#for item in items}
|
|
374
|
+
console.log("Item @{count}: @{item}");
|
|
375
|
+
{$do count += 1}
|
|
376
|
+
{/for}
|
|
377
|
+
console.log("Total: @{count}");
|
|
378
|
+
};
|
|
379
379
|
``` ## Side Effects: `{$do}`
|
|
380
380
|
Execute an expression for its side effects without producing output. This is commonly used with mutable variables:
|
|
381
381
|
```
|
|
382
|
-
let code = ts_template!
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return [
|
|
388
|
-
|
|
382
|
+
let code = ts_template! {
|
|
383
|
+
{$let mut results: Vec<String> = Vec::new()}
|
|
384
|
+
{#for field in fields}
|
|
385
|
+
{$do results.push(format!("this.{}", field))}
|
|
386
|
+
{/for}
|
|
387
|
+
return [@{results.join(", ")}];
|
|
388
|
+
};
|
|
389
389
|
``` Common uses for `{$do}`:
|
|
390
390
|
- Incrementing counters: `{$do i += 1}`
|
|
391
391
|
- Building collections: `{$do vec.push(item)}`
|
|
@@ -395,69 +395,69 @@ let code = ts_template! {
|
|
|
395
395
|
Inject another TsStream into your template, preserving both its source code and runtime patches (like imports added via `add_import()`):
|
|
396
396
|
```
|
|
397
397
|
// Create a helper method with its own import
|
|
398
|
-
let mut helper = body!
|
|
399
|
-
validateEmail(email: string): boolean
|
|
398
|
+
let mut helper = body! {
|
|
399
|
+
validateEmail(email: string): boolean {
|
|
400
400
|
return Result.ok(true);
|
|
401
|
-
|
|
402
|
-
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
403
|
helper.add_import("Result", "macroforge/utils");
|
|
404
404
|
|
|
405
405
|
// Inject the helper into the main template
|
|
406
|
-
let result = body!
|
|
407
|
-
|
|
406
|
+
let result = body! {
|
|
407
|
+
{$typescript helper}
|
|
408
408
|
|
|
409
|
-
process(data: Record
|
|
409
|
+
process(data: Record<string, unknown>): void {
|
|
410
410
|
// ...
|
|
411
|
-
|
|
412
|
-
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
413
|
// result now includes helper's source AND its Result import
|
|
414
414
|
``` This is essential for composing multiple macro outputs while preserving imports and patches:
|
|
415
415
|
```
|
|
416
|
-
let extra_methods = if include_validation
|
|
417
|
-
Some(body!
|
|
418
|
-
validate(): boolean
|
|
419
|
-
|
|
420
|
-
|
|
416
|
+
let extra_methods = if include_validation {
|
|
417
|
+
Some(body! {
|
|
418
|
+
validate(): boolean { return true; }
|
|
419
|
+
})
|
|
420
|
+
} else {
|
|
421
421
|
None
|
|
422
|
-
|
|
422
|
+
};
|
|
423
423
|
|
|
424
|
-
body!
|
|
425
|
-
mainMethod(): void
|
|
424
|
+
body! {
|
|
425
|
+
mainMethod(): void {}
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
{#if let Some(methods) = extra_methods}
|
|
428
|
+
{$typescript methods}
|
|
429
|
+
{/if}
|
|
430
|
+
}
|
|
431
431
|
``` ## Escape Syntax
|
|
432
432
|
If you need a literal `@{` in your output (not interpolation), use `@@{`:
|
|
433
433
|
```
|
|
434
|
-
ts_template!
|
|
435
|
-
// This outputs a literal
|
|
436
|
-
const example = "Use
|
|
437
|
-
|
|
434
|
+
ts_template! {
|
|
435
|
+
// This outputs a literal @{foo}
|
|
436
|
+
const example = "Use @@{foo} for templates";
|
|
437
|
+
}
|
|
438
438
|
``` **Generates:**
|
|
439
439
|
```
|
|
440
|
-
// This outputs a literal
|
|
441
|
-
const example = "Use
|
|
440
|
+
// This outputs a literal @{foo}
|
|
441
|
+
const example = "Use @{foo} for templates";
|
|
442
442
|
``` ## Complete Example: JSON Derive Macro
|
|
443
443
|
Here's a comparison showing how `ts_template!` simplifies code generation:
|
|
444
444
|
### Before (Manual AST Building)
|
|
445
445
|
```
|
|
446
|
-
pub fn derive_json_macro(input: TsStream) -> MacroResult
|
|
446
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
447
447
|
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
448
448
|
|
|
449
|
-
match
|
|
450
|
-
Data::Class(class) =>
|
|
449
|
+
match &input.data {
|
|
450
|
+
Data::Class(class) => {
|
|
451
451
|
let class_name = input.name();
|
|
452
452
|
|
|
453
|
-
let mut body_stmts = vec![ts_quote!( const result =
|
|
453
|
+
let mut body_stmts = vec![ts_quote!( const result = {}; as Stmt )];
|
|
454
454
|
|
|
455
|
-
for field_name in class.field_names()
|
|
455
|
+
for field_name in class.field_names() {
|
|
456
456
|
body_stmts.push(ts_quote!(
|
|
457
|
-
result.$(ident!("
|
|
457
|
+
result.$(ident!("{}", field_name)) = this.$(ident!("{}", field_name));
|
|
458
458
|
as Stmt
|
|
459
459
|
));
|
|
460
|
-
|
|
460
|
+
}
|
|
461
461
|
|
|
462
462
|
body_stmts.push(ts_quote!( return result; as Stmt ));
|
|
463
463
|
|
|
@@ -468,33 +468,33 @@ pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
|
468
468
|
);
|
|
469
469
|
|
|
470
470
|
// ...
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
474
|
``` ### After (With ts_template!)
|
|
475
475
|
```
|
|
476
|
-
pub fn derive_json_macro(input: TsStream) -> MacroResult
|
|
476
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
477
477
|
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
478
478
|
|
|
479
|
-
match
|
|
480
|
-
Data::Class(class) =>
|
|
479
|
+
match &input.data {
|
|
480
|
+
Data::Class(class) => {
|
|
481
481
|
let class_name = input.name();
|
|
482
482
|
let fields = class.field_names();
|
|
483
483
|
|
|
484
|
-
let runtime_code = ts_template!
|
|
485
|
-
|
|
486
|
-
const result =
|
|
487
|
-
|
|
488
|
-
result
|
|
489
|
-
|
|
484
|
+
let runtime_code = ts_template! {
|
|
485
|
+
@{class_name}.prototype.toJSON = function() {
|
|
486
|
+
const result = {};
|
|
487
|
+
{#for field in fields}
|
|
488
|
+
result.@{field} = this.@{field};
|
|
489
|
+
{/for}
|
|
490
490
|
return result;
|
|
491
|
-
|
|
492
|
-
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
493
|
|
|
494
494
|
// ...
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
498
|
``` ## How It Works
|
|
499
499
|
1. **Compile-Time:** The template is parsed during macro expansion
|
|
500
500
|
2. **String Building:** Generates Rust code that builds a TypeScript string at runtime
|
|
@@ -504,25 +504,25 @@ pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
|
504
504
|
`ts_template!` returns a `Result<Stmt, TsSynError>` by default. The macro automatically unwraps and provides helpful error messages showing the generated TypeScript code if parsing fails:
|
|
505
505
|
```
|
|
506
506
|
Failed to parse generated TypeScript:
|
|
507
|
-
User.prototype.toJSON = function(
|
|
508
|
-
return
|
|
509
|
-
|
|
507
|
+
User.prototype.toJSON = function( {
|
|
508
|
+
return {};
|
|
509
|
+
}
|
|
510
510
|
``` This shows you exactly what was generated, making debugging easy!
|
|
511
511
|
## Nesting and Regular TypeScript
|
|
512
512
|
You can mix template syntax with regular TypeScript. Braces `{}` are recognized as either:
|
|
513
513
|
- **Template tags** if they start with `#`, `$`, `:`, or `/`
|
|
514
514
|
- **Regular TypeScript blocks** otherwise
|
|
515
515
|
```
|
|
516
|
-
ts_template!
|
|
517
|
-
const config =
|
|
518
|
-
|
|
516
|
+
ts_template! {
|
|
517
|
+
const config = {
|
|
518
|
+
{#if use_strict}
|
|
519
519
|
strict: true,
|
|
520
|
-
|
|
520
|
+
{:else}
|
|
521
521
|
strict: false,
|
|
522
|
-
|
|
522
|
+
{/if}
|
|
523
523
|
timeout: 5000
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
526
|
``` ## Comparison with Alternatives
|
|
527
527
|
| Approach | Pros | Cons |
|
|
528
528
|
| --- | --- | --- |
|