Skip to content

Commit ab13a46

Browse files
authored
feat: add support for {#snippet} and {@render} (#431)
1 parent af1bae5 commit ab13a46

File tree

52 files changed

+35491
-34
lines changed

Some content is hidden

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

52 files changed

+35491
-34
lines changed

.changeset/grumpy-pans-guess.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
feat: add support for `{#snippet}` and `{@render}`

docs/AST.md

+27
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

explorer-v2/package.json

+16-16
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@
1212
"preview": "vite preview"
1313
},
1414
"dependencies": {
15-
"@fontsource/fira-mono": "^5.0.0",
16-
"@typescript-eslint/parser": "^6.0.0",
17-
"eslint": "^8.0.0",
18-
"eslint-scope": "^7.0.0",
15+
"@fontsource/fira-mono": "^5.0.8",
16+
"@typescript-eslint/parser": "^6.11.0",
17+
"eslint": "^8.54.0",
18+
"eslint-scope": "^7.2.2",
1919
"esquery": "^1.5.0",
20-
"pako": "^2.0.3",
21-
"svelte": "^5.0.0-next.2",
20+
"pako": "^2.1.0",
21+
"svelte": "^5.0.0-next.8",
2222
"svelte-eslint-parser": "link:..",
23-
"tslib": "^2.5.0"
23+
"tslib": "^2.6.2"
2424
},
2525
"devDependencies": {
26-
"@sveltejs/adapter-static": "^2.0.0",
27-
"@sveltejs/kit": "^1.0.0-next.456",
28-
"prettier": "^3.0.0",
29-
"prettier-plugin-svelte": "^3.0.0",
30-
"string-replace-loader": "^3.0.1",
31-
"typescript": "^5.0.4",
26+
"@sveltejs/adapter-static": "^2.0.3",
27+
"@sveltejs/kit": "^1.27.6",
28+
"prettier": "^3.1.0",
29+
"prettier-plugin-svelte": "^3.1.0",
30+
"string-replace-loader": "^3.1.0",
31+
"typescript": "^5.2.2",
3232
"vite": "^5.0.0",
33-
"webpack": "^5.82.1",
34-
"webpack-cli": "^5.0.0",
35-
"wrapper-webpack-plugin": "^2.1.0"
33+
"webpack": "^5.89.0",
34+
"webpack-cli": "^5.1.4",
35+
"wrapper-webpack-plugin": "^2.2.2"
3636
}
3737
}

explorer-v2/src/app.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="utf-8" />

explorer-v2/src/lib/MonacoEditor.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
renderValidationDecorations: 'on',
9595
renderWhitespace: 'boundary',
9696
scrollBeyondLastLine: false,
97+
useInlineViewWhenSpaceIsLimited: false,
9798
...editorOptions
9899
};
99100

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@
7070
"@types/eslint": "^8.40.1",
7171
"@types/eslint-scope": "^3.7.4",
7272
"@types/eslint-visitor-keys": "^1.0.0",
73-
"@types/estree": "^1.0.1",
73+
"@types/estree": "^1.0.5",
7474
"@types/mocha": "^10.0.1",
7575
"@types/node": "^20.0.0",
7676
"@types/semver": "^7.5.0",
77-
"@typescript-eslint/eslint-plugin": "^6.9.0",
77+
"@typescript-eslint/eslint-plugin": "^6.10.0",
7878
"@typescript-eslint/parser": "~6.10.0",
7979
"@typescript-eslint/types": "~6.10.0",
8080
"benchmark": "^2.1.4",

src/ast/html.ts

+60-9
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

+3
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

+49
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?:

0 commit comments

Comments
 (0)