@macroforge/mcp-server 0.1.40 → 0.1.49

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.
Files changed (71) hide show
  1. package/LICENSE +22 -0
  2. package/docs/BOOK.md +165 -0
  3. package/docs/api/api-overview.md +65 -46
  4. package/docs/api/expand-sync.md +88 -53
  5. package/docs/api/native-plugin.md +121 -71
  6. package/docs/api/position-mapper.md +114 -54
  7. package/docs/api/transform-sync.md +85 -59
  8. package/docs/builtin-macros/clone.md +0 -20
  9. package/docs/builtin-macros/debug.md +0 -23
  10. package/docs/builtin-macros/default.md +1 -40
  11. package/docs/builtin-macros/deserialize/example.md +8 -1416
  12. package/docs/builtin-macros/deserialize.md +8 -1416
  13. package/docs/builtin-macros/hash.md +0 -42
  14. package/docs/builtin-macros/macros-overview/detailed-documentation.md +13 -0
  15. package/docs/builtin-macros/macros-overview/enum-support.md +30 -0
  16. package/docs/builtin-macros/macros-overview/interface-support.md +28 -0
  17. package/docs/builtin-macros/macros-overview/overview.md +36 -0
  18. package/docs/builtin-macros/macros-overview/type-alias-support.md +62 -0
  19. package/docs/builtin-macros/macros-overview.md +171 -130
  20. package/docs/builtin-macros/ord.md +0 -25
  21. package/docs/builtin-macros/partial-eq.md +0 -84
  22. package/docs/builtin-macros/partial-ord.md +11 -43
  23. package/docs/builtin-macros/serialize.md +2 -62
  24. package/docs/concepts/architecture.md +125 -48
  25. package/docs/concepts/derive-system/built-in-vs-custom-macros.md +13 -0
  26. package/docs/concepts/derive-system/overview.md +200 -0
  27. package/docs/concepts/derive-system.md +171 -97
  28. package/docs/concepts/how-macros-work.md +89 -37
  29. package/docs/custom-macros/custom-overview.md +79 -57
  30. package/docs/custom-macros/rust-setup.md +138 -99
  31. package/docs/custom-macros/ts-macro-derive/accessing-field-data.md +40 -31
  32. package/docs/custom-macros/ts-macro-derive/adding-imports.md +14 -11
  33. package/docs/custom-macros/ts-macro-derive/attribute-options.md +20 -25
  34. package/docs/custom-macros/ts-macro-derive/complete-example.md +40 -38
  35. package/docs/custom-macros/ts-macro-derive/deriveinput-structure.md +49 -47
  36. package/docs/custom-macros/ts-macro-derive/function-signature.md +12 -0
  37. package/docs/custom-macros/ts-macro-derive/overview.md +9 -7
  38. package/docs/custom-macros/ts-macro-derive/parsing-input.md +20 -18
  39. package/docs/custom-macros/ts-macro-derive/returning-errors.md +15 -13
  40. package/docs/custom-macros/ts-macro-derive.md +322 -228
  41. package/docs/custom-macros/ts-quote/backtick-template-literals.md +19 -7
  42. package/docs/custom-macros/ts-quote/comments-and.md +56 -22
  43. package/docs/custom-macros/ts-quote/complete-example-json-derive-macro.md +89 -98
  44. package/docs/custom-macros/ts-quote/conditionals-ifif.md +35 -29
  45. package/docs/custom-macros/ts-quote/identifier-concatenation-content.md +30 -22
  46. package/docs/custom-macros/ts-quote/iteration-for.md +48 -40
  47. package/docs/custom-macros/ts-quote/local-constants-let.md +23 -21
  48. package/docs/custom-macros/ts-quote/match-expressions-match.md +46 -38
  49. package/docs/custom-macros/ts-quote/overview.md +5 -10
  50. package/docs/custom-macros/ts-quote/pattern-matching-iflet.md +39 -0
  51. package/docs/custom-macros/ts-quote/quick-reference.md +50 -129
  52. package/docs/custom-macros/ts-quote/side-effects-do.md +13 -78
  53. package/docs/custom-macros/ts-quote/string-interpolation-textexpr.md +36 -0
  54. package/docs/custom-macros/ts-quote/tsstream-injection-typescript.md +43 -35
  55. package/docs/custom-macros/ts-quote/while-loops-while.md +31 -23
  56. package/docs/custom-macros/ts-quote.md +799 -519
  57. package/docs/getting-started/first-macro.md +61 -32
  58. package/docs/getting-started/installation.md +109 -66
  59. package/docs/integration/cli.md +212 -103
  60. package/docs/integration/configuration.md +114 -71
  61. package/docs/integration/integration-overview.md +54 -17
  62. package/docs/integration/mcp-server.md +83 -42
  63. package/docs/integration/svelte-preprocessor.md +183 -126
  64. package/docs/integration/typescript-plugin.md +101 -54
  65. package/docs/integration/vite-plugin.md +116 -76
  66. package/docs/language-servers/ls-overview.md +37 -21
  67. package/docs/language-servers/svelte.md +69 -39
  68. package/docs/language-servers/zed.md +81 -45
  69. package/docs/roadmap/roadmap.md +75 -53
  70. package/docs/sections.json +333 -44
  71. package/package.json +27 -28
