Skip to content

Commit 18804d7

Browse files
committed
feat: add support for {#snippet} and {@render}
1 parent 48f7e5e commit 18804d7

File tree

47 files changed

+35463
-14
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+35463
-14
lines changed

docs/AST.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ type Child =
104104
| SvelteMustacheTag
105105
| SvelteDebugTag
106106
| SvelteConstTag
107+
| SvelteRenderTag
107108
| SvelteIfBlock
108109
| SvelteEachBlock
109110
| SvelteAwaitBlock
110111
| SvelteKeyBlock
112+
| SvelteSnippetBlock
111113
| SvelteHTMLComment;
112114
```
113115

@@ -421,6 +423,18 @@ interface SvelteConstTag extends Node {
421423
}
422424
```
423425

426+
### SvelteRenderTag
427+
428+
This is the `{@render}` tag node.
429+
430+
```ts
431+
interface SvelteRenderTag extends Node {
432+
type: "SvelteRenderTag";
433+
callee: Identifier;
434+
argument: Expression | null;
435+
}
436+
```
437+
424438
[VariableDeclarator] is a node defined in ESTree.
425439

426440
### SvelteIfBlock
@@ -533,6 +547,19 @@ interface SvelteKeyBlock extends Node {
533547
}
534548
```
535549

550+
### SvelteSnippetBlock
551+
552+
This is the `{#snippet}` tag node.
553+
554+
```ts
555+
interface SvelteSnippetBlock extends Node {
556+
type: "SvelteSnippetBlock";
557+
id: Identifier;
558+
context: null | Pattern;
559+
children: Child[];
560+
}
561+
```
562+
536563
## Comments
537564

538565
### SvelteHTMLComment

src/ast/html.ts

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type SvelteHTMLNode =
1616
| SvelteMustacheTag
1717
| SvelteDebugTag
1818
| SvelteConstTag
19+
| SvelteRenderTag
1920
| SvelteIfBlock
2021
| SvelteElseBlock
2122
| SvelteEachBlock
@@ -24,6 +25,7 @@ export type SvelteHTMLNode =
2425
| SvelteAwaitThenBlock
2526
| SvelteAwaitCatchBlock
2627
| SvelteKeyBlock
28+
| SvelteSnippetBlock
2729
| SvelteAttribute
2830
| SvelteShorthandAttribute
2931
| SvelteSpreadAttribute
@@ -87,7 +89,8 @@ export interface SvelteHTMLElement extends BaseSvelteElement {
8789
| SvelteAwaitPendingBlock
8890
| SvelteAwaitThenBlock
8991
| SvelteAwaitCatchBlock
90-
| SvelteKeyBlock;
92+
| SvelteKeyBlock
93+
| SvelteSnippetBlock;
9194
}
9295
/** Node of Svelte component element. */
9396
export interface SvelteComponentElement extends BaseSvelteElement {
@@ -106,7 +109,8 @@ export interface SvelteComponentElement extends BaseSvelteElement {
106109
| SvelteAwaitPendingBlock
107110
| SvelteAwaitThenBlock
108111
| SvelteAwaitCatchBlock
109-
| SvelteKeyBlock;
112+
| SvelteKeyBlock
113+
| SvelteSnippetBlock;
110114
}
111115
/** Node of Svelte special component element. e.g. `<svelte:window>` */
112116
export interface SvelteSpecialElement extends BaseSvelteElement {
@@ -125,7 +129,8 @@ export interface SvelteSpecialElement extends BaseSvelteElement {
125129
| SvelteAwaitPendingBlock
126130
| SvelteAwaitThenBlock
127131
| SvelteAwaitCatchBlock
128-
| SvelteKeyBlock;
132+
| SvelteKeyBlock
133+
| SvelteSnippetBlock;
129134
}
130135
/** Node of start tag. */
131136
export interface SvelteStartTag extends BaseNode {
@@ -174,10 +179,12 @@ type Child =
174179
| SvelteMustacheTag
175180
| SvelteDebugTag
176181
| SvelteConstTag
182+
| SvelteRenderTag
177183
| SvelteIfBlockAlone
178184
| SvelteEachBlock
179185
| SvelteAwaitBlock
180186
| SvelteKeyBlock
187+
| SvelteSnippetBlock
181188
| SvelteHTMLComment;
182189

183190
/** Node of text. like HTML text. */
@@ -194,7 +201,8 @@ export interface SvelteText extends BaseNode {
194201
| SvelteAwaitPendingBlock
195202
| SvelteAwaitThenBlock
196203
| SvelteAwaitCatchBlock
197-
| SvelteKeyBlock;
204+
| SvelteKeyBlock
205+
| SvelteSnippetBlock;
198206
}
199207
/** Node of literal. */
200208
export interface SvelteLiteral extends BaseNode {
@@ -219,6 +227,7 @@ interface BaseSvelteMustacheTag extends BaseNode {
219227
| SvelteAwaitThenBlock
220228
| SvelteAwaitCatchBlock
221229
| SvelteKeyBlock
230+
| SvelteSnippetBlock
222231
| SvelteAttribute
223232
| SvelteStyleDirective;
224233
}
@@ -244,6 +253,7 @@ export interface SvelteDebugTag extends BaseNode {
244253
| SvelteAwaitThenBlock
245254
| SvelteAwaitCatchBlock
246255
| SvelteKeyBlock
256+
| SvelteSnippetBlock
247257
| SvelteAttribute;
248258
}
249259
/** Node of const tag. e.g. `{@const}` */
@@ -260,8 +270,26 @@ export interface SvelteConstTag extends BaseNode {
260270
| SvelteAwaitThenBlock
261271
| SvelteAwaitCatchBlock
262272
| SvelteKeyBlock
273+
| SvelteSnippetBlock
263274
| SvelteAttribute;
264275
}
276+
/** Node of render tag. e.g. `{@render}` */
277+
export interface SvelteRenderTag extends BaseNode {
278+
type: "SvelteRenderTag";
279+
callee: ESTree.Identifier;
280+
argument: ESTree.Expression | null;
281+
parent:
282+
| SvelteProgram
283+
| SvelteElement
284+
| SvelteIfBlock
285+
| SvelteElseBlockAlone
286+
| SvelteEachBlock
287+
| SvelteAwaitPendingBlock
288+
| SvelteAwaitThenBlock
289+
| SvelteAwaitCatchBlock
290+
| SvelteKeyBlock
291+
| SvelteSnippetBlock;
292+
}
265293
/** Node of if block. e.g. `{#if}` */
266294
export type SvelteIfBlock = SvelteIfBlockAlone | SvelteIfBlockElseIf;
267295
interface BaseSvelteIfBlock extends BaseNode {
@@ -279,7 +307,8 @@ interface BaseSvelteIfBlock extends BaseNode {
279307
| SvelteAwaitPendingBlock
280308
| SvelteAwaitThenBlock
281309
| SvelteAwaitCatchBlock
282-
| SvelteKeyBlock;
310+
| SvelteKeyBlock
311+
| SvelteSnippetBlock;
283312
}
284313
/** Node of if block. e.g. `{#if}` */
285314
export interface SvelteIfBlockAlone extends BaseSvelteIfBlock {
@@ -328,7 +357,8 @@ export interface SvelteEachBlock extends BaseNode {
328357
| SvelteAwaitPendingBlock
329358
| SvelteAwaitThenBlock
330359
| SvelteAwaitCatchBlock
331-
| SvelteKeyBlock;
360+
| SvelteKeyBlock
361+
| SvelteSnippetBlock;
332362
}
333363
/** Node of await block. e.g. `{#await}`, `{#await ... then ... }`, `{#await ... catch ... }` */
334364
export type SvelteAwaitBlock =
@@ -351,7 +381,8 @@ interface BaseSvelteAwaitBlock extends BaseNode {
351381
| SvelteAwaitPendingBlock
352382
| SvelteAwaitThenBlock
353383
| SvelteAwaitCatchBlock
354-
| SvelteKeyBlock;
384+
| SvelteKeyBlock
385+
| SvelteSnippetBlock;
355386
}
356387
/** Node of await block. e.g. `{#await}` */
357388
export interface SvelteAwaitBlockAwaitPending extends BaseSvelteAwaitBlock {
@@ -442,7 +473,26 @@ export interface SvelteKeyBlock extends BaseNode {
442473
| SvelteAwaitPendingBlock
443474
| SvelteAwaitThenBlock
444475
| SvelteAwaitCatchBlock
445-
| SvelteKeyBlock;
476+
| SvelteKeyBlock
477+
| SvelteSnippetBlock;
478+
}
479+
/** Node of snippet block. e.g. `{#snippet}` */
480+
export interface SvelteSnippetBlock extends BaseNode {
481+
type: "SvelteSnippetBlock";
482+
id: ESTree.Identifier;
483+
context: null | ESTree.Pattern;
484+
children: Child[];
485+
parent:
486+
| SvelteProgram
487+
| SvelteElement
488+
| SvelteIfBlock
489+
| SvelteElseBlockAlone
490+
| SvelteEachBlock
491+
| SvelteAwaitPendingBlock
492+
| SvelteAwaitThenBlock
493+
| SvelteAwaitCatchBlock
494+
| SvelteKeyBlock
495+
| SvelteSnippetBlock;
446496
}
447497
/** Node of HTML comment. */
448498
export interface SvelteHTMLComment extends BaseNode {
@@ -457,7 +507,8 @@ export interface SvelteHTMLComment extends BaseNode {
457507
| SvelteAwaitPendingBlock
458508
| SvelteAwaitThenBlock
459509
| SvelteAwaitCatchBlock
460-
| SvelteKeyBlock;
510+
| SvelteKeyBlock
511+
| SvelteSnippetBlock;
461512
}
462513
/** Node of HTML attribute. */
463514
export interface SvelteAttribute extends BaseNode {

src/context/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
SvelteHTMLElement,
77
SvelteName,
88
SvelteScriptElement,
9+
SvelteSnippetBlock,
910
SvelteStyleElement,
1011
Token,
1112
} from "../ast";
@@ -142,6 +143,8 @@ export class Context {
142143
| SvAST.Title
143144
>();
144145

146+
public readonly snippets: SvelteSnippetBlock[] = [];
147+
145148
// ----- States ------
146149
private readonly state: { isTypeScript?: boolean } = {};
147150

src/context/script-let.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
SvelteIfBlock,
1111
SvelteName,
1212
SvelteNode,
13+
SvelteSnippetBlock,
1314
Token,
1415
} from "../ast";
1516
import type { ESLintExtendedProgram } from "../parser";
@@ -148,6 +149,15 @@ export class ScriptLetContext {
148149
...callbacks: ScriptLetCallback<E>[]
149150
): ScriptLetCallback<E>[] {
150151
const range = getNodeRange(expression);
152+
return this.addExpressionFromRange(range, parent, typing, ...callbacks);
153+
}
154+
155+
public addExpressionFromRange<E extends ESTree.Expression>(
156+
range: [number, number],
157+
parent: SvelteNode,
158+
typing?: string | null,
159+
...callbacks: ScriptLetCallback<E>[]
160+
): ScriptLetCallback<E>[] {
151161
const part = this.ctx.code.slice(...range);
152162
const isTS = typing && this.ctx.isTypeScript();
153163
this.appendScript(
@@ -414,6 +424,45 @@ export class ScriptLetContext {
414424
this.pushScope(restore, "});");
415425
}
416426

427+
public nestSnippetBlock(
428+
id: ESTree.Identifier,
429+
closeParentIndex: number,
430+
snippetBlock: SvelteSnippetBlock,
431+
callback: (id: ESTree.Identifier, ctx: ESTree.Pattern | null) => void,
432+
): void {
433+
const idRange = getNodeRange(id);
434+
const part = this.ctx.code.slice(idRange[0], closeParentIndex + 1);
435+
const restore = this.appendScript(
436+
`function ${part}{`,
437+
idRange[0] - 9,
438+
(st, tokens, _comments, result) => {
439+
const fnDecl = st as ESTree.FunctionDeclaration;
440+
const idNode = fnDecl.id!;
441+
const context = fnDecl.params.length > 0 ? fnDecl.params[0] : null;
442+
const scope = result.getScope(fnDecl);
443+
444+
// Process for nodes
445+
callback(idNode, context);
446+
(idNode as any).parent = snippetBlock;
447+
if (context) {
448+
(context as any).parent = snippetBlock;
449+
}
450+
451+
// Process for scope
452+
result.registerNodeToScope(snippetBlock, scope);
453+
454+
tokens.shift(); // function
455+
tokens.pop(); // {
456+
tokens.pop(); // }
457+
458+
// Disconnect the tree structure.
459+
fnDecl.id = null as never;
460+
fnDecl.params = [];
461+
},
462+
);
463+
this.pushScope(restore, "}");
464+
}
465+
417466
public nestBlock(
418467
block: SvelteNode,
419468
params?:

src/parser/analyze-scope.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ import type ESTree from "estree";
22
import type { Scope, ScopeManager } from "eslint-scope";
33
import { Variable, Reference, analyze } from "eslint-scope";
44
import { getFallbackKeys } from "../traverse";
5-
import type { SvelteReactiveStatement, SvelteScriptElement } from "../ast";
6-
import { addReference, addVariable } from "../scope";
5+
import type {
6+
SvelteReactiveStatement,
7+
SvelteScriptElement,
8+
SvelteSnippetBlock,
9+
} from "../ast";
10+
import { addReference, addVariable, getScopeFromNode } from "../scope";
711
import { addElementToSortedArray } from "../utils";
812
import type { NormalizedParserOptions } from "./parser-options";
913
/**
@@ -218,6 +222,33 @@ export function analyzePropsScope(
218222
}
219223
}
220224

225+
/** Analyze snippets in component scope */
226+
export function analyzeSnippetsScope(
227+
snippets: SvelteSnippetBlock[],
228+
scopeManager: ScopeManager,
229+
): void {
230+
for (const snippet of snippets) {
231+
const parent = snippet.parent;
232+
if (
233+
parent.type === "SvelteElement" &&
234+
(parent.kind === "component" ||
235+
(parent.kind === "special" && parent.name.name === "svelte:component"))
236+
) {
237+
const scope = getScopeFromNode(scopeManager, snippet.id);
238+
const variable = scope.upper
239+
? scope.upper.set.get(snippet.id.name)
240+
: null;
241+
if (variable) {
242+
// Add the virtual reference for reading.
243+
const reference = addVirtualReference(snippet.id, variable, scope, {
244+
read: true,
245+
});
246+
(reference as any).svelteSnippetReference = true;
247+
}
248+
}
249+
}
250+
}
251+
221252
/** Remove reference from through */
222253
function removeReferenceFromThrough(reference: Reference, baseScope: Scope) {
223254
const variable = reference.resolved!;

0 commit comments

Comments
 (0)