@macroforge/mcp-server 0.1.25 → 0.1.27
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/dist/tools/docs-loader.d.ts +3 -0
- package/dist/tools/docs-loader.d.ts.map +1 -1
- package/dist/tools/docs-loader.js +4 -0
- package/dist/tools/docs-loader.js.map +1 -1
- package/dist/tools/index.js +60 -3
- package/dist/tools/index.js.map +1 -1
- package/docs/builtin-macros/deserialize/all-options.md +33 -0
- package/docs/builtin-macros/deserialize/combining-with-serialize.md +27 -0
- package/docs/builtin-macros/deserialize/enum-support.md +31 -0
- package/docs/builtin-macros/deserialize/error-handling.md +24 -0
- package/docs/builtin-macros/deserialize/interface-support.md +18 -0
- package/docs/builtin-macros/deserialize/overview.md +16 -0
- package/docs/builtin-macros/deserialize/runtime-validation.md +51 -0
- package/docs/builtin-macros/deserialize/serde-options.md +83 -0
- package/docs/builtin-macros/deserialize/type-alias-support.md +26 -0
- package/docs/custom-macros/rust-setup.md +6 -6
- package/docs/custom-macros/ts-macro-derive/accessing-field-data.md +53 -0
- package/docs/custom-macros/ts-macro-derive/adding-imports.md +21 -0
- package/docs/custom-macros/ts-macro-derive/attribute-options.md +50 -0
- package/docs/custom-macros/ts-macro-derive/complete-example.md +51 -0
- package/docs/custom-macros/ts-macro-derive/deriveinput-structure.md +61 -0
- package/docs/custom-macros/ts-macro-derive/overview.md +15 -0
- package/docs/custom-macros/ts-macro-derive/parsing-input.md +27 -0
- package/docs/custom-macros/ts-macro-derive/returning-errors.md +21 -0
- package/docs/custom-macros/ts-quote/backtick-template-literals.md +29 -0
- package/docs/custom-macros/ts-quote/comments-and.md +66 -0
- package/docs/custom-macros/ts-quote/complete-example-json-derive-macro.md +131 -0
- package/docs/custom-macros/ts-quote/conditionals-ifif.md +46 -0
- package/docs/custom-macros/ts-quote/identifier-concatenation-content.md +42 -0
- package/docs/custom-macros/ts-quote/iteration-for.md +60 -0
- package/docs/custom-macros/ts-quote/match-expressions-match.md +58 -0
- package/docs/custom-macros/ts-quote/overview.md +13 -0
- package/docs/custom-macros/ts-quote/pattern-matching-if-let.md +33 -0
- package/docs/custom-macros/ts-quote/quick-reference.md +83 -0
- package/docs/custom-macros/ts-quote/side-effects-do.md +23 -0
- package/docs/custom-macros/ts-quote/string-interpolation-text-expr.md +30 -0
- package/docs/custom-macros/ts-quote/tsstream-injection-typescript.md +61 -0
- package/docs/custom-macros/ts-quote/while-loops-while.md +81 -0
- package/docs/sections.json +322 -3
- package/package.json +2 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## Attribute Options
|
|
2
|
+
|
|
3
|
+
### Name (Required)
|
|
4
|
+
|
|
5
|
+
The first argument is the macro name that users will reference in `@derive()`:
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
#[ts_macro_derive(JSON)] // Users write: @derive(JSON)
|
|
9
|
+
pub fn derive_json(...)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Description
|
|
13
|
+
|
|
14
|
+
Provides documentation for the macro:
|
|
15
|
+
|
|
16
|
+
```rust
|
|
17
|
+
#[ts_macro_derive(
|
|
18
|
+
JSON,
|
|
19
|
+
description = "Generates toJSON() returning a plain object"
|
|
20
|
+
)]
|
|
21
|
+
pub fn derive_json(...)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Attributes
|
|
25
|
+
|
|
26
|
+
Declare which field-level decorators your macro accepts:
|
|
27
|
+
|
|
28
|
+
```rust
|
|
29
|
+
#[ts_macro_derive(
|
|
30
|
+
Debug,
|
|
31
|
+
description = "Generates toString()",
|
|
32
|
+
attributes(debug) // Allows @debug({ ... }) on fields
|
|
33
|
+
)]
|
|
34
|
+
pub fn derive_debug(...)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
>
|
|
38
|
+
> Declared attributes become available as `@attributeName({ options })` decorators in TypeScript.
|
|
39
|
+
|
|
40
|
+
## Function Signature
|
|
41
|
+
|
|
42
|
+
```rust
|
|
43
|
+
pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| `input: TsStream`
|
|
47
|
+
| Token stream containing the class/interface AST
|
|
48
|
+
|
|
49
|
+
| `Result<TsStream, MacroforgeError>`
|
|
50
|
+
| Returns generated code or an error with source location
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
## Complete Example
|
|
2
|
+
|
|
3
|
+
```rust
|
|
4
|
+
use macroforge_ts::macros::{ts_macro_derive, body};
|
|
5
|
+
use macroforge_ts::ts_syn::{
|
|
6
|
+
Data, DeriveInput, FieldIR, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Helper function to check if a field has a decorator
|
|
10
|
+
fn has_decorator(field: &FieldIR, name: &str) -> bool {
|
|
11
|
+
field.decorators.iter().any(|d| d.name.eq_ignore_ascii_case(name))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#[ts_macro_derive(
|
|
15
|
+
Validate,
|
|
16
|
+
description = "Generates a validate() method",
|
|
17
|
+
attributes(validate)
|
|
18
|
+
)]
|
|
19
|
+
pub fn derive_validate(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
20
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
21
|
+
|
|
22
|
+
match &input.data {
|
|
23
|
+
Data::Class(class) => {
|
|
24
|
+
let validations: Vec<_> = class.fields()
|
|
25
|
+
.iter()
|
|
26
|
+
.filter(|f| has_decorator(f, "validate"))
|
|
27
|
+
.collect();
|
|
28
|
+
|
|
29
|
+
Ok(body! {
|
|
30
|
+
validate(): string[] {
|
|
31
|
+
const errors: string[] = [];
|
|
32
|
+
{#for field in validations}
|
|
33
|
+
if (!this.@{field.name}) {
|
|
34
|
+
errors.push("@{field.name} is required");
|
|
35
|
+
}
|
|
36
|
+
{/for}
|
|
37
|
+
return errors;
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
_ => Err(MacroforgeError::new(
|
|
42
|
+
input.decorator_span(),
|
|
43
|
+
"@derive(Validate) only works on classes",
|
|
44
|
+
)),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Next Steps
|
|
50
|
+
|
|
51
|
+
- [Learn the template syntax]({base}/docs/custom-macros/ts-quote)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## DeriveInput Structure
|
|
2
|
+
|
|
3
|
+
```rust
|
|
4
|
+
struct DeriveInput {
|
|
5
|
+
pub ident: Ident, // The type name
|
|
6
|
+
pub span: SpanIR, // Span of the type definition
|
|
7
|
+
pub attrs: Vec<Attribute>, // Decorators (excluding @derive)
|
|
8
|
+
pub data: Data, // The parsed type data
|
|
9
|
+
pub context: MacroContextIR, // Macro context with spans
|
|
10
|
+
|
|
11
|
+
// Helper methods
|
|
12
|
+
fn name(&self) -> &str; // Get the type name
|
|
13
|
+
fn decorator_span(&self) -> SpanIR; // Span of @derive decorator
|
|
14
|
+
fn as_class(&self) -> Option<&DataClass>;
|
|
15
|
+
fn as_interface(&self) -> Option<&DataInterface>;
|
|
16
|
+
fn as_enum(&self) -> Option<&DataEnum>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
enum Data {
|
|
20
|
+
Class(DataClass),
|
|
21
|
+
Interface(DataInterface),
|
|
22
|
+
Enum(DataEnum),
|
|
23
|
+
TypeAlias(DataTypeAlias),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl DataClass {
|
|
27
|
+
fn fields(&self) -> &[FieldIR];
|
|
28
|
+
fn methods(&self) -> &[MethodSigIR];
|
|
29
|
+
fn field_names(&self) -> impl Iterator<Item = &str>;
|
|
30
|
+
fn field(&self, name: &str) -> Option<&FieldIR>;
|
|
31
|
+
fn body_span(&self) -> SpanIR; // For inserting code into class body
|
|
32
|
+
fn type_params(&self) -> &[String]; // Generic type parameters
|
|
33
|
+
fn heritage(&self) -> &[String]; // extends/implements clauses
|
|
34
|
+
fn is_abstract(&self) -> bool;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl DataInterface {
|
|
38
|
+
fn fields(&self) -> &[InterfaceFieldIR];
|
|
39
|
+
fn methods(&self) -> &[InterfaceMethodIR];
|
|
40
|
+
fn field_names(&self) -> impl Iterator<Item = &str>;
|
|
41
|
+
fn field(&self, name: &str) -> Option<&InterfaceFieldIR>;
|
|
42
|
+
fn body_span(&self) -> SpanIR;
|
|
43
|
+
fn type_params(&self) -> &[String];
|
|
44
|
+
fn heritage(&self) -> &[String]; // extends clauses
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl DataEnum {
|
|
48
|
+
fn variants(&self) -> &[EnumVariantIR];
|
|
49
|
+
fn variant_names(&self) -> impl Iterator<Item = &str>;
|
|
50
|
+
fn variant(&self, name: &str) -> Option<&EnumVariantIR>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl DataTypeAlias {
|
|
54
|
+
fn body(&self) -> &TypeBody;
|
|
55
|
+
fn type_params(&self) -> &[String];
|
|
56
|
+
fn is_union(&self) -> bool;
|
|
57
|
+
fn is_object(&self) -> bool;
|
|
58
|
+
fn as_union(&self) -> Option<&[TypeMember]>;
|
|
59
|
+
fn as_object(&self) -> Option<&[InterfaceFieldIR]>;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
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::macros::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
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Parsing Input
|
|
2
|
+
|
|
3
|
+
Use `parse_ts_macro_input!` to convert the token stream:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
use macroforge_ts::ts_syn::{Data, DeriveInput, parse_ts_macro_input};
|
|
7
|
+
|
|
8
|
+
#[ts_macro_derive(MyMacro)]
|
|
9
|
+
pub fn my_macro(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
10
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
11
|
+
|
|
12
|
+
// Access class data
|
|
13
|
+
match &input.data {
|
|
14
|
+
Data::Class(class) => {
|
|
15
|
+
let class_name = input.name();
|
|
16
|
+
let fields = class.fields();
|
|
17
|
+
// ...
|
|
18
|
+
}
|
|
19
|
+
Data::Interface(interface) => {
|
|
20
|
+
// Handle interfaces
|
|
21
|
+
}
|
|
22
|
+
Data::Enum(_) => {
|
|
23
|
+
// Handle enums (if supported)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Returning Errors
|
|
2
|
+
|
|
3
|
+
Use `MacroforgeError` to report errors with source locations:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
#[ts_macro_derive(ClassOnly)]
|
|
7
|
+
pub fn class_only(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
8
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
9
|
+
|
|
10
|
+
match &input.data {
|
|
11
|
+
Data::Class(_) => {
|
|
12
|
+
// Generate code...
|
|
13
|
+
Ok(body! { /* ... */ })
|
|
14
|
+
}
|
|
15
|
+
_ => Err(MacroforgeError::new(
|
|
16
|
+
input.decorator_span(),
|
|
17
|
+
"@derive(ClassOnly) can only be used on classes",
|
|
18
|
+
)),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Backtick Template Literals: `"'^...^'"`
|
|
2
|
+
|
|
3
|
+
For JavaScript template literals (backtick strings), use the `'^...^'` syntax. This outputs actual backticks and passes through `${"${}"}` for JS interpolation:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
let tag_name = "div";
|
|
7
|
+
|
|
8
|
+
let code = ts_template! {
|
|
9
|
+
const html = "'^<@{tag_name}>\${content}</@{tag_name}>^'";
|
|
10
|
+
};
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Generates:**
|
|
14
|
+
|
|
15
|
+
<CodeBlock code={'const html = `${content}`;'} lang="typescript" />
|
|
16
|
+
|
|
17
|
+
You can mix Rust `@{}` interpolation (evaluated at macro expansion time) with JS `${"${}"}` interpolation (evaluated at runtime):
|
|
18
|
+
|
|
19
|
+
```rust
|
|
20
|
+
let class_name = "User";
|
|
21
|
+
|
|
22
|
+
let code = ts_template! {
|
|
23
|
+
"'^Hello \${this.name}, you are a @{class_name}^'"
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Generates:**
|
|
28
|
+
|
|
29
|
+
<CodeBlock code={'`Hello ${this.name}, you are a User`'} lang="typescript" />
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
## Comments: `{> ... <}` and `{>> ... <<}`
|
|
2
|
+
|
|
3
|
+
Since Rust's tokenizer strips comments before macros see them, you can't write JSDoc comments directly. Instead, use the comment syntax to output JavaScript comments:
|
|
4
|
+
|
|
5
|
+
### Block Comments
|
|
6
|
+
|
|
7
|
+
Use `{> comment <}` for block comments:
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
let code = ts_template! {
|
|
11
|
+
{> This is a block comment <}
|
|
12
|
+
const x = 42;
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Generates:**
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
/* This is a block comment */
|
|
20
|
+
const x = 42;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Doc Comments (JSDoc)
|
|
24
|
+
|
|
25
|
+
Use `{>> doc <<}` for JSDoc comments:
|
|
26
|
+
|
|
27
|
+
```rust
|
|
28
|
+
let code = ts_template! {
|
|
29
|
+
{>> @param {string} name - The user's name <<}
|
|
30
|
+
{>> @returns {string} A greeting message <<}
|
|
31
|
+
function greet(name: string): string {
|
|
32
|
+
return "Hello, " + name;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Generates:**
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
/** @param {string} name - The user's name */
|
|
41
|
+
/** @returns {string} A greeting message */
|
|
42
|
+
function greet(name: string): string {
|
|
43
|
+
return "Hello, " + name;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Comments with Interpolation
|
|
48
|
+
|
|
49
|
+
Comments support `@{expr}` interpolation for dynamic content:
|
|
50
|
+
|
|
51
|
+
```rust
|
|
52
|
+
let param_name = "userId";
|
|
53
|
+
let param_type = "number";
|
|
54
|
+
|
|
55
|
+
let code = ts_template! {
|
|
56
|
+
{>> @param {@{param_type}} @{param_name} - The user ID <<}
|
|
57
|
+
function getUser(userId: number) {}
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Generates:**
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
/** @param {number} userId - The user ID */
|
|
65
|
+
function getUser(userId: number) {}
|
|
66
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
## Complete Example: JSON Derive Macro
|
|
2
|
+
|
|
3
|
+
Here's a comparison showing how `ts_template!` simplifies code generation:
|
|
4
|
+
|
|
5
|
+
### Before (Manual AST Building)
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
9
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
10
|
+
|
|
11
|
+
match &input.data {
|
|
12
|
+
Data::Class(class) => {
|
|
13
|
+
let class_name = input.name();
|
|
14
|
+
|
|
15
|
+
let mut body_stmts = vec![ts_quote!( const result = {}; as Stmt )];
|
|
16
|
+
|
|
17
|
+
for field_name in class.field_names() {
|
|
18
|
+
body_stmts.push(ts_quote!(
|
|
19
|
+
result.$(ident!("{}", field_name)) = this.$(ident!("{}", field_name));
|
|
20
|
+
as Stmt
|
|
21
|
+
));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body_stmts.push(ts_quote!( return result; as Stmt ));
|
|
25
|
+
|
|
26
|
+
let runtime_code = fn_assign!(
|
|
27
|
+
member_expr!(Expr::Ident(ident!(class_name)), "prototype"),
|
|
28
|
+
"toJSON",
|
|
29
|
+
body_stmts
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// ...
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### After (With ts_template!)
|
|
39
|
+
|
|
40
|
+
```rust
|
|
41
|
+
pub fn derive_json_macro(input: TsStream) -> MacroResult {
|
|
42
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
43
|
+
|
|
44
|
+
match &input.data {
|
|
45
|
+
Data::Class(class) => {
|
|
46
|
+
let class_name = input.name();
|
|
47
|
+
let fields = class.field_names();
|
|
48
|
+
|
|
49
|
+
let runtime_code = ts_template! {
|
|
50
|
+
@{class_name}.prototype.toJSON = function() {
|
|
51
|
+
const result = {};
|
|
52
|
+
{#for field in fields}
|
|
53
|
+
result.@{field} = this.@{field};
|
|
54
|
+
{/for}
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ...
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## How It Works
|
|
66
|
+
|
|
67
|
+
1. **Compile-Time:** The template is parsed during macro expansion
|
|
68
|
+
|
|
69
|
+
2. **String Building:** Generates Rust code that builds a TypeScript string at runtime
|
|
70
|
+
|
|
71
|
+
3. **SWC Parsing:** The generated string is parsed with SWC to produce a typed AST
|
|
72
|
+
|
|
73
|
+
4. **Result:** Returns `Stmt` that can be used in `MacroResult` patches
|
|
74
|
+
|
|
75
|
+
## Return Type
|
|
76
|
+
|
|
77
|
+
`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:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
Failed to parse generated TypeScript:
|
|
81
|
+
User.prototype.toJSON = function( {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This shows you exactly what was generated, making debugging easy!
|
|
87
|
+
|
|
88
|
+
## Nesting and Regular TypeScript
|
|
89
|
+
|
|
90
|
+
You can mix template syntax with regular TypeScript. Braces `{}` are recognized as either:
|
|
91
|
+
|
|
92
|
+
- **Template tags** if they start with `#`, `$`, `:`, or `/`
|
|
93
|
+
|
|
94
|
+
- **Regular TypeScript blocks** otherwise
|
|
95
|
+
|
|
96
|
+
```rust
|
|
97
|
+
ts_template! {
|
|
98
|
+
const config = {
|
|
99
|
+
{#if use_strict}
|
|
100
|
+
strict: true,
|
|
101
|
+
{:else}
|
|
102
|
+
strict: false,
|
|
103
|
+
{/if}
|
|
104
|
+
timeout: 5000
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Comparison with Alternatives
|
|
110
|
+
|
|
111
|
+
| `ts_quote!`
|
|
112
|
+
| Compile-time validation, type-safe
|
|
113
|
+
| Can't handle Vec<Stmt>, verbose
|
|
114
|
+
|
|
115
|
+
| `parse_ts_str()`
|
|
116
|
+
| Maximum flexibility
|
|
117
|
+
| Runtime parsing, less readable
|
|
118
|
+
|
|
119
|
+
| `ts_template!`
|
|
120
|
+
| Readable, handles loops/conditions
|
|
121
|
+
| Small runtime parsing overhead
|
|
122
|
+
|
|
123
|
+
## Best Practices
|
|
124
|
+
|
|
125
|
+
1. Use `ts_template!` for complex code generation with loops/conditions
|
|
126
|
+
|
|
127
|
+
2. Use `ts_quote!` for simple, static statements
|
|
128
|
+
|
|
129
|
+
3. Keep templates readable - extract complex logic into variables
|
|
130
|
+
|
|
131
|
+
4. Don't nest templates too deeply - split into helper functions
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## Conditionals: `{#if}...{/if}`
|
|
2
|
+
|
|
3
|
+
Basic conditional:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
let needs_validation = true;
|
|
7
|
+
|
|
8
|
+
let code = ts_template! {
|
|
9
|
+
function save() {
|
|
10
|
+
{#if needs_validation}
|
|
11
|
+
if (!this.isValid()) return false;
|
|
12
|
+
{/if}
|
|
13
|
+
return this.doSave();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### If-Else
|
|
19
|
+
|
|
20
|
+
```rust
|
|
21
|
+
let has_default = true;
|
|
22
|
+
|
|
23
|
+
let code = ts_template! {
|
|
24
|
+
{#if has_default}
|
|
25
|
+
return defaultValue;
|
|
26
|
+
{:else}
|
|
27
|
+
throw new Error("No default");
|
|
28
|
+
{/if}
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### If-Else-If Chains
|
|
33
|
+
|
|
34
|
+
```rust
|
|
35
|
+
let level = 2;
|
|
36
|
+
|
|
37
|
+
let code = ts_template! {
|
|
38
|
+
{#if level == 1}
|
|
39
|
+
console.log("Level 1");
|
|
40
|
+
{:else if level == 2}
|
|
41
|
+
console.log("Level 2");
|
|
42
|
+
{:else}
|
|
43
|
+
console.log("Other level");
|
|
44
|
+
{/if}
|
|
45
|
+
};
|
|
46
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Identifier Concatenation: `{| content |}`
|
|
2
|
+
|
|
3
|
+
When you need to build identifiers dynamically (like `getUser`, `setName`), use the ident block syntax. Everything inside `{| |}` is concatenated without spaces:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
let field_name = "User";
|
|
7
|
+
|
|
8
|
+
let code = ts_template! {
|
|
9
|
+
function {|get@{field_name}|}() {
|
|
10
|
+
return this.@{field_name.to_lowercase()};
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Generates:**
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
function getUser() {
|
|
19
|
+
return this.user;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Without ident blocks, `@{}` always adds a space after for readability. Use `{| |}` when you explicitly want concatenation:
|
|
24
|
+
|
|
25
|
+
```rust
|
|
26
|
+
let name = "Status";
|
|
27
|
+
|
|
28
|
+
// With space (default behavior)
|
|
29
|
+
ts_template! { namespace @{name} } // → "namespace Status"
|
|
30
|
+
|
|
31
|
+
// Without space (ident block)
|
|
32
|
+
ts_template! { {|namespace@{name}|} } // → "namespaceStatus"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Multiple interpolations can be combined:
|
|
36
|
+
|
|
37
|
+
```rust
|
|
38
|
+
let entity = "user";
|
|
39
|
+
let action = "create";
|
|
40
|
+
|
|
41
|
+
ts_template! { {|@{entity}_@{action}|} } // → "user_create"
|
|
42
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## Iteration: `{#for}`
|
|
2
|
+
|
|
3
|
+
```rust
|
|
4
|
+
let fields = vec!["name", "email", "age"];
|
|
5
|
+
|
|
6
|
+
let code = ts_template! {
|
|
7
|
+
function toJSON() {
|
|
8
|
+
const result = {};
|
|
9
|
+
{#for field in fields}
|
|
10
|
+
result.@{field} = this.@{field};
|
|
11
|
+
{/for}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Generates:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
function toJSON() {
|
|
21
|
+
const result = {};
|
|
22
|
+
result.name = this.name;
|
|
23
|
+
result.email = this.email;
|
|
24
|
+
result.age = this.age;
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Tuple Destructuring in Loops
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
let items = vec![("user", "User"), ("post", "Post")];
|
|
33
|
+
|
|
34
|
+
let code = ts_template! {
|
|
35
|
+
{#for (key, class_name) in items}
|
|
36
|
+
const @{key} = new @{class_name}();
|
|
37
|
+
{/for}
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Nested Iterations
|
|
42
|
+
|
|
43
|
+
```rust
|
|
44
|
+
let classes = vec![
|
|
45
|
+
("User", vec!["name", "email"]),
|
|
46
|
+
("Post", vec!["title", "content"]),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
ts_template! {
|
|
50
|
+
{#for (class_name, fields) in classes}
|
|
51
|
+
@{class_name}.prototype.toJSON = function() {
|
|
52
|
+
return {
|
|
53
|
+
{#for field in fields}
|
|
54
|
+
@{field}: this.@{field},
|
|
55
|
+
{/for}
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
{/for}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## Match Expressions: `{#match}`
|
|
2
|
+
|
|
3
|
+
Use `match` for exhaustive pattern matching:
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
enum Visibility { Public, Private, Protected }
|
|
7
|
+
let visibility = Visibility::Public;
|
|
8
|
+
|
|
9
|
+
let code = ts_template! {
|
|
10
|
+
{#match visibility}
|
|
11
|
+
{:case Visibility::Public}
|
|
12
|
+
public
|
|
13
|
+
{:case Visibility::Private}
|
|
14
|
+
private
|
|
15
|
+
{:case Visibility::Protected}
|
|
16
|
+
protected
|
|
17
|
+
{/match}
|
|
18
|
+
field: string;
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Generates:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
public field: string;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Match with Value Extraction
|
|
29
|
+
|
|
30
|
+
```rust
|
|
31
|
+
let result: Result<i32, &str> = Ok(42);
|
|
32
|
+
|
|
33
|
+
let code = ts_template! {
|
|
34
|
+
const value = {#match result}
|
|
35
|
+
{:case Ok(val)}
|
|
36
|
+
@{val}
|
|
37
|
+
{:case Err(msg)}
|
|
38
|
+
throw new Error("@{msg}")
|
|
39
|
+
{/match};
|
|
40
|
+
};
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Match with Wildcard
|
|
44
|
+
|
|
45
|
+
```rust
|
|
46
|
+
let count = 5;
|
|
47
|
+
|
|
48
|
+
let code = ts_template! {
|
|
49
|
+
{#match count}
|
|
50
|
+
{:case 0}
|
|
51
|
+
console.log("none");
|
|
52
|
+
{:case 1}
|
|
53
|
+
console.log("one");
|
|
54
|
+
{:case _}
|
|
55
|
+
console.log("many");
|
|
56
|
+
{/match}
|
|
57
|
+
};
|
|
58
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Template Syntax
|
|
2
|
+
|
|
3
|
+
*The `macroforge_ts_quote` crate provides template-based code generation for TypeScript. The `ts_template!` macro uses Rust-inspired syntax for control flow and interpolation, making it easy to generate complex TypeScript code.*
|
|
4
|
+
|
|
5
|
+
## Available Macros
|
|
6
|
+
|
|
7
|
+
| `ts_template!`
|
|
8
|
+
| Any TypeScript code
|
|
9
|
+
| General code generation
|
|
10
|
+
|
|
11
|
+
| `body!`
|
|
12
|
+
| Class body members
|
|
13
|
+
| Methods and properties
|