@macroforge/mcp-server 0.1.42 → 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.
- package/LICENSE +22 -0
- package/docs/BOOK.md +165 -0
- package/docs/api/api-overview.md +67 -48
- package/docs/api/expand-sync.md +88 -53
- package/docs/api/native-plugin.md +121 -71
- package/docs/api/position-mapper.md +115 -55
- package/docs/api/transform-sync.md +86 -60
- package/docs/builtin-macros/clone.md +0 -20
- package/docs/builtin-macros/debug.md +0 -23
- package/docs/builtin-macros/default.md +1 -40
- package/docs/builtin-macros/deserialize/example.md +8 -1416
- package/docs/builtin-macros/deserialize.md +8 -1416
- package/docs/builtin-macros/hash.md +0 -42
- package/docs/builtin-macros/macros-overview/detailed-documentation.md +13 -0
- package/docs/builtin-macros/macros-overview/enum-support.md +30 -0
- package/docs/builtin-macros/macros-overview/interface-support.md +28 -0
- package/docs/builtin-macros/macros-overview/overview.md +36 -0
- package/docs/builtin-macros/macros-overview/type-alias-support.md +62 -0
- package/docs/builtin-macros/macros-overview.md +171 -130
- package/docs/builtin-macros/ord.md +0 -25
- package/docs/builtin-macros/partial-eq.md +0 -84
- package/docs/builtin-macros/partial-ord.md +2 -32
- package/docs/builtin-macros/serialize.md +2 -62
- package/docs/concepts/architecture.md +125 -48
- package/docs/concepts/derive-system/built-in-vs-custom-macros.md +13 -0
- package/docs/concepts/derive-system/overview.md +200 -0
- package/docs/concepts/derive-system.md +157 -104
- package/docs/concepts/how-macros-work.md +98 -47
- package/docs/custom-macros/custom-overview.md +79 -57
- package/docs/custom-macros/rust-setup.md +138 -99
- package/docs/custom-macros/ts-macro-derive/accessing-field-data.md +40 -31
- package/docs/custom-macros/ts-macro-derive/adding-imports.md +14 -11
- package/docs/custom-macros/ts-macro-derive/attribute-options.md +20 -25
- package/docs/custom-macros/ts-macro-derive/complete-example.md +40 -38
- package/docs/custom-macros/ts-macro-derive/deriveinput-structure.md +49 -47
- package/docs/custom-macros/ts-macro-derive/function-signature.md +12 -0
- package/docs/custom-macros/ts-macro-derive/overview.md +9 -7
- package/docs/custom-macros/ts-macro-derive/parsing-input.md +20 -18
- package/docs/custom-macros/ts-macro-derive/returning-errors.md +15 -13
- package/docs/custom-macros/ts-macro-derive.md +322 -228
- package/docs/custom-macros/ts-quote/backtick-template-literals.md +19 -7
- package/docs/custom-macros/ts-quote/comments-and.md +56 -22
- package/docs/custom-macros/ts-quote/complete-example-json-derive-macro.md +89 -98
- package/docs/custom-macros/ts-quote/conditionals-ifif.md +35 -29
- package/docs/custom-macros/ts-quote/identifier-concatenation-content.md +30 -22
- package/docs/custom-macros/ts-quote/iteration-for.md +48 -40
- package/docs/custom-macros/ts-quote/local-constants-let.md +23 -21
- package/docs/custom-macros/ts-quote/match-expressions-match.md +46 -38
- package/docs/custom-macros/ts-quote/overview.md +5 -10
- package/docs/custom-macros/ts-quote/pattern-matching-iflet.md +39 -0
- package/docs/custom-macros/ts-quote/quick-reference.md +50 -129
- package/docs/custom-macros/ts-quote/side-effects-do.md +13 -78
- package/docs/custom-macros/ts-quote/string-interpolation-textexpr.md +36 -0
- package/docs/custom-macros/ts-quote/tsstream-injection-typescript.md +43 -35
- package/docs/custom-macros/ts-quote/while-loops-while.md +31 -23
- package/docs/custom-macros/ts-quote.md +800 -520
- package/docs/getting-started/first-macro.md +98 -71
- package/docs/getting-started/installation.md +109 -65
- package/docs/integration/cli.md +214 -105
- package/docs/integration/configuration.md +115 -72
- package/docs/integration/integration-overview.md +55 -18
- package/docs/integration/mcp-server.md +84 -43
- package/docs/integration/svelte-preprocessor.md +183 -126
- package/docs/integration/typescript-plugin.md +101 -53
- package/docs/integration/vite-plugin.md +116 -76
- package/docs/language-servers/ls-overview.md +37 -21
- package/docs/language-servers/svelte.md +69 -38
- package/docs/language-servers/zed.md +81 -44
- package/docs/roadmap/roadmap.md +75 -53
- package/docs/sections.json +333 -44
- package/package.json +27 -28
|
@@ -1,112 +1,151 @@
|
|
|
1
1
|
# Rust Setup
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
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.88 or later)
|
|
8
|
+
* Node.js 24 or later
|
|
9
|
+
* NAPI-RS CLI: `cargo install macroforge_ts`
|
|
10
|
+
|
|
11
|
+
## Create the Project
|
|
12
|
+
|
|
13
|
+
Bash
|
|
14
|
+
|
|
15
|
+
```
|
|
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
|
+
|
|
30
|
+
```
|
|
18
31
|
[package]
|
|
19
|
-
name
|
|
20
|
-
version
|
|
21
|
-
edition
|
|
32
|
+
name = "my-macros"
|
|
33
|
+
version = "0.1.0"
|
|
34
|
+
edition = "2024"
|
|
22
35
|
|
|
23
36
|
[lib]
|
|
24
|
-
crate-type
|
|
37
|
+
crate-type = ["cdylib"]
|
|
25
38
|
|
|
26
39
|
[dependencies]
|
|
27
|
-
macroforge_ts
|
|
28
|
-
napi
|
|
29
|
-
napi-derive
|
|
40
|
+
macroforge_ts = "0.1"
|
|
41
|
+
napi = { version = "3", features = ["napi8", "compat-mode"] }
|
|
42
|
+
napi-derive = "3"
|
|
30
43
|
|
|
31
44
|
[build-dependencies]
|
|
32
|
-
napi-build
|
|
45
|
+
napi-build = "2"
|
|
33
46
|
|
|
34
47
|
[profile.release]
|
|
35
|
-
lto
|
|
36
|
-
strip
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
lto = true
|
|
49
|
+
strip = true
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Create build.rs
|
|
53
|
+
|
|
54
|
+
build.rs
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
fn main() {
|
|
58
|
+
napi_build::setup();
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Create src/lib.rs
|
|
63
|
+
|
|
64
|
+
src/lib.rs
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
use macroforge_ts::macros::{ts_macro_derive, body};
|
|
68
|
+
use macroforge_ts::ts_syn::{
|
|
69
|
+
Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
70
|
+
};
|
|
48
71
|
|
|
49
72
|
#[ts_macro_derive(
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
JSON,
|
|
74
|
+
description = "Generates toJSON() returning a plain object"
|
|
52
75
|
)]
|
|
53
|
-
pub
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
76
|
+
pub fn derive_json(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
77
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
78
|
+
|
|
79
|
+
match &input.data {
|
|
80
|
+
Data::Class(class) => {
|
|
81
|
+
Ok(body! {
|
|
82
|
+
toJSON(): Record<string, unknown> {
|
|
83
|
+
return {
|
|
84
|
+
{#for field in class.field_names()}
|
|
85
|
+
@{field}: this.@{field},
|
|
86
|
+
{/for}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
_ => Err(MacroforgeError::new(
|
|
92
|
+
input.decorator_span(),
|
|
93
|
+
"@derive(JSON) only works on classes",
|
|
94
|
+
)),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Create package.json
|
|
100
|
+
|
|
101
|
+
package.json
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
{
|
|
105
|
+
"name": "@my-org/macros",
|
|
106
|
+
"version": "0.1.0",
|
|
107
|
+
"main": "index.js",
|
|
108
|
+
"types": "index.d.ts",
|
|
109
|
+
"napi": {
|
|
110
|
+
"name": "my-macros",
|
|
111
|
+
"triples": {
|
|
112
|
+
"defaults": true
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"files": [
|
|
116
|
+
"index.js",
|
|
117
|
+
"index.d.ts",
|
|
118
|
+
"*.node"
|
|
119
|
+
],
|
|
120
|
+
"scripts": {
|
|
121
|
+
"build": "napi build --release",
|
|
122
|
+
"prepublishOnly": "napi build --release"
|
|
123
|
+
},
|
|
124
|
+
"devDependencies": {
|
|
125
|
+
"@napi-rs/cli": "^3.0.0-alpha.0"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Build the Package
|
|
131
|
+
|
|
132
|
+
Bash
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
# Build the native addon
|
|
136
|
+
npm run build
|
|
137
|
+
|
|
138
|
+
# This creates:
|
|
139
|
+
# - index.js (JavaScript bindings)
|
|
140
|
+
# - index.d.ts (TypeScript types)
|
|
141
|
+
# - *.node (native binary)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Tip
|
|
145
|
+
|
|
146
|
+
For cross-platform builds, use GitHub Actions with the NAPI-RS CI template.
|
|
147
|
+
|
|
148
|
+
## Next Steps
|
|
149
|
+
|
|
150
|
+
* [Learn the #\[ts\_macro\_derive\] attribute](../../docs/custom-macros/ts-macro-derive)
|
|
151
|
+
* [Master the template syntax](../../docs/custom-macros/ts-quote)
|
|
@@ -2,52 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
### Class Fields (FieldIR)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
Rust
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
struct FieldIR {
|
|
9
|
+
pub name: String, // Field name
|
|
10
|
+
pub span: SpanIR, // Field span
|
|
11
|
+
pub ts_type: String, // TypeScript type annotation
|
|
12
|
+
pub optional: bool, // Whether field has ?
|
|
13
|
+
pub readonly: bool, // Whether field is readonly
|
|
14
|
+
pub visibility: Visibility, // Public, Protected, Private
|
|
15
|
+
pub decorators: Vec<DecoratorIR>, // Field decorators
|
|
14
16
|
}
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
### Interface Fields (InterfaceFieldIR)
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
Rust
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
struct InterfaceFieldIR {
|
|
25
|
+
pub name: String,
|
|
26
|
+
pub span: SpanIR,
|
|
27
|
+
pub ts_type: String,
|
|
28
|
+
pub optional: bool,
|
|
29
|
+
pub readonly: bool,
|
|
30
|
+
pub decorators: Vec<DecoratorIR>,
|
|
31
|
+
// Note: No visibility field (interfaces are always public)
|
|
28
32
|
}
|
|
29
33
|
```
|
|
30
34
|
|
|
31
35
|
### Enum Variants (EnumVariantIR)
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
Rust
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
struct EnumVariantIR {
|
|
41
|
+
pub name: String,
|
|
42
|
+
pub span: SpanIR,
|
|
43
|
+
pub value: EnumValue, // Auto, String(String), or Number(f64)
|
|
44
|
+
pub decorators: Vec<DecoratorIR>,
|
|
39
45
|
}
|
|
40
46
|
```
|
|
41
47
|
|
|
42
48
|
### Decorator Structure
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
Rust
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
struct DecoratorIR {
|
|
54
|
+
pub name: String, // e.g., "serde"
|
|
55
|
+
pub args_src: String, // Raw args text, e.g., "skip, rename: 'id'"
|
|
56
|
+
pub span: SpanIR,
|
|
49
57
|
}
|
|
50
58
|
```
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
Note
|
|
61
|
+
|
|
62
|
+
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.
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
If your macro generates code that requires imports, use the `add_import` method on `TsStream`:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Rust
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
// Add an import to be inserted at the top of the file
|
|
9
|
+
let mut output = body! {
|
|
10
|
+
validate(): ValidationResult {
|
|
11
|
+
return validateFields(this);
|
|
12
|
+
}
|
|
11
13
|
};
|
|
12
14
|
|
|
13
|
-
//
|
|
14
|
-
output.add_import("validateFields",
|
|
15
|
-
output.add_import("ValidationResult",
|
|
15
|
+
// This will add: import { validateFields, ValidationResult } from "my-validation-lib";
|
|
16
|
+
output.add_import("validateFields", "my-validation-lib");
|
|
17
|
+
output.add_import("ValidationResult", "my-validation-lib");
|
|
16
18
|
|
|
17
19
|
Ok(output)
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
Note
|
|
23
|
+
|
|
24
|
+
Imports are automatically deduplicated. If the same import already exists in the file, it won't be added again.
|
|
@@ -4,47 +4,42 @@
|
|
|
4
4
|
|
|
5
5
|
The first argument is the macro name that users will reference in `@derive()`:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Rust
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
#[ts_macro_derive(JSON)] // Users write: @derive(JSON)
|
|
11
|
+
pub fn derive_json(...)
|
|
10
12
|
```
|
|
11
13
|
|
|
12
14
|
### Description
|
|
13
15
|
|
|
14
16
|
Provides documentation for the macro:
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
Rust
|
|
19
|
+
|
|
20
|
+
```
|
|
17
21
|
#[ts_macro_derive(
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
JSON,
|
|
23
|
+
description = "Generates toJSON() returning a plain object"
|
|
20
24
|
)]
|
|
21
|
-
pub
|
|
25
|
+
pub fn derive_json(...)
|
|
22
26
|
```
|
|
23
27
|
|
|
24
28
|
### Attributes
|
|
25
29
|
|
|
26
30
|
Declare which field-level decorators your macro accepts:
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
Rust
|
|
33
|
+
|
|
34
|
+
```
|
|
29
35
|
#[ts_macro_derive(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
Debug,
|
|
37
|
+
description = "Generates toString()",
|
|
38
|
+
attributes(debug) // Allows @debug({ ... }) on fields
|
|
33
39
|
)]
|
|
34
|
-
pub
|
|
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>
|
|
40
|
+
pub fn derive_debug(...)
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
| Token stream containing the class/interface AST
|
|
43
|
+
Note
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
| Returns generated code or an error with source location
|
|
45
|
+
Declared attributes become available as `@attributeName({ options })` decorators in TypeScript.
|
|
@@ -1,51 +1,53 @@
|
|
|
1
1
|
## Complete Example
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Rust
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
use macroforge_ts::macros::{ts_macro_derive, body};
|
|
7
|
+
use macroforge_ts::ts_syn::{
|
|
8
|
+
Data, DeriveInput, FieldIR, MacroforgeError, TsStream, parse_ts_macro_input,
|
|
7
9
|
};
|
|
8
10
|
|
|
9
|
-
//
|
|
10
|
-
fn
|
|
11
|
-
|
|
11
|
+
// Helper function to check if a field has a decorator
|
|
12
|
+
fn has_decorator(field: &FieldIR, name: &str) -> bool {
|
|
13
|
+
field.decorators.iter().any(|d| d.name.eq_ignore_ascii_case(name))
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
#[ts_macro_derive(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
Validate,
|
|
18
|
+
description = "Generates a validate() method",
|
|
19
|
+
attributes(validate)
|
|
18
20
|
)]
|
|
19
|
-
pub
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
21
|
+
pub fn derive_validate(mut input: TsStream) -> Result<TsStream, MacroforgeError> {
|
|
22
|
+
let input = parse_ts_macro_input!(input as DeriveInput);
|
|
23
|
+
|
|
24
|
+
match &input.data {
|
|
25
|
+
Data::Class(class) => {
|
|
26
|
+
let validations: Vec<_> = class.fields()
|
|
27
|
+
.iter()
|
|
28
|
+
.filter(|f| has_decorator(f, "validate"))
|
|
29
|
+
.collect();
|
|
30
|
+
|
|
31
|
+
Ok(body! {
|
|
32
|
+
validate(): string[] {
|
|
33
|
+
const errors: string[] = [];
|
|
34
|
+
{#for field in validations}
|
|
35
|
+
if (!this.@{field.name}) {
|
|
36
|
+
errors.push("@{field.name} is required");
|
|
37
|
+
}
|
|
38
|
+
{/for}
|
|
39
|
+
return errors;
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
_ => Err(MacroforgeError::new(
|
|
44
|
+
input.decorator_span(),
|
|
45
|
+
"@derive(Validate) only works on classes",
|
|
46
|
+
)),
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
## Next Steps
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
* [Learn the template syntax](../../docs/custom-macros/ts-quote)
|