@macroforge/mcp-server 0.1.42 → 0.1.50
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/LICENSE +22 -0
- package/docs/BOOK.md +165 -0
- package/docs/api/api-overview.md +67 -48
- package/docs/api/expand-sync.md +88 -53
- package/docs/api/native-plugin.md +121 -71
- package/docs/api/position-mapper.md +115 -55
- package/docs/api/transform-sync.md +86 -60
- package/docs/builtin-macros/clone.md +0 -20
- package/docs/builtin-macros/debug.md +0 -23
- package/docs/builtin-macros/default.md +1 -40
- package/docs/builtin-macros/deserialize/example.md +8 -1416
- package/docs/builtin-macros/deserialize.md +8 -1416
- package/docs/builtin-macros/hash.md +0 -42
- package/docs/builtin-macros/macros-overview/detailed-documentation.md +13 -0
- package/docs/builtin-macros/macros-overview/enum-support.md +30 -0
- package/docs/builtin-macros/macros-overview/interface-support.md +28 -0
- package/docs/builtin-macros/macros-overview/overview.md +36 -0
- package/docs/builtin-macros/macros-overview/type-alias-support.md +62 -0
- package/docs/builtin-macros/macros-overview.md +171 -130
- package/docs/builtin-macros/ord.md +0 -25
- package/docs/builtin-macros/partial-eq.md +0 -84
- package/docs/builtin-macros/partial-ord.md +2 -32
- package/docs/builtin-macros/serialize.md +2 -62
- package/docs/concepts/architecture.md +125 -48
- package/docs/concepts/derive-system/built-in-vs-custom-macros.md +13 -0
- package/docs/concepts/derive-system/overview.md +200 -0
- package/docs/concepts/derive-system.md +157 -104
- package/docs/concepts/how-macros-work.md +98 -47
- package/docs/custom-macros/custom-overview.md +79 -57
- package/docs/custom-macros/rust-setup.md +138 -99
- package/docs/custom-macros/ts-macro-derive/accessing-field-data.md +40 -31
- package/docs/custom-macros/ts-macro-derive/adding-imports.md +14 -11
- package/docs/custom-macros/ts-macro-derive/attribute-options.md +20 -25
- package/docs/custom-macros/ts-macro-derive/complete-example.md +40 -38
- package/docs/custom-macros/ts-macro-derive/deriveinput-structure.md +49 -47
- package/docs/custom-macros/ts-macro-derive/function-signature.md +12 -0
- package/docs/custom-macros/ts-macro-derive/overview.md +9 -7
- package/docs/custom-macros/ts-macro-derive/parsing-input.md +20 -18
- package/docs/custom-macros/ts-macro-derive/returning-errors.md +15 -13
- package/docs/custom-macros/ts-macro-derive.md +322 -228
- package/docs/custom-macros/ts-quote/backtick-template-literals.md +19 -7
- package/docs/custom-macros/ts-quote/comments-and.md +56 -22
- package/docs/custom-macros/ts-quote/complete-example-json-derive-macro.md +89 -98
- package/docs/custom-macros/ts-quote/conditionals-ifif.md +35 -29
- package/docs/custom-macros/ts-quote/identifier-concatenation-content.md +30 -22
- package/docs/custom-macros/ts-quote/iteration-for.md +48 -40
- package/docs/custom-macros/ts-quote/local-constants-let.md +23 -21
- package/docs/custom-macros/ts-quote/match-expressions-match.md +46 -38
- package/docs/custom-macros/ts-quote/overview.md +5 -10
- package/docs/custom-macros/ts-quote/pattern-matching-iflet.md +39 -0
- package/docs/custom-macros/ts-quote/quick-reference.md +50 -129
- package/docs/custom-macros/ts-quote/side-effects-do.md +13 -78
- package/docs/custom-macros/ts-quote/string-interpolation-textexpr.md +36 -0
- package/docs/custom-macros/ts-quote/tsstream-injection-typescript.md +43 -35
- package/docs/custom-macros/ts-quote/while-loops-while.md +31 -23
- package/docs/custom-macros/ts-quote.md +800 -520
- package/docs/getting-started/first-macro.md +98 -71
- package/docs/getting-started/installation.md +109 -65
- package/docs/integration/cli.md +214 -105
- package/docs/integration/configuration.md +115 -72
- package/docs/integration/integration-overview.md +55 -18
- package/docs/integration/mcp-server.md +84 -43
- package/docs/integration/svelte-preprocessor.md +183 -126
- package/docs/integration/typescript-plugin.md +101 -53
- package/docs/integration/vite-plugin.md +116 -76
- package/docs/language-servers/ls-overview.md +37 -21
- package/docs/language-servers/svelte.md +69 -38
- package/docs/language-servers/zed.md +81 -44
- package/docs/roadmap/roadmap.md +75 -53
- package/docs/sections.json +1 -242
- package/package.json +27 -28
|
@@ -1,536 +1,816 @@
|
|
|
1
1
|
# Template Syntax
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
| {
|
|
19
|
-
| `{
|
|
20
|
-
| {
|
|
21
|
-
| `{
|
|
22
|
-
| {
|
|
23
|
-
| `{#
|
|
24
|
-
| {#
|
|
25
|
-
| `{#
|
|
26
|
-
| {
|
|
27
|
-
| {
|
|
28
|
-
| {
|
|
29
|
-
| {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
let
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let
|
|
111
|
-
let
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
2
|
+
|
|
3
|
+
The `macroforge_ts_quote` crate provides template-based code generation for TypeScript. The `ts_template!` macro uses Svelte + Rust-inspired syntax for control flow and interpolation, making it easy to generate complex TypeScript code.
|
|
4
|
+
|
|
5
|
+
## Available Macros
|
|
6
|
+
|
|
7
|
+
| Macro | Output | Use Case |
|
|
8
|
+
| -------------- | ------------------- | ----------------------- |
|
|
9
|
+
| `ts_template!` | Any TypeScript code | General code generation |
|
|
10
|
+
| `body!` | Class body members | Methods and properties |
|
|
11
|
+
|
|
12
|
+
## Quick Reference
|
|
13
|
+
|
|
14
|
+
| Syntax | Description |
|
|
15
|
+
| -------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
16
|
+
| `@{expr}` | Interpolate a Rust expression (adds space after) |
|
|
17
|
+
| `{| content |}` | Ident block: concatenates without spaces (e.g., `{|get@{name}|}` → `getUser`) |
|
|
18
|
+
| `{> "comment" <}` | Block comment: outputs `/* comment */` (string preserves whitespace) |
|
|
19
|
+
| `{>> "doc" <<}` | Doc comment: outputs `/** doc */` (string preserves whitespace) |
|
|
20
|
+
| `@@{` | Escape for literal `@{` (e.g., `"@@{foo}"` → `@{foo}`) |
|
|
21
|
+
| `"text @{expr}"` | String interpolation (auto-detected) |
|
|
22
|
+
| `"'^template ${js}^'"` | JS backtick template literal (outputs `` `template ${js}` ``) |
|
|
23
|
+
| `{#if cond}...{/if}` | Conditional block |
|
|
24
|
+
| `{#if cond}...{:else}...{/if}` | Conditional with else |
|
|
25
|
+
| `{#if a}...{:else if b}...{:else}...{/if}` | Full if/else-if/else chain |
|
|
26
|
+
| `{#if let pattern = expr}...{/if}` | Pattern matching if-let |
|
|
27
|
+
| `{#match expr}{:case pattern}...{/match}` | Match expression with case arms |
|
|
28
|
+
| `{#for item in list}...{/for}` | Iterate over a collection |
|
|
29
|
+
| `{#while cond}...{/while}` | While loop |
|
|
30
|
+
| `{#while let pattern = expr}...{/while}` | While-let pattern matching loop |
|
|
31
|
+
| `{$let name = expr}` | Define a local constant |
|
|
32
|
+
| `{$let mut name = expr}` | Define a mutable local variable |
|
|
33
|
+
| `{$do expr}` | Execute a side-effectful expression |
|
|
34
|
+
| `{$typescript stream}` | Inject a TsStream, preserving its source and runtime\_patches (imports) |
|
|
35
|
+
|
|
36
|
+
**Note:** A single `@` not followed by `{` passes through unchanged (e.g., `email@domain.com` works as expected).
|
|
37
|
+
|
|
38
|
+
## Interpolation: `@{expr}`
|
|
39
|
+
|
|
40
|
+
Insert Rust expressions into the generated TypeScript:
|
|
41
|
+
|
|
42
|
+
Rust
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
let class_name = "User";
|
|
46
|
+
let method = "toString";
|
|
47
|
+
|
|
48
|
+
let code = ts_template! {
|
|
49
|
+
@{class_name}.prototype.@{method} = function() {
|
|
50
|
+
return "User instance";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Generates:**
|
|
56
|
+
|
|
57
|
+
TypeScript
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
User.prototype.toString = function () {
|
|
61
|
+
return "User instance";
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Identifier Concatenation: `{| content |}`
|
|
66
|
+
|
|
67
|
+
When you need to build identifiers dynamically (like `getUser`, `setName`), use the ident block syntax. Everything inside `{| |}` is concatenated without spaces:
|
|
68
|
+
|
|
69
|
+
Rust
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
let field_name = "User";
|
|
73
|
+
|
|
74
|
+
let code = ts_template! {
|
|
75
|
+
function {|get@{field_name}|}() {
|
|
76
|
+
return this.@{field_name.to_lowercase()};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Generates:**
|
|
82
|
+
|
|
83
|
+
TypeScript
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
function getUser() {
|
|
87
|
+
return this.user;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Without ident blocks, `@{}` always adds a space after for readability. Use `{| |}` when you explicitly want concatenation:
|
|
92
|
+
|
|
93
|
+
Rust
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
let name = "Status";
|
|
97
|
+
|
|
98
|
+
// With space (default behavior)
|
|
99
|
+
ts_template! { namespace @{name} } // → "namespace Status"
|
|
100
|
+
|
|
101
|
+
// Without space (ident block)
|
|
102
|
+
ts_template! { {|namespace@{name}|} } // → "namespaceStatus"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Multiple interpolations can be combined:
|
|
106
|
+
|
|
107
|
+
Rust
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
let entity = "user";
|
|
111
|
+
let action = "create";
|
|
112
|
+
|
|
113
|
+
ts_template! { {|@{entity}_@{action}|} } // → "user_create"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Comments: `{> "..." <}` and `{>> "..." <<}`
|
|
117
|
+
|
|
118
|
+
Since Rust's tokenizer strips whitespace before macros see them, use string literals to preserve exact spacing in comments:
|
|
119
|
+
|
|
120
|
+
### Block Comments
|
|
121
|
+
|
|
122
|
+
Use `{> "comment" <}` for block comments:
|
|
123
|
+
|
|
124
|
+
Rust
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
let code = ts_template! {
|
|
128
|
+
{> "This is a block comment" <}
|
|
129
|
+
const x = 42;
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Generates:**
|
|
134
|
+
|
|
135
|
+
TypeScript
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
/* This is a block comment */
|
|
139
|
+
const x = 42;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Doc Comments (JSDoc)
|
|
143
|
+
|
|
144
|
+
Use `{>> "doc" <<}` for JSDoc comments:
|
|
145
|
+
|
|
146
|
+
Rust
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
let code = ts_template! {
|
|
150
|
+
{>> "@param {string} name - The user's name" <<}
|
|
151
|
+
{>> "@returns {string} A greeting message" <<}
|
|
152
|
+
function greet(name: string): string {
|
|
153
|
+
return "Hello, " + name;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Generates:**
|
|
159
|
+
|
|
160
|
+
TypeScript
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
/** @param {string} name - The user's name */
|
|
164
|
+
/** @returns {string} A greeting message */
|
|
165
|
+
function greet(name: string): string {
|
|
166
|
+
return "Hello, " + name;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Comments with Interpolation
|
|
171
|
+
|
|
172
|
+
Use `format!()` or similar to build dynamic comment strings:
|
|
173
|
+
|
|
174
|
+
Rust
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
let param_name = "userId";
|
|
178
|
+
let param_type = "number";
|
|
179
|
+
let comment = format!("@param {{{}}} {} - The user ID", param_type, param_name);
|
|
180
|
+
|
|
181
|
+
let code = ts_template! {
|
|
182
|
+
{>> @{comment} <<}
|
|
183
|
+
function getUser(userId: number) {}
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Generates:**
|
|
188
|
+
|
|
189
|
+
TypeScript
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
/** @param {number} userId - The user ID */
|
|
193
|
+
function getUser(userId: number) {}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## String Interpolation: `"text @{expr}"`
|
|
197
|
+
|
|
198
|
+
Interpolation works automatically inside string literals - no `format!()` needed:
|
|
199
|
+
|
|
200
|
+
Rust
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
let name = "World";
|
|
204
|
+
let count = 42;
|
|
205
|
+
|
|
206
|
+
let code = ts_template! {
|
|
207
|
+
console.log("Hello @{name}!");
|
|
208
|
+
console.log("Count: @{count}, doubled: @{count * 2}");
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Generates:**
|
|
213
|
+
|
|
214
|
+
TypeScript
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
console.log("Hello World!");
|
|
218
|
+
console.log("Count: 42, doubled: 84");
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This also works with method calls and complex expressions:
|
|
222
|
+
|
|
223
|
+
Rust
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
let field = "username";
|
|
227
|
+
|
|
228
|
+
let code = ts_template! {
|
|
229
|
+
throw new Error("Invalid @{field.to_uppercase()}");
|
|
230
|
+
};
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Backtick Template Literals: `"'^...^'"`
|
|
234
|
+
|
|
235
|
+
For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through `${"${}"}` for JS interpolation:
|
|
236
|
+
|
|
237
|
+
Rust
|
|
238
|
+
|
|
239
|
+
```
|
|
146
240
|
let tag_name = "div";
|
|
147
241
|
|
|
148
242
|
let code = ts_template! {
|
|
149
243
|
const html = "'^<@{tag_name}>${content}</@{tag_name}>^'";
|
|
150
244
|
};
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Generates:**
|
|
248
|
+
|
|
249
|
+
TypeScript
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
const html = `<div>${content}</div>`;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
You can mix Rust `@{}` interpolation (evaluated at macro expansion time) with JS `${"${}"}` interpolation (evaluated at runtime):
|
|
256
|
+
|
|
257
|
+
Rust
|
|
258
|
+
|
|
259
|
+
```
|
|
156
260
|
let class_name = "User";
|
|
157
261
|
|
|
158
262
|
let code = ts_template! {
|
|
159
263
|
"'^Hello ${this.name}, you are a @{class_name}^'"
|
|
160
264
|
};
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
console.log("
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
&
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Generates:**
|
|
268
|
+
|
|
269
|
+
TypeScript
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
`Hello ${this.name}, you are a User`
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Conditionals: `{#if}...{/if}`
|
|
276
|
+
|
|
277
|
+
Basic conditional:
|
|
278
|
+
|
|
279
|
+
Rust
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
let needs_validation = true;
|
|
283
|
+
|
|
284
|
+
let code = ts_template! {
|
|
285
|
+
function save() {
|
|
286
|
+
{#if needs_validation}
|
|
287
|
+
if (!this.isValid()) return false;
|
|
288
|
+
{/if}
|
|
289
|
+
return this.doSave();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### If-Else
|
|
295
|
+
|
|
296
|
+
Rust
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
let has_default = true;
|
|
300
|
+
|
|
301
|
+
let code = ts_template! {
|
|
302
|
+
{#if has_default}
|
|
303
|
+
return defaultValue;
|
|
304
|
+
{:else}
|
|
305
|
+
throw new Error("No default");
|
|
306
|
+
{/if}
|
|
307
|
+
};
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### If-Else-If Chains
|
|
311
|
+
|
|
312
|
+
Rust
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
let level = 2;
|
|
316
|
+
|
|
317
|
+
let code = ts_template! {
|
|
318
|
+
{#if level == 1}
|
|
319
|
+
console.log("Level 1");
|
|
320
|
+
{:else if level == 2}
|
|
321
|
+
console.log("Level 2");
|
|
322
|
+
{:else}
|
|
323
|
+
console.log("Other level");
|
|
324
|
+
{/if}
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Pattern Matching: `{#if let}`
|
|
329
|
+
|
|
330
|
+
Use `if let` for pattern matching on `Option`, `Result`, or other Rust enums:
|
|
331
|
+
|
|
332
|
+
Rust
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
let maybe_name: Option<&str> = Some("Alice");
|
|
336
|
+
|
|
337
|
+
let code = ts_template! {
|
|
338
|
+
{#if let Some(name) = maybe_name}
|
|
339
|
+
console.log("Hello, @{name}!");
|
|
340
|
+
{:else}
|
|
341
|
+
console.log("Hello, anonymous!");
|
|
342
|
+
{/if}
|
|
343
|
+
};
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Generates:**
|
|
347
|
+
|
|
348
|
+
TypeScript
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
console.log("Hello, Alice!");
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
This is useful when working with optional values from your IR:
|
|
355
|
+
|
|
356
|
+
Rust
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
let code = ts_template! {
|
|
360
|
+
{#if let Some(default_val) = field.default_value}
|
|
361
|
+
this.@{field.name} = @{default_val};
|
|
362
|
+
{:else}
|
|
363
|
+
this.@{field.name} = undefined;
|
|
364
|
+
{/if}
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Match Expressions: `{#match}`
|
|
369
|
+
|
|
370
|
+
Use `match` for exhaustive pattern matching:
|
|
371
|
+
|
|
372
|
+
Rust
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
enum Visibility { Public, Private, Protected }
|
|
376
|
+
let visibility = Visibility::Public;
|
|
377
|
+
|
|
378
|
+
let code = ts_template! {
|
|
379
|
+
{#match visibility}
|
|
380
|
+
{:case Visibility::Public}
|
|
381
|
+
public
|
|
382
|
+
{:case Visibility::Private}
|
|
383
|
+
private
|
|
384
|
+
{:case Visibility::Protected}
|
|
385
|
+
protected
|
|
386
|
+
{/match}
|
|
387
|
+
field: string;
|
|
388
|
+
};
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Generates:**
|
|
392
|
+
|
|
393
|
+
TypeScript
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
public field: string;
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Match with Value Extraction
|
|
400
|
+
|
|
401
|
+
Rust
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
let result: Result<i32, &str> = Ok(42);
|
|
405
|
+
|
|
406
|
+
let code = ts_template! {
|
|
407
|
+
const value = {#match result}
|
|
408
|
+
{:case Ok(val)}
|
|
409
|
+
@{val}
|
|
410
|
+
{:case Err(msg)}
|
|
411
|
+
throw new Error("@{msg}")
|
|
412
|
+
{/match};
|
|
413
|
+
};
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Match with Wildcard
|
|
417
|
+
|
|
418
|
+
Rust
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
let count = 5;
|
|
422
|
+
|
|
423
|
+
let code = ts_template! {
|
|
424
|
+
{#match count}
|
|
425
|
+
{:case 0}
|
|
426
|
+
console.log("none");
|
|
427
|
+
{:case 1}
|
|
428
|
+
console.log("one");
|
|
429
|
+
{:case _}
|
|
430
|
+
console.log("many");
|
|
431
|
+
{/match}
|
|
432
|
+
};
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Iteration: `{#for}`
|
|
436
|
+
|
|
437
|
+
Rust
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
let fields = vec!["name", "email", "age"];
|
|
441
|
+
|
|
442
|
+
let code = ts_template! {
|
|
443
|
+
function toJSON() {
|
|
444
|
+
const result = {};
|
|
445
|
+
{#for field in fields}
|
|
446
|
+
result.@{field} = this.@{field};
|
|
447
|
+
{/for}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Generates:**
|
|
454
|
+
|
|
455
|
+
TypeScript
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
function toJSON() {
|
|
459
|
+
const result = {};
|
|
460
|
+
result.name = this.name;
|
|
461
|
+
result.email = this.email;
|
|
462
|
+
result.age = this.age;
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Tuple Destructuring in Loops
|
|
468
|
+
|
|
469
|
+
Rust
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
let items = vec![("user", "User"), ("post", "Post")];
|
|
473
|
+
|
|
474
|
+
let code = ts_template! {
|
|
475
|
+
{#for (key, class_name) in items}
|
|
476
|
+
const @{key} = new @{class_name}();
|
|
477
|
+
{/for}
|
|
478
|
+
};
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Nested Iterations
|
|
482
|
+
|
|
483
|
+
Rust
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
let classes = vec![
|
|
487
|
+
("User", vec!["name", "email"]),
|
|
488
|
+
("Post", vec!["title", "content"]),
|
|
307
489
|
];
|
|
308
490
|
|
|
309
|
-
ts_template
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
491
|
+
ts_template! {
|
|
492
|
+
{#for (class_name, fields) in classes}
|
|
493
|
+
@{class_name}.prototype.toJSON = function() {
|
|
494
|
+
return {
|
|
495
|
+
{#for field in fields}
|
|
496
|
+
@{field}: this.@{field},
|
|
497
|
+
{/for}
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
{/for}
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## While Loops: `{#while}`
|
|
505
|
+
|
|
506
|
+
Use `while` for loops that need to continue until a condition is false:
|
|
507
|
+
|
|
508
|
+
Rust
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
let items = get_items();
|
|
512
|
+
let mut idx = 0;
|
|
513
|
+
|
|
514
|
+
let code = ts_template! {
|
|
515
|
+
{$let mut i = 0}
|
|
516
|
+
{#while i < items.len()}
|
|
517
|
+
console.log("Item @{i}");
|
|
518
|
+
{$do i += 1}
|
|
519
|
+
{/while}
|
|
520
|
+
};
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### While-Let Pattern Matching
|
|
524
|
+
|
|
525
|
+
Use `while let` for iterating with pattern matching, similar to `if let`:
|
|
526
|
+
|
|
527
|
+
Rust
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
let mut items = vec!["a", "b", "c"].into_iter();
|
|
531
|
+
|
|
532
|
+
let code = ts_template! {
|
|
533
|
+
{#while let Some(item) = items.next()}
|
|
534
|
+
console.log("@{item}");
|
|
535
|
+
{/while}
|
|
536
|
+
};
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Generates:**
|
|
540
|
+
|
|
541
|
+
TypeScript
|
|
542
|
+
|
|
543
|
+
```
|
|
345
544
|
console.log("a");
|
|
346
545
|
console.log("b");
|
|
347
546
|
console.log("c");
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
let
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
let
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
This is especially useful when working with iterators or consuming optional values:
|
|
550
|
+
|
|
551
|
+
Rust
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
let code = ts_template! {
|
|
555
|
+
{#while let Some(next_field) = remaining_fields.pop()}
|
|
556
|
+
result.@{next_field.name} = this.@{next_field.name};
|
|
557
|
+
{/while}
|
|
558
|
+
};
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## Local Constants: `{$let}`
|
|
562
|
+
|
|
563
|
+
Define local variables within the template scope:
|
|
564
|
+
|
|
565
|
+
Rust
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
let items = vec![("user", "User"), ("post", "Post")];
|
|
569
|
+
|
|
570
|
+
let code = ts_template! {
|
|
571
|
+
{#for (key, class_name) in items}
|
|
572
|
+
{$let upper = class_name.to_uppercase()}
|
|
573
|
+
console.log("Processing @{upper}");
|
|
574
|
+
const @{key} = new @{class_name}();
|
|
575
|
+
{/for}
|
|
576
|
+
};
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
This is useful for computing derived values inside loops without cluttering the Rust code.
|
|
580
|
+
|
|
581
|
+
## Mutable Variables: `{$let mut}`
|
|
582
|
+
|
|
583
|
+
When you need to modify a variable within the template (e.g., in a `while` loop), use `{$let mut}`:
|
|
584
|
+
|
|
585
|
+
Rust
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
let code = ts_template! {
|
|
589
|
+
{$let mut count = 0}
|
|
590
|
+
{#for item in items}
|
|
591
|
+
console.log("Item @{count}: @{item}");
|
|
592
|
+
{$do count += 1}
|
|
593
|
+
{/for}
|
|
594
|
+
console.log("Total: @{count}");
|
|
595
|
+
};
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Side Effects: `{$do}`
|
|
599
|
+
|
|
600
|
+
Execute an expression for its side effects without producing output. This is commonly used with mutable variables:
|
|
601
|
+
|
|
602
|
+
Rust
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
let code = ts_template! {
|
|
606
|
+
{$let mut results: Vec<String> = Vec::new()}
|
|
607
|
+
{#for field in fields}
|
|
608
|
+
{$do results.push(format!("this.{}", field))}
|
|
609
|
+
{/for}
|
|
610
|
+
return [@{results.join(", ")}];
|
|
611
|
+
};
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Common uses for `{$do}`:
|
|
615
|
+
|
|
616
|
+
* Incrementing counters: `{$do i += 1}`
|
|
617
|
+
* Building collections: `{$do vec.push(item)}`
|
|
618
|
+
* Setting flags: `{$do found = true}`
|
|
619
|
+
* Any mutating operation
|
|
620
|
+
|
|
621
|
+
## TsStream Injection: `{$typescript}`
|
|
622
|
+
|
|
623
|
+
Inject another TsStream into your template, preserving both its source code and runtime patches (like imports added via `add_import()`):
|
|
624
|
+
|
|
625
|
+
Rust
|
|
626
|
+
|
|
627
|
+
```
|
|
628
|
+
// Create a helper method with its own import
|
|
629
|
+
let mut helper = body! {
|
|
630
|
+
validateEmail(email: string): boolean {
|
|
631
|
+
return Result.ok(true);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
helper.add_import("Result", "macroforge/utils");
|
|
635
|
+
|
|
636
|
+
// Inject the helper into the main template
|
|
637
|
+
let result = body! {
|
|
638
|
+
{$typescript helper}
|
|
639
|
+
|
|
640
|
+
process(data: Record<string, unknown>): void {
|
|
641
|
+
// ...
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
// result now includes helper's source AND its Result import
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
This is essential for composing multiple macro outputs while preserving imports and patches:
|
|
648
|
+
|
|
649
|
+
Rust
|
|
650
|
+
|
|
651
|
+
```
|
|
652
|
+
let extra_methods = if include_validation {
|
|
653
|
+
Some(body! {
|
|
654
|
+
validate(): boolean { return true; }
|
|
655
|
+
})
|
|
656
|
+
} else {
|
|
657
|
+
None
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
body! {
|
|
661
|
+
mainMethod(): void {}
|
|
662
|
+
|
|
663
|
+
{#if let Some(methods) = extra_methods}
|
|
664
|
+
{$typescript methods}
|
|
665
|
+
{/if}
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## Escape Syntax
|
|
670
|
+
|
|
671
|
+
If you need a literal `@{` in your output (not interpolation), use `@@{`:
|
|
672
|
+
|
|
673
|
+
Rust
|
|
674
|
+
|
|
675
|
+
```
|
|
676
|
+
ts_template! {
|
|
677
|
+
// This outputs a literal @{foo}
|
|
678
|
+
const example = "Use @@{foo} for templates";
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Generates:**
|
|
683
|
+
|
|
684
|
+
TypeScript
|
|
685
|
+
|
|
686
|
+
```
|
|
687
|
+
// This outputs a literal @{foo}
|
|
688
|
+
const example = "Use @{foo} for templates";
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
## Complete Example: JSON Derive Macro
|
|
692
|
+
|
|
693
|
+
Here's a comparison showing how `ts_template!` simplifies code generation:
|
|
694
|
+
|
|
695
|
+
### Before (Manual AST Building)
|
|
696
|
+
|
|
697
|
+
Rust
|
|
698
|
+
|
|
699
|
+
```
|
|
700
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
701
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
702
|
+
|
|
703
|
+
match &input.data {
|
|
704
|
+
Data::Class(class) => {
|
|
705
|
+
let class_name = input.name();
|
|
706
|
+
|
|
707
|
+
let mut body_stmts = vec![ts_quote!( const result = {}; as Stmt )];
|
|
708
|
+
|
|
709
|
+
for field_name in class.field_names() {
|
|
710
|
+
body_stmts.push(ts_quote!(
|
|
711
|
+
result.$(ident!("{}", field_name)) = this.$(ident!("{}", field_name));
|
|
712
|
+
as Stmt
|
|
713
|
+
));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
body_stmts.push(ts_quote!( return result; as Stmt ));
|
|
717
|
+
|
|
718
|
+
let runtime_code = fn_assign!(
|
|
719
|
+
member_expr!(Expr::Ident(ident!(class_name)), "prototype"),
|
|
720
|
+
"toJSON",
|
|
721
|
+
body_stmts
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
// ...
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### After (With ts\_template!)
|
|
731
|
+
|
|
732
|
+
Rust
|
|
733
|
+
|
|
734
|
+
```
|
|
735
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
736
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
737
|
+
|
|
738
|
+
match &input.data {
|
|
739
|
+
Data::Class(class) => {
|
|
740
|
+
let class_name = input.name();
|
|
741
|
+
let fields = class.field_names();
|
|
742
|
+
|
|
743
|
+
let runtime_code = ts_template! {
|
|
744
|
+
@{class_name}.prototype.toJSON = function() {
|
|
745
|
+
const result = {};
|
|
746
|
+
{#for field in fields}
|
|
747
|
+
result.@{field} = this.@{field};
|
|
748
|
+
{/for}
|
|
749
|
+
return result;
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// ...
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
## How It Works
|
|
760
|
+
|
|
761
|
+
1. **Compile-Time:** The template is parsed during macro expansion
|
|
762
|
+
2. **String Building:** Generates Rust code that builds a TypeScript string at runtime
|
|
763
|
+
3. **SWC Parsing:** The generated string is parsed with SWC to produce a typed AST
|
|
764
|
+
4. **Result:** Returns `Stmt` that can be used in `MacroResult` patches
|
|
765
|
+
|
|
766
|
+
## Return Type
|
|
767
|
+
|
|
768
|
+
`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:
|
|
769
|
+
|
|
770
|
+
Text
|
|
771
|
+
|
|
772
|
+
```
|
|
773
|
+
Failed to parse generated TypeScript:
|
|
774
|
+
User.prototype.toJSON = function( {
|
|
775
|
+
return {};
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
This shows you exactly what was generated, making debugging easy!
|
|
780
|
+
|
|
781
|
+
## Nesting and Regular TypeScript
|
|
782
|
+
|
|
783
|
+
You can mix template syntax with regular TypeScript. Braces `{}` are recognized as either:
|
|
784
|
+
|
|
785
|
+
* **Template tags** if they start with `#`, `$`, `:`, or `/`
|
|
786
|
+
* **Regular TypeScript blocks** otherwise
|
|
787
|
+
|
|
788
|
+
Rust
|
|
789
|
+
|
|
790
|
+
```
|
|
791
|
+
ts_template! {
|
|
792
|
+
const config = {
|
|
793
|
+
{#if use_strict}
|
|
794
|
+
strict: true,
|
|
795
|
+
{:else}
|
|
796
|
+
strict: false,
|
|
797
|
+
{/if}
|
|
798
|
+
timeout: 5000
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
## Comparison with Alternatives
|
|
804
|
+
|
|
805
|
+
| Approach | Pros | Cons |
|
|
806
|
+
| ---------------- | ---------------------------------- | -------------------------------- |
|
|
807
|
+
| `ts_quote!` | Compile-time validation, type-safe | Can't handle Vec\<Stmt>, verbose |
|
|
808
|
+
| `parse_ts_str()` | Maximum flexibility | Runtime parsing, less readable |
|
|
809
|
+
| `ts_template!` | Readable, handles loops/conditions | Small runtime parsing overhead |
|
|
810
|
+
|
|
811
|
+
## Best Practices
|
|
812
|
+
|
|
813
|
+
1. Use `ts_template!` for complex code generation with loops/conditions
|
|
814
|
+
2. Use `ts_quote!` for simple, static statements
|
|
815
|
+
3. Keep templates readable - extract complex logic into variables
|
|
816
|
+
4. Don't nest templates too deeply - split into helper functions
|