@@ -1,242 +1,336 @@
1
- # ts_macro_derive
2
- *The `#[ts_macro_derive]` attribute is a Rust procedural macro that registers your function as a Macroforge derive macro.*
3
- ## Basic Syntax
4
- ```
5
- use macroforge_ts::macros::ts_macro_derive;
6
- use macroforge_ts::ts_syn::{TsStream, MacroforgeError};
1
+ # ts\_macro\_derive
2
+
3
+ The `#[ts_macro_derive]` attribute is a Rust procedural macro that registers your function as a Macroforge derive macro.
4
+
5
+ ## Basic Syntax
6
+
7
+ Rust
8
+
9
+ ```
10
+ use macroforge_ts::macros::ts_macro_derive;
11
+ use macroforge_ts::ts_syn::{TsStream, MacroforgeError};
7
12
 
8
13
  #[ts_macro_derive(MacroName)]
9
- pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
10
- // Macro implementation
11
- }
12
- ``` ## Attribute Options
13
- ### Name (Required)
14
- The first argument is the macro name that users will reference in `@derive()`:
15
- ```
16
- #[ts_macro_derive(JSON)] // Users write: @derive(JSON)
17
- pub fn derive_json(...)
18
- ``` ### Description
19
- Provides documentation for the macro:
20
- ```
14
+ pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
15
+     // Macro implementation
16
+ }
17
+ ```
18
+
19
+ ## Attribute Options
20
+
21
+ ### Name (Required)
22
+
23
+ The first argument is the macro name that users will reference in `@derive()`:
24
+
25
+ Rust
26
+
27
+ ```
28
+ #[ts_macro_derive(JSON)]  // Users write: @derive(JSON)
29
+ pub fn derive_json(...)
30
+ ```
31
+
32
+ ### Description
33
+
34
+ Provides documentation for the macro:
35
+
36
+ Rust
37
+
38
+ ```
21
39
  #[ts_macro_derive(
22
- JSON,
23
- description = "Generates toJSON() returning a plain object"
40
+     JSON,
41
+     description = "Generates toJSON() returning a plain object"
24
42
  )]
25
- pub fn derive_json(...)
26
- ``` ### Attributes
27
- Declare which field-level decorators your macro accepts:
28
- ```
43
+ pub fn derive_json(...)
44
+ ```
45
+
46
+ ### Attributes
47
+
48
+ Declare which field-level decorators your macro accepts:
49
+
50
+ Rust
51
+
52
+ ```
29
53
  #[ts_macro_derive(
30
- Debug,
31
- description = "Generates toString()",
32
- attributes(debug) // Allows @debug(&#123; ... &#125;) on fields
54
+     Debug,
55
+     description = "Generates toString()",
56
+     attributes(debug)  // Allows @debug(... }) on fields
33
57
  )]
34
- pub fn derive_debug(...)
35
- ``` > **Note:** Declared attributes become available as @attributeName({ options }) decorators in TypeScript. ## Function Signature
36
- ```
37
- pub fn my_macro(mut input: TsStream) -> Result&#x3C;TsStream, MacroforgeError>
38
- ``` | Parameter | Description |
39
- | --- | --- |
40
- | `input: TsStream` | Token stream containing the class/interface AST |
41
- | `Result<TsStream, MacroforgeError>` | Returns generated code or an error with source location |
42
- ## Parsing Input
43
- Use `parse_ts_macro_input!` to convert the token stream:
44
- ```
45
- use macroforge_ts::ts_syn::&#123;Data, DeriveInput, parse_ts_macro_input&#125;;
58
+ pub fn derive_debug(...)
59
+ ```
60
+
61
+ Note
62
+
63
+ Declared attributes become available as `@attributeName({ options })` decorators in TypeScript.
64
+
65
+ ## Function Signature
66
+
67
+ Rust
68
+
69
+ ```
70
+ pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError>
71
+ ```
72
+
73
+ | Parameter | Description |
74
+ | ----------------------------------- | ------------------------------------------------------- |
75
+ | `input: TsStream` | Token stream containing the class/interface AST |
76
+ | `Result<TsStream, MacroforgeError>` | Returns generated code or an error with source location |
77
+
78
+ ## Parsing Input
79
+
80
+ Use `parse_ts_macro_input!` to convert the token stream:
81
+
82
+ Rust
83
+
84
+ ```
85
+ use macroforge_ts::ts_syn::{Data, DeriveInput, parse_ts_macro_input};
46
86
 
47
87
  #[ts_macro_derive(MyMacro)]
48
- pub fn my_macro(mut input: TsStream) -> Result&#x3C;TsStream, MacroforgeError> &#123;
49
- let input = parse_ts_macro_input!(input as DeriveInput);
50
-
51
- // Access class data
52
- match &#x26;input.data &#123;
53
- Data::Class(class) => &#123;
54
- let class_name = input.name();
55
- let fields = class.fields();
56
- // ...
57
- &#125;
58
- Data::Interface(interface) => &#123;
59
- // Handle interfaces
60
- &#125;
61
- Data::Enum(_) => &#123;
62
- // Handle enums (if supported)
63
- &#125;
64
- &#125;
65
- &#125;
66
- ``` ## DeriveInput Structure
67
- ```
68
- struct DeriveInput &#123;
69
- pub ident: Ident, // The type name
70
- pub span: SpanIR, // Span of the type definition
71
- pub attrs: Vec&#x3C;Attribute>, // Decorators (excluding @derive)
72
- pub data: Data, // The parsed type data
73
- pub context: MacroContextIR, // Macro context with spans
74
-
75
- // Helper methods
76
- fn name(&#x26;self) -> &#x26;str; // Get the type name
77
- fn decorator_span(&#x26;self) -> SpanIR; // Span of @derive decorator
78
- fn as_class(&#x26;self) -> Option&#x3C;&#x26;DataClass>;
79
- fn as_interface(&#x26;self) -> Option&#x3C;&#x26;DataInterface>;
80
- fn as_enum(&#x26;self) -> Option&#x3C;&#x26;DataEnum>;
81
- &#125;
82
-
83
- enum Data &#123;
84
- Class(DataClass),
85
- Interface(DataInterface),
86
- Enum(DataEnum),
87
- TypeAlias(DataTypeAlias),
88
- &#125;
89
-
90
- impl DataClass &#123;
91
- fn fields(&#x26;self) -> &#x26;[FieldIR];
92
- fn methods(&#x26;self) -> &#x26;[MethodSigIR];
93
- fn field_names(&#x26;self) -> impl Iterator&#x3C;Item = &#x26;str>;
94
- fn field(&#x26;self, name: &#x26;str) -> Option&#x3C;&#x26;FieldIR>;
95
- fn body_span(&#x26;self) -> SpanIR; // For inserting code into class body
96
- fn type_params(&#x26;self) -> &#x26;[String]; // Generic type parameters
97
- fn heritage(&#x26;self) -> &#x26;[String]; // extends/implements clauses
98
- fn is_abstract(&#x26;self) -> bool;
99
- &#125;
100
-
101
- impl DataInterface &#123;
102
- fn fields(&#x26;self) -> &#x26;[InterfaceFieldIR];
103
- fn methods(&#x26;self) -> &#x26;[InterfaceMethodIR];
104
- fn field_names(&#x26;self) -> impl Iterator&#x3C;Item = &#x26;str>;
105
- fn field(&#x26;self, name: &#x26;str) -> Option&#x3C;&#x26;InterfaceFieldIR>;
106
- fn body_span(&#x26;self) -> SpanIR;
107
- fn type_params(&#x26;self) -> &#x26;[String];
108
- fn heritage(&#x26;self) -> &#x26;[String]; // extends clauses
109
- &#125;
110
-
111
- impl DataEnum &#123;
112
- fn variants(&#x26;self) -> &#x26;[EnumVariantIR];
113
- fn variant_names(&#x26;self) -> impl Iterator&#x3C;Item = &#x26;str>;
114
- fn variant(&#x26;self, name: &#x26;str) -> Option&#x3C;&#x26;EnumVariantIR>;
115
- &#125;
116
-
117
- impl DataTypeAlias &#123;
118
- fn body(&#x26;self) -> &#x26;TypeBody;
119
- fn type_params(&#x26;self) -> &#x26;[String];
120
- fn is_union(&#x26;self) -> bool;
121
- fn is_object(&#x26;self) -> bool;
122
- fn as_union(&#x26;self) -> Option&#x3C;&#x26;[TypeMember]>;
123
- fn as_object(&#x26;self) -> Option&#x3C;&#x26;[InterfaceFieldIR]>;
124
- &#125;
125
- ``` ## Accessing Field Data
126
- ### Class Fields (FieldIR)
127
- ```
128
- struct FieldIR &#123;
129
- pub name: String, // Field name
130
- pub span: SpanIR, // Field span
131
- pub ts_type: String, // TypeScript type annotation
132
- pub optional: bool, // Whether field has ?
133
- pub readonly: bool, // Whether field is readonly
134
- pub visibility: Visibility, // Public, Protected, Private
135
- pub decorators: Vec&#x3C;DecoratorIR>, // Field decorators
136
- &#125;
137
- ``` ### Interface Fields (InterfaceFieldIR)
138
- ```
139
- struct InterfaceFieldIR &#123;
140
- pub name: String,
141
- pub span: SpanIR,
142
- pub ts_type: String,
143
- pub optional: bool,
144
- pub readonly: bool,
145
- pub decorators: Vec&#x3C;DecoratorIR>,
146
- // Note: No visibility field (interfaces are always public)
147
- &#125;
148
- ``` ### Enum Variants (EnumVariantIR)
149
- ```
150
- struct EnumVariantIR &#123;
151
- pub name: String,
152
- pub span: SpanIR,
153
- pub value: EnumValue, // Auto, String(String), or Number(f64)
154
- pub decorators: Vec&#x3C;DecoratorIR>,
155
- &#125;
156
- ``` ### Decorator Structure
157
- ```
158
- struct DecoratorIR &#123;
159
- pub name: String, // e.g., "serde"
160
- pub args_src: String, // Raw args text, e.g., "skip, rename: 'id'"
161
- pub span: SpanIR,
162
- &#125;
163
- ``` > **Note:** To check for decorators, iterate through field.decorators and check decorator.name. For parsing options, you can write helper functions like the built-in macros do. ## Adding Imports
164
- If your macro generates code that requires imports, use the `add_import` method on `TsStream`:
165
- ```
166
- // Add an import to be inserted at the top of the file
167
- let mut output = body! &#123;
168
- validate(): ValidationResult &#123;
169
- return validateFields(this);
170
- &#125;
171
- &#125;;
172
-
173
- // This will add: import &#123; validateFields, ValidationResult &#125; from "my-validation-lib";
174
- output.add_import("validateFields", "my-validation-lib");
175
- output.add_import("ValidationResult", "my-validation-lib");
88
+ pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
89
+     let input = parse_ts_macro_input!(input as DeriveInput);
90
+
91
+     // Access class data
92
+     match &input.data {
93
+         Data::Class(class) => {
94
+             let class_name = input.name();
95
+             let fields = class.fields();
96
+             // ...
97
+         }
98
+         Data::Interface(interface) => {
99
+             // Handle interfaces
100
+         }
101
+         Data::Enum(_) => {
102
+             // Handle enums (if supported)
103
+         }
104
+     }
105
+ }
106
+ ```
107
+
108
+ ## DeriveInput Structure
109
+
110
+ Rust
111
+
112
+ ```
113
+ struct DeriveInput {
114
+     pub ident: Ident,           // The type name
115
+     pub span: SpanIR,           // Span of the type definition
116
+     pub attrs: Vec<Attribute>,  // Decorators (excluding @derive)
117
+     pub data: Data,             // The parsed type data
118
+     pub context: MacroContextIR, // Macro context with spans
119
+
120
+     // Helper methods
121
+     fn name(&self) -> &str;              // Get the type name
122
+     fn decorator_span(&self) -> SpanIR;  // Span of @derive decorator
123
+     fn as_class(&self) -> Option<&DataClass>;
124
+     fn as_interface(&self) -> Option<&DataInterface>;
125
+     fn as_enum(&self) -> Option<&DataEnum>;
126
+ }
127
+
128
+ enum Data {
129
+     Class(DataClass),
130
+     Interface(DataInterface),
131
+     Enum(DataEnum),
132
+     TypeAlias(DataTypeAlias),
133
+ }
134
+
135
+ impl DataClass {
136
+     fn fields(&self) -> &[FieldIR];
137
+     fn methods(&self) -> &[MethodSigIR];
138
+     fn field_names(&self) -> impl Iterator<Item = &str>;
139
+     fn field(&self, name: &str) -> Option<&FieldIR>;
140
+     fn body_span(&self) -> SpanIR;      // For inserting code into class body
141
+     fn type_params(&self) -> &[String]; // Generic type parameters
142
+     fn heritage(&self) -> &[String];    // extends/implements clauses
143
+     fn is_abstract(&self) -> bool;
144
+ }
145
+
146
+ impl DataInterface {
147
+     fn fields(&self) -> &[InterfaceFieldIR];
148
+     fn methods(&self) -> &[InterfaceMethodIR];
149
+     fn field_names(&self) -> impl Iterator<Item = &str>;
150
+     fn field(&self, name: &str) -> Option<&InterfaceFieldIR>;
151
+     fn body_span(&self) -> SpanIR;
152
+     fn type_params(&self) -> &[String];
153
+     fn heritage(&self) -> &[String];    // extends clauses
154
+ }
155
+
156
+ impl DataEnum {
157
+     fn variants(&self) -> &[EnumVariantIR];
158
+     fn variant_names(&self) -> impl Iterator<Item = &str>;
159
+     fn variant(&self, name: &str) -> Option<&EnumVariantIR>;
160
+ }
161
+
162
+ impl DataTypeAlias {
163
+     fn body(&self) -> &TypeBody;
164
+     fn type_params(&self) -> &[String];
165
+     fn is_union(&self) -> bool;
166
+     fn is_object(&self) -> bool;
167
+     fn as_union(&self) -> Option<&[TypeMember]>;
168
+     fn as_object(&self) -> Option<&[InterfaceFieldIR]>;
169
+ }
170
+ ```
171
+
172
+ ## Accessing Field Data
173
+
174
+ ### Class Fields (FieldIR)
175
+
176
+ Rust
177
+
178
+ ```
179
+ struct FieldIR {
180
+     pub name: String,               // Field name
181
+     pub span: SpanIR,               // Field span
182
+     pub ts_type: String,            // TypeScript type annotation
183
+     pub optional: bool,             // Whether field has ?
184
+     pub readonly: bool,             // Whether field is readonly
185
+     pub visibility: Visibility,     // Public, Protected, Private
186
+     pub decorators: Vec<DecoratorIR>, // Field decorators
187
+ }
188
+ ```
189
+
190
+ ### Interface Fields (InterfaceFieldIR)
191
+
192
+ Rust
193
+
194
+ ```
195
+ struct InterfaceFieldIR {
196
+     pub name: String,
197
+     pub span: SpanIR,
198
+     pub ts_type: String,
199
+     pub optional: bool,
200
+     pub readonly: bool,
201
+     pub decorators: Vec<DecoratorIR>,
202
+     // Note: No visibility field (interfaces are always public)
203
+ }
204
+ ```
205
+
206
+ ### Enum Variants (EnumVariantIR)
207
+
208
+ Rust
209
+
210
+ ```
211
+ struct EnumVariantIR {
212
+     pub name: String,
213
+     pub span: SpanIR,
214
+     pub value: EnumValue,  // Auto, String(String), or Number(f64)
215
+     pub decorators: Vec<DecoratorIR>,
216
+ }
217
+ ```
218
+
219
+ ### Decorator Structure
220
+
221
+ Rust
222
+
223
+ ```
224
+ struct DecoratorIR {
225
+     pub name: String,      // e.g., "serde"
226
+     pub args_src: String,  // Raw args text, e.g., "skip, rename: 'id'"
227
+     pub span: SpanIR,
228
+ }
229
+ ```
230
+
231
+ Note
232
+
233
+ To check for decorators, iterate through `field.decorators` and check `decorator.name`. For parsing options, you can write helper functions like the built-in macros do.
234
+
235
+ ## Adding Imports
236
+
237
+ If your macro generates code that requires imports, use the `add_import` method on `TsStream`:
238
+
239
+ Rust
240
+
241
+ ```
242
+ // Add an import to be inserted at the top of the file
243
+ let mut output = body! {
244
+     validate(): ValidationResult {
245
+         return validateFields(this);
246
+     }
247
+ };
248
+
249
+ // This will add: import { validateFields, ValidationResult } from "my-validation-lib";
250
+ output.add_import("validateFields", "my-validation-lib");
251
+ output.add_import("ValidationResult", "my-validation-lib");
176
252
 
177
253
  Ok(output)
178
- ``` > **Note:** Imports are automatically deduplicated. If the same import already exists in the file, it won't be added again. ## Returning Errors
179
- Use `MacroforgeError` to report errors with source locations:
180
- ```
254
+ ```
255
+
256
+ Note
257
+
258
+ Imports are automatically deduplicated. If the same import already exists in the file, it won't be added again.
259
+
260
+ ## Returning Errors
261
+
262
+ Use `MacroforgeError` to report errors with source locations:
263
+
264
+ Rust
265
+
266
+ ```
181
267
  #[ts_macro_derive(ClassOnly)]
182
- pub fn class_only(mut input: TsStream) -> Result&#x3C;TsStream, MacroforgeError> &#123;
183
- let input = parse_ts_macro_input!(input as DeriveInput);
184
-
185
- match &#x26;input.data &#123;
186
- Data::Class(_) => &#123;
187
- // Generate code...
188
- Ok(body! &#123; /* ... */ &#125;)
189
- &#125;
190
- _ => Err(MacroforgeError::new(
191
- input.decorator_span(),
192
- "@derive(ClassOnly) can only be used on classes",
193
- )),
194
- &#125;
195
- &#125;
196
- ``` ## Complete Example
197
- ```
198
- use macroforge_ts::macros::&#123;ts_macro_derive, body&#125;;
199
- use macroforge_ts::ts_syn::&#123;
200
- Data, DeriveInput, FieldIR, MacroforgeError, TsStream, parse_ts_macro_input,
201
- &#125;;
202
-
203
- // Helper function to check if a field has a decorator
204
- fn has_decorator(field: &#x26;FieldIR, name: &#x26;str) -> bool &#123;
205
- field.decorators.iter().any(|d| d.name.eq_ignore_ascii_case(name))
206
- &#125;
268
+ pub fn class_only(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
269
+     let input = parse_ts_macro_input!(input as DeriveInput);
270
+
271
+     match &input.data {
272
+         Data::Class(_) => {
273
+             // Generate code...
274
+             Ok(body! { /* ... */ })
275
+         }
276
+         _ => Err(MacroforgeError::new(
277
+             input.decorator_span(),
278
+             "@derive(ClassOnly) can only be used on classes",
279
+         )),
280
+     }
281
+ }
282
+ ```
283
+
284
+ ## Complete Example
285
+
286
+ Rust
287
+
288
+ ```
289
+ use macroforge_ts::macros::{ts_macro_derive, body};
290
+ use macroforge_ts::ts_syn::{
291
+     Data, DeriveInput, FieldIR, MacroforgeError, TsStream, parse_ts_macro_input,
292
+ };
293
+
294
+ // Helper function to check if a field has a decorator
295
+ fn has_decorator(field: &FieldIR, name: &str) -> bool {
296
+     field.decorators.iter().any(|d| d.name.eq_ignore_ascii_case(name))
297
+ }
207
298
 
208
299
  #[ts_macro_derive(
209
- Validate,
210
- description = "Generates a validate() method",
211
- attributes(validate)
300
+     Validate,
301
+     description = "Generates a validate() method",
302
+     attributes(validate)
212
303
  )]
213
- pub fn derive_validate(mut input: TsStream) -> Result&#x3C;TsStream, MacroforgeError> &#123;
214
- let input = parse_ts_macro_input!(input as DeriveInput);
215
-
216
- match &#x26;input.data &#123;
217
- Data::Class(class) => &#123;
218
- let validations: Vec&#x3C;_> = class.fields()
219
- .iter()
220
- .filter(|f| has_decorator(f, "validate"))
221
- .collect();
222
-
223
- Ok(body! &#123;
224
- validate(): string[] &#123;
225
- const errors: string[] = [];
226
- &#123;#for field in validations&#125;
227
- if (!this.@&#123;field.name&#125;) &#123;
228
- errors.push("@&#123;field.name&#125; is required");
229
- &#125;
230
- &#123;/for&#125;
231
- return errors;
232
- &#125;
233
- &#125;)
234
- &#125;
235
- _ => Err(MacroforgeError::new(
236
- input.decorator_span(),
237
- "@derive(Validate) only works on classes",
238
- )),
239
- &#125;
240
- &#125;
241
- ``` ## Next Steps
242
- - [Learn the template syntax](../../docs/custom-macros/ts-quote)
304
+ pub fn derive_validate(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
305
+     let input = parse_ts_macro_input!(input as DeriveInput);
306
+
307
+     match &input.data {
308
+         Data::Class(class) => {
309
+             let validations: Vec<_> = class.fields()
310
+                 .iter()
311
+                 .filter(|f| has_decorator(f, "validate"))
312
+                 .collect();
313
+
314
+             Ok(body! {
315
+                 validate(): string[] {
316
+                     const errors: string[] = [];
317
+                     {#for field in validations}
318
+                         if (!this.@{field.name}) {
319
+                             errors.push("@{field.nameis required");
320
+                         }
321
+                     {/for}
322
+                     return errors;
323
+                 }
324
+             })
325
+         }
326
+         _ => Err(MacroforgeError::new(
327
+             input.decorator_span(),
328
+             "@derive(Validate) only works on classes",
329
+         )),
330
+     }
331
+ }
332
+ ```
333
+
334
+ ## Next Steps
335
+
336
+ * [Learn the template syntax](../../docs/custom-macros/ts-quote)
@@ -2,28 +2,40 @@
2
2
 
3
3
  For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through `${"${}"}` for JS interpolation:
4
4
 
5
- ```rust
5
+ Rust
6
+
7
+ ```
6
8
  let tag_name = "div";
7
9
 
8
10
  let code = ts_template! {
9
- const html = "'^<@{tag_name}>\${content}</@{tag_name}>^'";
11
+ const html = "'^<@{tag_name}>${content}</@{tag_name}>^'";
10
12
  };
11
13
  ```
12
14
 
13
15
  **Generates:**
14
16
 
15
- <CodeBlock code={'const html = `${content}`;'} lang="typescript" />
17
+ TypeScript
18
+
19
+ ```
20
+ const html = `<div>${content}</div>`;
21
+ ```
16
22
 
17
- You can mix Rust `@&#123;&#125;` interpolation (evaluated at macro expansion time) with JS `${"${}"}` interpolation (evaluated at runtime):
23
+ You can mix Rust `@{}` interpolation (evaluated at macro expansion time) with JS `${"${}"}` interpolation (evaluated at runtime):
18
24
 
19
- ```rust
25
+ Rust
26
+
27
+ ```
20
28
  let class_name = "User";
21
29
 
22
30
  let code = ts_template! {
23
- "'^Hello \${this.name}, you are a @{class_name}^'"
31
+ "'^Hello ${this.name}, you are a @{class_name}^'"
24
32
  };
25
33
  ```
26
34
 
27
35
  **Generates:**
28
36
 
29
- <CodeBlock code={'`Hello ${this.name}, you are a User`'} lang="typescript" />
37
+ TypeScript
38
+
39
+ ```
40
+ `Hello ${this.name}, you are a User`
41
+ ```