@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
|
@@ -1,147 +1,147 @@
|
|
|
1
1
|
# Template Syntax
|
|
2
|
-
*The
|
|
2
|
+
*The <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">macroforge_ts_quote</code> crate provides template-based code generation for TypeScript. The <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_template!</code> macro uses Svelte + Rust-inspired syntax for control flow and interpolation, making it easy to generate complex TypeScript code.*
|
|
3
3
|
## Available Macros
|
|
4
4
|
| Macro | Output | Use Case |
|
|
5
5
|
| --- | --- | --- |
|
|
6
|
-
|
|
|
7
|
-
|
|
|
6
|
+
| ts_template! | Any TypeScript code | General code generation |
|
|
7
|
+
| body! | Class body members | Methods and properties |
|
|
8
8
|
## Quick Reference
|
|
9
9
|
| Syntax | Description |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
11
|
+
| @{expr} | Interpolate a Rust expression (adds space after) |
|
|
12
|
+
| {| content |} | Ident block: concatenates without spaces (e.g., `{|get@{name}|}` → getUser) |
|
|
13
|
+
| {> "comment" <} | Block comment: outputs /* comment */ (string preserves whitespace) |
|
|
14
|
+
| {>> "doc" <<} | Doc comment: outputs /** doc */ (string preserves whitespace) |
|
|
15
|
+
| @@{ | Escape for literal @{ (e.g., "@@{foo}" → @{foo}) |
|
|
16
|
+
| "text @{expr}" | String interpolation (auto-detected) |
|
|
17
|
+
| "'^template ${js}^'" | JS backtick template literal (outputs ``template ${js}``) |
|
|
18
|
+
| {#if cond}...{/if} | Conditional block |
|
|
19
19
|
| `{#if cond}...{:else}...{/if}` | Conditional with else |
|
|
20
20
|
| {#if a}...{:else if b}...{:else}...{/if} | Full if/else-if/else chain |
|
|
21
21
|
| `{#if let pattern = expr}...{/if}` | Pattern matching if-let |
|
|
22
22
|
| {#match expr}{:case pattern}...{/match} | Match expression with case arms |
|
|
23
23
|
| `{#for item in list}...{/for}` | Iterate over a collection |
|
|
24
|
-
|
|
|
24
|
+
| {#while cond}...{/while} | While loop |
|
|
25
25
|
| `{#while let pattern = expr}...{/while}` | While-let pattern matching loop |
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
**Note:** A single
|
|
31
|
-
## Interpolation:
|
|
26
|
+
| {$let name = expr} | Define a local constant |
|
|
27
|
+
| {$let mut name = expr} | Define a mutable local variable |
|
|
28
|
+
| {$do expr} | Execute a side-effectful expression |
|
|
29
|
+
| {$typescript stream} | Inject a TsStream, preserving its source and runtime_patches (imports) |
|
|
30
|
+
**Note:** A single <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@</code> not followed by <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</code> passes through unchanged (e.g., <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">email@domain.com</code> works as expected).
|
|
31
|
+
## Interpolation: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@{expr}</code>
|
|
32
32
|
Insert Rust expressions into the generated TypeScript:
|
|
33
33
|
```
|
|
34
|
-
let
|
|
35
|
-
let
|
|
34
|
+
let class_name = "User";
|
|
35
|
+
let method = "toString";
|
|
36
36
|
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
let code = ts_template! {
|
|
38
|
+
@{class_name}.prototype.@{method} = function() {
|
|
39
|
+
return "User instance";
|
|
40
|
+
};
|
|
41
41
|
};
|
|
42
42
|
``` **Generates:**
|
|
43
43
|
```
|
|
44
|
-
User.prototype.toString
|
|
45
|
-
|
|
44
|
+
User.prototype.toString = function () {
|
|
45
|
+
return "User instance";
|
|
46
46
|
};
|
|
47
|
-
``` ## Identifier Concatenation:
|
|
48
|
-
When you need to build identifiers dynamically (like
|
|
47
|
+
``` ## Identifier Concatenation: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> content <span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
48
|
+
When you need to build identifiers dynamically (like <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">getUser</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">setName</code>), use the ident block syntax. Everything inside <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> is concatenated without spaces:
|
|
49
49
|
```
|
|
50
|
-
let
|
|
50
|
+
let field_name = "User";
|
|
51
51
|
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
let code = ts_template! {
|
|
53
|
+
function {|get@{field_name}|}() {
|
|
54
|
+
return this.@{field_name.to_lowercase()};
|
|
55
|
+
}
|
|
56
56
|
};
|
|
57
57
|
``` **Generates:**
|
|
58
58
|
```
|
|
59
|
-
function
|
|
60
|
-
|
|
59
|
+
function getUser() {
|
|
60
|
+
return this.user;
|
|
61
61
|
}
|
|
62
|
-
``` Without ident blocks,
|
|
62
|
+
``` Without ident blocks, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@{}</code> always adds a space after for readability. Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> when you explicitly want concatenation:
|
|
63
63
|
```
|
|
64
|
-
let
|
|
64
|
+
let name = "Status";
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
ts_template
|
|
66
|
+
// With space (default behavior)
|
|
67
|
+
ts_template! { namespace @{name} } // → "namespace Status"
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
ts_template
|
|
69
|
+
// Without space (ident block)
|
|
70
|
+
ts_template! { {|namespace@{name}|} } // → "namespaceStatus"
|
|
71
71
|
``` Multiple interpolations can be combined:
|
|
72
72
|
```
|
|
73
|
-
let
|
|
74
|
-
let
|
|
73
|
+
let entity = "user";
|
|
74
|
+
let action = "create";
|
|
75
75
|
|
|
76
|
-
ts_template
|
|
77
|
-
``` ## Comments:
|
|
76
|
+
ts_template! { {|@{entity}_@{action}|} } // → "user_create"
|
|
77
|
+
``` ## Comments: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "..."<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> <<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> and <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "..."<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> <<<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
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
|
-
Use
|
|
80
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "comment"<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> <<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> for block comments:
|
|
81
81
|
```
|
|
82
|
-
let
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
let code = ts_template! {
|
|
83
|
+
{> "This is a block comment" <}
|
|
84
|
+
const x = 42;
|
|
85
85
|
};
|
|
86
86
|
``` **Generates:**
|
|
87
87
|
```
|
|
88
|
-
|
|
89
|
-
const
|
|
88
|
+
/* This is a block comment */
|
|
89
|
+
const x = 42;
|
|
90
90
|
``` ### Doc Comments (JSDoc)
|
|
91
|
-
Use
|
|
92
|
-
```
|
|
93
|
-
let
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "doc"<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> <<<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> for JSDoc comments:
|
|
92
|
+
```
|
|
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
|
+
return "Hello, " + name;
|
|
98
|
+
}
|
|
99
99
|
};
|
|
100
100
|
``` **Generates:**
|
|
101
101
|
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
function
|
|
105
|
-
|
|
102
|
+
/** @param {string} name - The user's name */
|
|
103
|
+
/** @returns {string} A greeting message */
|
|
104
|
+
function greet(name: string): string {
|
|
105
|
+
return "Hello, " + name;
|
|
106
106
|
}
|
|
107
107
|
``` ### Comments with Interpolation
|
|
108
|
-
Use
|
|
108
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">format<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</code> or similar to build dynamic comment strings:
|
|
109
109
|
```
|
|
110
|
-
let
|
|
111
|
-
let
|
|
112
|
-
let
|
|
110
|
+
let param_name = "userId";
|
|
111
|
+
let param_type = "number";
|
|
112
|
+
let comment = format!("@param {{{}}} {} - The user ID", param_type, param_name);
|
|
113
113
|
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
let code = ts_template! {
|
|
115
|
+
{>> @{comment} <<}
|
|
116
|
+
function getUser(userId: number) {}
|
|
117
117
|
};
|
|
118
118
|
``` **Generates:**
|
|
119
119
|
```
|
|
120
|
-
|
|
121
|
-
function
|
|
122
|
-
``` ## String Interpolation:
|
|
120
|
+
/** @param {number} userId - The user ID */
|
|
121
|
+
function getUser(userId: number) {}
|
|
122
|
+
``` ## String Interpolation: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"text @{expr}"</code>
|
|
123
123
|
Interpolation works automatically inside string literals - no `format!()` needed:
|
|
124
124
|
```
|
|
125
|
-
let
|
|
126
|
-
let
|
|
125
|
+
let name = "World";
|
|
126
|
+
let count = 42;
|
|
127
127
|
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
let code = ts_template! {
|
|
129
|
+
console.log("Hello @{name}!");
|
|
130
|
+
console.log("Count: @{count}, doubled: @{count * 2}");
|
|
131
131
|
};
|
|
132
132
|
``` **Generates:**
|
|
133
133
|
```
|
|
134
|
-
console.log("Hello
|
|
135
|
-
console.log("Count
|
|
134
|
+
console.log("Hello World!");
|
|
135
|
+
console.log("Count: 42, doubled: 84");
|
|
136
136
|
``` This also works with method calls and complex expressions:
|
|
137
137
|
```
|
|
138
|
-
let
|
|
138
|
+
let field = "username";
|
|
139
139
|
|
|
140
|
-
let
|
|
141
|
-
|
|
140
|
+
let code = ts_template! {
|
|
141
|
+
throw new Error("Invalid @{field.to_uppercase()}");
|
|
142
142
|
};
|
|
143
|
-
``` ## Backtick Template Literals:
|
|
144
|
-
For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through
|
|
143
|
+
``` ## Backtick Template Literals: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"'^...^'"</code>
|
|
144
|
+
For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">${<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${}"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> for JS interpolation:
|
|
145
145
|
```
|
|
146
146
|
let tag_name = "div";
|
|
147
147
|
|
|
@@ -151,7 +151,7 @@ let code = ts_template! {
|
|
|
151
151
|
``` **Generates:**
|
|
152
152
|
```
|
|
153
153
|
const html = `${content}`;
|
|
154
|
-
``` You can mix Rust
|
|
154
|
+
``` You can mix Rust <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@{}</code> interpolation (evaluated at macro expansion time) with JS <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">${<span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"${}"<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code> interpolation (evaluated at runtime):
|
|
155
155
|
```
|
|
156
156
|
let class_name = "User";
|
|
157
157
|
|
|
@@ -160,185 +160,185 @@ let code = ts_template! {
|
|
|
160
160
|
};
|
|
161
161
|
``` **Generates:**
|
|
162
162
|
```
|
|
163
|
-
`Hello
|
|
164
|
-
``` ## Conditionals:
|
|
163
|
+
`Hello ${this.name}, you are a User`
|
|
164
|
+
``` ## Conditionals: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{#<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/if<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
165
165
|
Basic conditional:
|
|
166
166
|
```
|
|
167
|
-
let
|
|
167
|
+
let needs_validation = true;
|
|
168
168
|
|
|
169
|
-
let
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
let code = ts_template! {
|
|
170
|
+
function save() {
|
|
171
|
+
{#if needs_validation}
|
|
172
|
+
if (!this.isValid()) return false;
|
|
173
|
+
{/if}
|
|
174
|
+
return this.doSave();
|
|
175
|
+
}
|
|
176
176
|
};
|
|
177
177
|
``` ### If-Else
|
|
178
178
|
```
|
|
179
|
-
let
|
|
179
|
+
let has_default = true;
|
|
180
180
|
|
|
181
|
-
let
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
let code = ts_template! {
|
|
182
|
+
{#if has_default}
|
|
183
|
+
return defaultValue;
|
|
184
|
+
{:else}
|
|
185
|
+
throw new Error("No default");
|
|
186
|
+
{/if}
|
|
187
187
|
};
|
|
188
188
|
``` ### If-Else-If Chains
|
|
189
189
|
```
|
|
190
|
-
let
|
|
190
|
+
let level = 2;
|
|
191
191
|
|
|
192
|
-
let
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
let code = ts_template! {
|
|
193
|
+
{#if level == 1}
|
|
194
|
+
console.log("Level 1");
|
|
195
|
+
{:else if level == 2}
|
|
196
|
+
console.log("Level 2");
|
|
197
|
+
{:else}
|
|
198
|
+
console.log("Other level");
|
|
199
|
+
{/if}
|
|
200
200
|
};
|
|
201
|
-
``` ## Pattern Matching:
|
|
202
|
-
Use
|
|
201
|
+
``` ## Pattern Matching: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{#<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> let<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
202
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> let</code> for pattern matching on <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Option</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Result</code>, or other Rust enums:
|
|
203
203
|
```
|
|
204
|
-
let
|
|
204
|
+
let maybe_name: Option<&str> = Some("Alice");
|
|
205
205
|
|
|
206
|
-
let
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
let code = ts_template! {
|
|
207
|
+
{#if let Some(name) = maybe_name}
|
|
208
|
+
console.log("Hello, @{name}!");
|
|
209
|
+
{:else}
|
|
210
|
+
console.log("Hello, anonymous!");
|
|
211
|
+
{/if}
|
|
212
212
|
};
|
|
213
213
|
``` **Generates:**
|
|
214
214
|
```
|
|
215
|
-
console.log("Hello
|
|
215
|
+
console.log("Hello, Alice!");
|
|
216
216
|
``` This is useful when working with optional values from your IR:
|
|
217
217
|
```
|
|
218
|
-
let
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
224
|
};
|
|
225
|
-
``` ## Match Expressions:
|
|
226
|
-
Use
|
|
227
|
-
```
|
|
228
|
-
enum
|
|
229
|
-
let
|
|
230
|
-
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
225
|
+
``` ## Match Expressions: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{#match}</code>
|
|
226
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">match</code> for exhaustive pattern matching:
|
|
227
|
+
```
|
|
228
|
+
enum Visibility { Public, Private, Protected }
|
|
229
|
+
let visibility = Visibility::Public;
|
|
230
|
+
|
|
231
|
+
let code = ts_template! {
|
|
232
|
+
{#match visibility}
|
|
233
|
+
{:case Visibility::Public}
|
|
234
|
+
public
|
|
235
|
+
{:case Visibility::Private}
|
|
236
|
+
private
|
|
237
|
+
{:case Visibility::Protected}
|
|
238
|
+
protected
|
|
239
|
+
{/match}
|
|
240
|
+
field: string;
|
|
241
241
|
};
|
|
242
242
|
``` **Generates:**
|
|
243
243
|
```
|
|
244
|
-
public
|
|
244
|
+
public field: string;
|
|
245
245
|
``` ### Match with Value Extraction
|
|
246
246
|
```
|
|
247
|
-
let
|
|
247
|
+
let result: Result<i32, &str> = Ok(42);
|
|
248
248
|
|
|
249
|
-
let
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
256
|
};
|
|
257
257
|
``` ### Match with Wildcard
|
|
258
258
|
```
|
|
259
|
-
let
|
|
260
|
-
|
|
261
|
-
let
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
259
|
+
let count = 5;
|
|
260
|
+
|
|
261
|
+
let code = ts_template! {
|
|
262
|
+
{#match count}
|
|
263
|
+
{:case 0}
|
|
264
|
+
console.log("none");
|
|
265
|
+
{:case 1}
|
|
266
|
+
console.log("one");
|
|
267
|
+
{:case _}
|
|
268
|
+
console.log("many");
|
|
269
|
+
{/match}
|
|
270
270
|
};
|
|
271
|
-
``` ## Iteration:
|
|
272
|
-
```
|
|
273
|
-
let
|
|
274
|
-
|
|
275
|
-
let
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
271
|
+
``` ## Iteration: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{#for}</code>
|
|
272
|
+
```
|
|
273
|
+
let fields = vec!["name", "email", "age"];
|
|
274
|
+
|
|
275
|
+
let code = ts_template! {
|
|
276
|
+
function toJSON() {
|
|
277
|
+
const result = {};
|
|
278
|
+
{#for field in fields}
|
|
279
|
+
result.@{field} = this.@{field};
|
|
280
|
+
{/for}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
283
|
};
|
|
284
284
|
``` **Generates:**
|
|
285
285
|
```
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
286
|
+
function toJSON() {
|
|
287
|
+
const result = {};
|
|
288
|
+
result.name = this.name;
|
|
289
|
+
result.email = this.email;
|
|
290
|
+
result.age = this.age;
|
|
291
|
+
return result;
|
|
292
292
|
}
|
|
293
293
|
``` ### Tuple Destructuring in Loops
|
|
294
294
|
```
|
|
295
|
-
let
|
|
295
|
+
let items = vec![("user", "User"), ("post", "Post")];
|
|
296
296
|
|
|
297
|
-
let
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
let code = ts_template! {
|
|
298
|
+
{#for (key, class_name) in items}
|
|
299
|
+
const @{key} = new @{class_name}();
|
|
300
|
+
{/for}
|
|
301
301
|
};
|
|
302
302
|
``` ### Nested Iterations
|
|
303
303
|
```
|
|
304
|
-
let
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
let classes = vec![
|
|
305
|
+
("User", vec!["name", "email"]),
|
|
306
|
+
("Post", vec!["title", "content"]),
|
|
307
307
|
];
|
|
308
308
|
|
|
309
|
-
ts_template
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
319
|
}
|
|
320
|
-
``` ## While Loops:
|
|
321
|
-
Use
|
|
322
|
-
```
|
|
323
|
-
let
|
|
324
|
-
let
|
|
325
|
-
|
|
326
|
-
let
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
320
|
+
``` ## While Loops: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{#<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">while<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
321
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">while</code> for loops that need to continue until a condition is false:
|
|
322
|
+
```
|
|
323
|
+
let items = get_items();
|
|
324
|
+
let mut idx = 0;
|
|
325
|
+
|
|
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
332
|
};
|
|
333
333
|
``` ### While-Let Pattern Matching
|
|
334
|
-
Use
|
|
334
|
+
Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">while<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> let</code> for iterating with pattern matching, similar to <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> let</code>:
|
|
335
335
|
```
|
|
336
|
-
let
|
|
336
|
+
let mut items = vec!["a", "b", "c"].into_iter();
|
|
337
337
|
|
|
338
|
-
let
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
let code = ts_template! {
|
|
339
|
+
{#while let Some(item) = items.next()}
|
|
340
|
+
console.log("@{item}");
|
|
341
|
+
{/while}
|
|
342
342
|
};
|
|
343
343
|
``` **Generates:**
|
|
344
344
|
```
|
|
@@ -347,190 +347,190 @@ 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
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
354
|
};
|
|
355
|
-
``` ## Local Constants:
|
|
355
|
+
``` ## Local Constants: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$let}</code>
|
|
356
356
|
Define local variables within the template scope:
|
|
357
357
|
```
|
|
358
|
-
let
|
|
358
|
+
let items = vec![("user", "User"), ("post", "Post")];
|
|
359
359
|
|
|
360
|
-
let
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
366
|
};
|
|
367
367
|
``` This is useful for computing derived values inside loops without cluttering the Rust code.
|
|
368
|
-
## Mutable Variables:
|
|
369
|
-
When you need to modify a variable within the template (e.g., in a `while` loop), use
|
|
370
|
-
```
|
|
371
|
-
let
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
368
|
+
## Mutable Variables: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$let mut}</code>
|
|
369
|
+
When you need to modify a variable within the template (e.g., in a `while` loop), use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$let mut}</code>:
|
|
370
|
+
```
|
|
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
378
|
};
|
|
379
|
-
``` ## Side Effects:
|
|
379
|
+
``` ## Side Effects: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$do}</code>
|
|
380
380
|
Execute an expression for its side effects without producing output. This is commonly used with mutable variables:
|
|
381
381
|
```
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
388
|
};
|
|
389
|
-
``` Common uses for
|
|
390
|
-
- Incrementing counters:
|
|
391
|
-
- Building collections:
|
|
392
|
-
- Setting flags:
|
|
389
|
+
``` Common uses for <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$do}</code>:
|
|
390
|
+
- Incrementing counters: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$do i <span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=<span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
391
|
+
- Building collections: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$do vec.<span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item)}</code>
|
|
392
|
+
- Setting flags: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$do found <span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=<span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</code>
|
|
393
393
|
- Any mutating operation
|
|
394
|
-
## TsStream Injection:
|
|
395
|
-
Inject another TsStream into your template, preserving both its source code and runtime patches (like imports added via
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
let
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
394
|
+
## TsStream Injection: <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{$typescript}</code>
|
|
395
|
+
Inject another TsStream into your template, preserving both its source code and runtime patches (like imports added via <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add_import<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</code>):
|
|
396
|
+
```
|
|
397
|
+
// Create a helper method with its own import
|
|
398
|
+
let mut helper = body! {
|
|
399
|
+
validateEmail(email: string): boolean {
|
|
400
|
+
return Result.ok(true);
|
|
401
|
+
}
|
|
402
402
|
};
|
|
403
|
-
helper.add_import("Result"
|
|
403
|
+
helper.add_import("Result", "macroforge/utils");
|
|
404
404
|
|
|
405
|
-
|
|
406
|
-
let
|
|
407
|
-
|
|
405
|
+
// Inject the helper into the main template
|
|
406
|
+
let result = body! {
|
|
407
|
+
{$typescript helper}
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
process(data: Record<string, unknown>): void {
|
|
410
|
+
// ...
|
|
411
|
+
}
|
|
412
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
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
416
|
+
let extra_methods = if include_validation {
|
|
417
|
+
Some(body! {
|
|
418
|
+
validate(): boolean { return true; }
|
|
419
|
+
})
|
|
420
|
+
} else {
|
|
421
|
+
None
|
|
422
422
|
};
|
|
423
423
|
|
|
424
|
-
body
|
|
425
|
-
|
|
424
|
+
body! {
|
|
425
|
+
mainMethod(): void {}
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
427
|
+
{#if let Some(methods) = extra_methods}
|
|
428
|
+
{$typescript methods}
|
|
429
|
+
{/if}
|
|
430
430
|
}
|
|
431
431
|
``` ## Escape Syntax
|
|
432
|
-
If you need a literal
|
|
432
|
+
If you need a literal <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@{</code> in your output (not interpolation), use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">@@{</code>:
|
|
433
433
|
```
|
|
434
|
-
ts_template
|
|
435
|
-
|
|
436
|
-
|
|
434
|
+
ts_template! {
|
|
435
|
+
// This outputs a literal @{foo}
|
|
436
|
+
const example = "Use @@{foo} for templates";
|
|
437
437
|
}
|
|
438
438
|
``` **Generates:**
|
|
439
439
|
```
|
|
440
|
-
|
|
441
|
-
const
|
|
440
|
+
// This outputs a literal @{foo}
|
|
441
|
+
const example = "Use @{foo} for templates";
|
|
442
442
|
``` ## Complete Example: JSON Derive Macro
|
|
443
|
-
Here's a comparison showing how
|
|
443
|
+
Here's a comparison showing how <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_template!</code> simplifies code generation:
|
|
444
444
|
### Before (Manual AST Building)
|
|
445
445
|
```
|
|
446
|
-
pub
|
|
447
|
-
|
|
446
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
447
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
448
448
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
449
|
+
match &input.data {
|
|
450
|
+
Data::Class(class) => {
|
|
451
|
+
let class_name = input.name();
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
let mut body_stmts = vec![ts_quote!( const result = {}; as Stmt )];
|
|
454
454
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
455
|
+
for field_name in class.field_names() {
|
|
456
|
+
body_stmts.push(ts_quote!(
|
|
457
|
+
result.$(ident!("{}", field_name)) = this.$(ident!("{}", field_name));
|
|
458
|
+
as Stmt
|
|
459
|
+
));
|
|
460
|
+
}
|
|
461
461
|
|
|
462
|
-
|
|
462
|
+
body_stmts.push(ts_quote!( return result; as Stmt ));
|
|
463
463
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
let runtime_code = fn_assign!(
|
|
465
|
+
member_expr!(Expr::Ident(ident!(class_name)), "prototype"),
|
|
466
|
+
"toJSON",
|
|
467
|
+
body_stmts
|
|
468
|
+
);
|
|
469
469
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
470
|
+
// ...
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
473
|
}
|
|
474
474
|
``` ### After (With ts_template!)
|
|
475
475
|
```
|
|
476
|
-
pub
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
476
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
477
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
478
|
+
|
|
479
|
+
match &input.data {
|
|
480
|
+
Data::Class(class) => {
|
|
481
|
+
let class_name = input.name();
|
|
482
|
+
let fields = class.field_names();
|
|
483
|
+
|
|
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
|
+
return result;
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// ...
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
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
|
|
501
501
|
3. **SWC Parsing:** The generated string is parsed with SWC to produce a typed AST
|
|
502
|
-
4. **Result:** Returns
|
|
502
|
+
4. **Result:** Returns <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Stmt</code> that can be used in <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">MacroResult</code> patches
|
|
503
503
|
## Return Type
|
|
504
|
-
|
|
504
|
+
<code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_template!</code> returns a <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Result<span style="--shiki-dark:#F97583;--shiki-light:#D73A49"><<span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Stmt, TsSynError<span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></code> by default. The macro automatically unwraps and provides helpful error messages showing the generated TypeScript code if parsing fails:
|
|
505
505
|
```
|
|
506
|
-
Failed
|
|
507
|
-
User.prototype.toJSON
|
|
508
|
-
|
|
506
|
+
Failed to parse generated TypeScript:
|
|
507
|
+
User.prototype.toJSON = function( {
|
|
508
|
+
return {};
|
|
509
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
|
-
- **Template tags** if they start with
|
|
513
|
+
- **Template tags** if they start with <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">#</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">$</code>, <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</code>, or <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</code>
|
|
514
514
|
- **Regular TypeScript blocks** otherwise
|
|
515
515
|
```
|
|
516
|
-
ts_template
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
516
|
+
ts_template! {
|
|
517
|
+
const config = {
|
|
518
|
+
{#if use_strict}
|
|
519
|
+
strict: true,
|
|
520
|
+
{:else}
|
|
521
|
+
strict: false,
|
|
522
|
+
{/if}
|
|
523
|
+
timeout: 5000
|
|
524
|
+
};
|
|
525
525
|
}
|
|
526
526
|
``` ## Comparison with Alternatives
|
|
527
527
|
| Approach | Pros | Cons |
|
|
528
528
|
| --- | --- | --- |
|
|
529
|
-
|
|
|
530
|
-
|
|
|
531
|
-
|
|
|
529
|
+
| ts_quote! | Compile-time validation, type-safe | Can't handle Vec<Stmt>, verbose |
|
|
530
|
+
| parse_ts_str() | Maximum flexibility | Runtime parsing, less readable |
|
|
531
|
+
| ts_template! | Readable, handles loops/conditions | Small runtime parsing overhead |
|
|
532
532
|
## Best Practices
|
|
533
|
-
1. Use
|
|
534
|
-
2. Use
|
|
533
|
+
1. Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_template!</code> for complex code generation with loops/conditions
|
|
534
|
+
2. Use <code class="shiki-inline"><span class="line"><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ts_quote!</code> for simple, static statements
|
|
535
535
|
3. Keep templates readable - extract complex logic into variables
|
|
536
536
|
4. Don't nest templates too deeply - split into helper functions
|