@macroforge/mcp-server 0.1.17

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