Skip to content

Commit 2cf6431

Browse files
authored
feat(svelte-ast-print): Add support for AttachTag (#127)
1 parent d25fb96 commit 2cf6431

File tree

11 files changed

+173
-32
lines changed

11 files changed

+173
-32
lines changed

.changeset/cold-eggs-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-ast-print": minor
3+
---
4+
5+
✨ Add support for new AST node - `AttachTag` - via function `printAttachTag()`

.changeset/easy-doors-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-ast-print": minor
3+
---
4+
5+
Required peer dependency `svelte` is now `v5.29`

packages/svelte-ast-print/src/_internal/template/element-like.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type { AST as SV } from "svelte/compiler";
2+
23
import { printFragment } from "../../fragment.ts";
34
import { printAttributeLike } from "../../template/attribute-like.ts";
4-
import * as char from "../char.js";
5-
import { has_frag_text_or_exp_tag_only } from "../fragment.js";
6-
import { HTMLClosingTag, HTMLOpeningTag, HTMLSelfClosingTag } from "../html.js";
7-
import type { PrintOptions } from "../option.js";
8-
import { type Result, State } from "../shared.js";
5+
import { printAttachTag } from "../../template/tag.ts";
6+
import * as char from "../char.ts";
7+
import { has_frag_text_or_exp_tag_only } from "../fragment.ts";
8+
import { HTMLClosingTag, HTMLOpeningTag, HTMLSelfClosingTag } from "../html.ts";
9+
import type { PrintOptions } from "../option.ts";
10+
import { type Result, State, Wrapper } from "../shared.ts";
911

1012
/**
1113
* @internal
@@ -102,15 +104,23 @@ export function print_maybe_self_closing_el<N extends SV.ElementLike>(params: {
102104
n.name,
103105
);
104106
if (n.attributes.length > 0) {
105-
for (const a of n.attributes) tag.insert(char.SPACE, printAttributeLike(a));
107+
print_element_like_attributes({
108+
attributes: n.attributes,
109+
opts,
110+
tag,
111+
});
106112
}
107113
tag.insert(char.SPACE);
108114
st.add(tag);
109115
return st.result;
110116
}
111117
const opening = new HTMLOpeningTag("inline", n.name);
112118
if (n.attributes.length > 0) {
113-
for (const a of n.attributes) opening.insert(char.SPACE, printAttributeLike(a));
119+
print_element_like_attributes({
120+
attributes: n.attributes,
121+
opts,
122+
tag: opening,
123+
});
114124
}
115125
st.add(opening);
116126
const should_break =
@@ -135,7 +145,11 @@ export function print_self_closing_el<N extends SV.ElementLike>(params: {
135145
const st = State.get(params.n, opts);
136146
const tag = new HTMLSelfClosingTag("inline", n.name);
137147
if (n.attributes.length > 0) {
138-
for (const a of n.attributes) tag.insert(char.SPACE, printAttributeLike(a, opts));
148+
print_element_like_attributes({
149+
attributes: n.attributes,
150+
opts,
151+
tag,
152+
});
139153
}
140154
tag.insert(char.SPACE);
141155
st.add(tag);
@@ -154,7 +168,11 @@ export function print_non_self_closing_el<N extends SV.ElementLike>(params: {
154168
const st = State.get(n, opts);
155169
const opening = new HTMLOpeningTag("inline", n.name);
156170
if (n.attributes.length > 0) {
157-
for (const a of n.attributes) opening.insert(char.SPACE, printAttributeLike(a));
171+
print_element_like_attributes({
172+
attributes: n.attributes,
173+
opts,
174+
tag: opening,
175+
});
158176
}
159177
st.add(opening);
160178
const should_break =
@@ -186,3 +204,21 @@ export function isElementLike(n: SV.BaseNode): n is SV.ElementLike {
186204
"SvelteBoundary",
187205
]).has(n.type);
188206
}
207+
208+
function print_element_like_attributes({
209+
attributes,
210+
tag,
211+
opts,
212+
}: {
213+
attributes: SV.ElementLike["attributes"];
214+
tag: Wrapper;
215+
opts: Partial<PrintOptions>;
216+
}) {
217+
for (const a of attributes) {
218+
if (a.type === "AttachTag") {
219+
tag.insert(char.SPACE, printAttachTag(a, opts));
220+
} else {
221+
tag.insert(char.SPACE, printAttributeLike(a, opts));
222+
}
223+
}
224+
}

packages/svelte-ast-print/src/fragment.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@ import { printFragment } from "./fragment.ts";
77
describe(printFragment, () => {
88
it("it prints correctly fragment code", ({ expect }) => {
99
const code = `
10-
<h1>Shopping list</h1>
10+
<h1 {@attach tooltip(content)}>Shopping list</h1>
1111
<ul>
1212
{#each items as item}
1313
<li>{item.name} x {item.qty}</li>
1414
{/each}
1515
</ul>
1616
17+
<canvas
18+
width={32}
19+
height={32}
20+
{@attach (canvas) => {
21+
const context = canvas.getContext('2d');
22+
23+
$effect(() => {
24+
context.fillStyle = color;
25+
context.fillRect(0, 0, canvas.width, canvas.height);
26+
});
27+
}}
28+
></canvas>
29+
1730
<div class="mb-6">
1831
<Label for="large-input" class="block mb-2">Large input</Label>
1932
<Input id="large-input" size="lg" placeholder="Large input" />
@@ -44,12 +57,20 @@ describe(printFragment, () => {
4457
`;
4558
const node = parse_and_extract<AST.Fragment>(code, "Fragment");
4659
expect(printFragment(node).code).toMatchInlineSnapshot(`
47-
"<h1>Shopping list</h1>
60+
"<h1 {@attach tooltip(content)}>Shopping list</h1>
4861
<ul>
4962
{#each items as item}
5063
<li>{item.name} x {item.qty}</li>
5164
{/each}
5265
</ul>
66+
<canvas width={32} height={32} {@attach (canvas) => {
67+
const context = canvas.getContext('2d');
68+
69+
$effect(() => {
70+
context.fillStyle = color;
71+
context.fillRect(0, 0, canvas.width, canvas.height);
72+
});
73+
}} />
5374
<div class="mb-6">
5475
<Label for="large-input" class="block mb-2">Large input</Label>
5576
<Input id="large-input" size="lg" placeholder="Large input" />

packages/svelte-ast-print/src/lib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export function printSvelte<N extends SvelteOnlyNode>(n: N, opts: Partial<PrintO
174174
case "SvelteWindow":
175175
case "TitleElement":
176176
// tag
177+
case "AttachTag":
177178
case "ConstTag":
178179
case "DebugTag":
179180
case "ExpressionTag":

packages/svelte-ast-print/src/template.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function printTemplateNode(n: SV.TemplateNode, opts: Partial<PrintOptions
5151
case "SvelteSelf":
5252
case "SvelteWindow":
5353
case "TitleElement": return printElementLike(n, opts);
54+
case "AttachTag":
5455
case "ConstTag":
5556
case "DebugTag":
5657
case "ExpressionTag":

packages/svelte-ast-print/src/template/element-like.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@
55

66
import type { AST as SV } from "svelte/compiler";
77

8-
import * as char from "../_internal/char.ts";
9-
import { HTMLClosingTag, HTMLOpeningTag } from "../_internal/html.ts";
108
import type { PrintOptions } from "../_internal/option.ts";
11-
import { type Result, State } from "../_internal/shared.ts";
9+
import type { Result } from "../_internal/shared.ts";
1210
import {
1311
print_maybe_self_closing_el,
1412
print_non_self_closing_el,
1513
print_self_closing_el,
1614
} from "../_internal/template/element-like.ts";
17-
import { printFragment } from "../fragment.ts";
18-
import { printAttributeLike } from "./attribute-like.ts";
1915

2016
/**
2117
* @since 1.0.0
@@ -122,8 +118,6 @@ export function printSvelteDocument(n: SV.SvelteDocument, opts: Partial<PrintOpt
122118
* @__NO_SIDE_EFFECTS__
123119
*/
124120
export function printSvelteElement(n: SV.SvelteElement, opts: Partial<PrintOptions> = {}): Result<SV.SvelteElement> {
125-
const st = State.get(n, opts);
126-
const opening = new HTMLOpeningTag("inline", n.name);
127121
n.attributes.unshift({
128122
type: "Attribute",
129123
name: "this",
@@ -132,11 +126,7 @@ export function printSvelteElement(n: SV.SvelteElement, opts: Partial<PrintOptio
132126
expression: n.tag,
133127
},
134128
});
135-
for (const a of n.attributes) opening.insert(char.SPACE, printAttributeLike(a));
136-
st.add(opening);
137-
st.add(printFragment(n.fragment, opts));
138-
st.add(new HTMLClosingTag("inline", n.name));
139-
return st.result;
129+
return print_non_self_closing_el({ n, opts });
140130
}
141131

142132
/**

packages/svelte-ast-print/src/template/tag.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@ import { parse_and_extract } from "@internals/test/svelte";
22
import type { AST } from "svelte/compiler";
33
import { describe, it } from "vitest";
44

5-
import { printConstTag, printDebugTag, printExpressionTag, printHtmlTag, printRenderTag, printTag } from "./tag.ts";
5+
import {
6+
printAttachTag,
7+
printConstTag,
8+
printDebugTag,
9+
printExpressionTag,
10+
printHtmlTag,
11+
printRenderTag,
12+
printTag,
13+
} from "./tag.ts";
614

715
describe(printTag, () => {
16+
it("print correctly `@attach` tag", ({ expect }) => {
17+
const code = "<canvas {@attach tooltip(content)}></canvas>";
18+
const node = parse_and_extract<AST.AttachTag>(code, "AttachTag");
19+
expect(printTag(node).code).toMatchInlineSnapshot(`"{@attach tooltip(content)}"`);
20+
});
21+
822
it("print correctly `@const` tag", ({ expect }) => {
923
const code = "{@const area = box.width * box.height}";
1024
const node = parse_and_extract<AST.ConstTag>(code, "ConstTag");
@@ -36,6 +50,56 @@ describe(printTag, () => {
3650
});
3751
});
3852

53+
describe(printAttachTag, () => {
54+
it("prints correctly when is a reference", ({ expect }) => {
55+
const code = `
56+
<div {@attach myAttachment}>...</div>
57+
`;
58+
const node = parse_and_extract<AST.AttachTag>(code, "AttachTag");
59+
expect(printAttachTag(node).code).toMatchInlineSnapshot(`"{@attach myAttachment}"`);
60+
});
61+
62+
it("prints correctly when is function call", ({ expect }) => {
63+
const code = `
64+
<button {@attach tooltip(content)}>
65+
Hover me
66+
</button>
67+
`;
68+
const node = parse_and_extract<AST.AttachTag>(code, "AttachTag");
69+
expect(printAttachTag(node).code).toMatchInlineSnapshot(`"{@attach tooltip(content)}"`);
70+
});
71+
72+
it("prints correctly when is an inline attachment", ({ expect }) => {
73+
const code = `
74+
<canvas
75+
width={32}
76+
height={32}
77+
{@attach (canvas) => {
78+
const context = canvas.getContext('2d');
79+
80+
$effect(() => {
81+
context.fillStyle = color;
82+
context.fillRect(0, 0, canvas.width, canvas.height);
83+
});
84+
}}
85+
></canvas>
86+
`;
87+
const node = parse_and_extract<AST.AttachTag>(code, "AttachTag");
88+
expect(printAttachTag(node).code).toMatchInlineSnapshot(
89+
`
90+
"{@attach (canvas) => {
91+
const context = canvas.getContext('2d');
92+
93+
$effect(() => {
94+
context.fillStyle = color;
95+
context.fillRect(0, 0, canvas.width, canvas.height);
96+
});
97+
}}"
98+
`,
99+
);
100+
});
101+
});
102+
39103
describe(printConstTag, () => {
40104
it("prints correctly when used as direct child of allowed tags ", ({ expect }) => {
41105
const code = `

packages/svelte-ast-print/src/template/tag.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function printTag(n: SV.Tag, opts: Partial<PrintOptions> = {}): Result<SV
1919
// biome-ignore format: Prettier
2020
// prettier-ignore
2121
switch (n.type) {
22+
case "AttachTag": return printAttachTag(n, opts);
2223
case "ConstTag": return printConstTag(n, opts);
2324
case "DebugTag": return printDebugTag(n, opts);
2425
case "ExpressionTag": return printExpressionTag(n, opts);
@@ -27,6 +28,23 @@ export function printTag(n: SV.Tag, opts: Partial<PrintOptions> = {}): Result<SV
2728
}
2829
}
2930

31+
/**
32+
* @see {@link https://svelte.dev/docs/svelte/@attach}
33+
*
34+
* @example pattern
35+
* ```svelte
36+
* {@attach expression}
37+
* ```
38+
*
39+
* @since 1.1.0
40+
* @__NO_SIDE_EFFECTS__
41+
*/
42+
export function printAttachTag(n: SV.AttachTag, opts: Partial<PrintOptions> = {}): Result<SV.AttachTag> {
43+
const st = State.get(n, opts);
44+
st.add(new CurlyBrackets("inline", char.AT, "attach", char.SPACE, print_js(n.expression, st.opts, false)));
45+
return st.result;
46+
}
47+
3048
/**
3149
* @see {@link https://svelte.dev/docs/svelte/@const}
3250
*

pnpm-lock.yaml

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)