Skip to content

Commit d9cb8ae

Browse files
authored
feat: improve let directive type (#395)
* feat: improve let directive type * Create six-deers-develop.md * fix * fix * fix * update
1 parent 709fd49 commit d9cb8ae

12 files changed

+17177
-20
lines changed

.changeset/six-deers-develop.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
feat: improve let directive type

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@
103103
"prettier-plugin-svelte": "^3.0.0",
104104
"rimraf": "^5.0.1",
105105
"semver": "^7.5.1",
106-
"svelte": "^4.0.0",
107-
"svelte2tsx": "^0.6.15",
106+
"svelte": "^4.2.0",
107+
"svelte2tsx": "^0.6.20",
108108
"typescript": "~5.1.3",
109109
"typescript-eslint-parser-for-extra-files": "^0.5.0"
110110
},

src/context/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
Token,
1313
} from "../ast";
1414
import type ESTree from "estree";
15+
import type * as SvAST from "../parser/svelte-ast-types";
1516
import { ScriptLetContext } from "./script-let";
1617
import { LetDirectiveCollections } from "./let-directive-collection";
1718
import { getParserForLang } from "../parser/resolve-parser";
@@ -135,6 +136,20 @@ export class Context {
135136

136137
public readonly slots = new Set<SvelteHTMLElement>();
137138

139+
public readonly elements = new Map<
140+
SvelteElement,
141+
| SvAST.InlineComponent
142+
| SvAST.Element
143+
| SvAST.Window
144+
| SvAST.Document
145+
| SvAST.Body
146+
| SvAST.Head
147+
| SvAST.Options
148+
| SvAST.SlotTemplate
149+
| SvAST.Slot
150+
| SvAST.Title
151+
>();
152+
138153
// ----- States ------
139154
private readonly state: { isTypeScript?: boolean } = {};
140155

src/parser/converts/attr.ts

+73-9
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import type { AttributeToken } from "../html";
3636
export function* convertAttributes(
3737
attributes: SvAST.AttributeOrDirective[],
3838
parent: SvelteStartTag,
39-
elementName: string,
4039
ctx: Context,
4140
): IterableIterator<
4241
| SvelteAttribute
@@ -59,7 +58,7 @@ export function* convertAttributes(
5958
continue;
6059
}
6160
if (attr.type === "EventHandler") {
62-
yield convertEventHandlerDirective(attr, parent, elementName, ctx);
61+
yield convertEventHandlerDirective(attr, parent, ctx);
6362
continue;
6463
}
6564
if (attr.type === "Class") {
@@ -318,7 +317,6 @@ function convertBindingDirective(
318317
function convertEventHandlerDirective(
319318
node: SvAST.DirectiveForExpression,
320319
parent: SvelteDirective["parent"],
321-
elementName: string,
322320
ctx: Context,
323321
): SvelteEventHandlerDirective {
324322
const directive: SvelteEventHandlerDirective = {
@@ -329,7 +327,7 @@ function convertEventHandlerDirective(
329327
parent,
330328
...ctx.getConvertLocation(node),
331329
};
332-
const typing = buildEventHandlerType(parent.parent, elementName, node.name);
330+
const typing = buildEventHandlerType(parent.parent, node.name, ctx);
333331
processDirective(node, directive, ctx, {
334332
processExpression: buildProcessExpressionForExpression(
335333
directive,
@@ -343,8 +341,8 @@ function convertEventHandlerDirective(
343341
/** Build event handler type */
344342
function buildEventHandlerType(
345343
element: SvelteElement | SvelteScriptElement | SvelteStyleElement,
346-
elementName: string,
347344
eventName: string,
345+
ctx: Context,
348346
) {
349347
const nativeEventHandlerType = `(e:${conditional({
350348
check: `'${eventName}'`,
@@ -360,6 +358,7 @@ function buildEventHandlerType(
360358
if (element.type !== "SvelteElement") {
361359
return nativeEventHandlerType;
362360
}
361+
const elementName = ctx.elements.get(element)!.name;
363362
if (element.kind === "component") {
364363
const componentEventsType = `import('svelte').ComponentEvents<${elementName}>`;
365364
return `(e:${conditional({
@@ -608,23 +607,88 @@ function convertLetDirective(
608607
processPattern(pattern) {
609608
return ctx.letDirCollections
610609
.getCollection()
611-
.addPattern(pattern, directive, "any");
610+
.addPattern(
611+
pattern,
612+
directive,
613+
buildLetDirectiveType(parent.parent, node.name, ctx),
614+
);
612615
},
613616
processName: node.expression
614617
? undefined
615618
: (name) => {
616619
// shorthand
617620
ctx.letDirCollections
618621
.getCollection()
619-
.addPattern(name, directive, "any", (es) => {
620-
directive.expression = es;
621-
});
622+
.addPattern(
623+
name,
624+
directive,
625+
buildLetDirectiveType(parent.parent, node.name, ctx),
626+
(es) => {
627+
directive.expression = es;
628+
},
629+
);
622630
return [];
623631
},
624632
});
625633
return directive;
626634
}
627635

636+
/** Build let directive param type */
637+
function buildLetDirectiveType(
638+
element: SvelteElement | SvelteScriptElement | SvelteStyleElement,
639+
letName: string,
640+
ctx: Context,
641+
) {
642+
if (element.type !== "SvelteElement") {
643+
return "any";
644+
}
645+
let slotName = "default";
646+
let componentName: string;
647+
const svelteNode = ctx.elements.get(element)!;
648+
const slotAttr = svelteNode.attributes.find(
649+
(attr): attr is SvAST.Attribute => {
650+
return attr.type === "Attribute" && attr.name === "slot";
651+
},
652+
);
653+
if (slotAttr) {
654+
if (
655+
Array.isArray(slotAttr.value) &&
656+
slotAttr.value.length === 1 &&
657+
slotAttr.value[0].type === "Text"
658+
) {
659+
slotName = slotAttr.value[0].data;
660+
} else {
661+
return "any";
662+
}
663+
const parent = findParentComponent(element);
664+
if (parent == null) return "any";
665+
componentName = ctx.elements.get(parent)!.name;
666+
} else {
667+
if (element.kind === "component") {
668+
componentName = svelteNode.name;
669+
} else {
670+
const parent = findParentComponent(element);
671+
if (parent == null) return "any";
672+
componentName = ctx.elements.get(parent)!.name;
673+
}
674+
}
675+
return `${String(componentName)}['$$slot_def'][${JSON.stringify(
676+
slotName,
677+
)}][${JSON.stringify(letName)}]`;
678+
679+
/** Find parent component element */
680+
function findParentComponent(node: SvelteElement) {
681+
let parent: SvelteElement["parent"] | null = node.parent;
682+
while (parent && parent.type !== "SvelteElement") {
683+
parent = node.parent;
684+
}
685+
if (!parent || parent.kind !== "component") {
686+
return null;
687+
}
688+
return parent;
689+
}
690+
}
691+
628692
type DirectiveProcessors<
629693
D extends SvAST.Directive,
630694
S extends SvelteDirective,

src/parser/converts/element.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ function convertHTMLElement(
257257
parent,
258258
...locs,
259259
};
260+
ctx.elements.set(element, node);
260261
element.startTag.parent = element;
261262
const elementName = node.name;
262263

@@ -265,19 +266,19 @@ function convertHTMLElement(
265266
if (letDirectives.length) {
266267
ctx.letDirCollections.beginExtract();
267268
element.startTag.attributes.push(
268-
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
269+
...convertAttributes(letDirectives, element.startTag, ctx),
269270
);
270271
letParams.push(...ctx.letDirCollections.extract().getLetParams());
271272
}
272273
if (!letParams.length && !needScopeByChildren(node)) {
273274
element.startTag.attributes.push(
274-
...convertAttributes(attributes, element.startTag, elementName, ctx),
275+
...convertAttributes(attributes, element.startTag, ctx),
275276
);
276277
element.children.push(...convertChildren(node, element, ctx));
277278
} else {
278279
ctx.scriptLet.nestBlock(element, letParams);
279280
element.startTag.attributes.push(
280-
...convertAttributes(attributes, element.startTag, elementName, ctx),
281+
...convertAttributes(attributes, element.startTag, ctx),
281282
);
282283
sortNodes(element.startTag.attributes);
283284
element.children.push(...convertChildren(node, element, ctx));
@@ -366,6 +367,7 @@ function convertSpecialElement(
366367
parent,
367368
...locs,
368369
};
370+
ctx.elements.set(element, node);
369371
element.startTag.parent = element;
370372
const elementName = node.name;
371373

@@ -374,19 +376,19 @@ function convertSpecialElement(
374376
if (letDirectives.length) {
375377
ctx.letDirCollections.beginExtract();
376378
element.startTag.attributes.push(
377-
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
379+
...convertAttributes(letDirectives, element.startTag, ctx),
378380
);
379381
letParams.push(...ctx.letDirCollections.extract().getLetParams());
380382
}
381383
if (!letParams.length && !needScopeByChildren(node)) {
382384
element.startTag.attributes.push(
383-
...convertAttributes(attributes, element.startTag, elementName, ctx),
385+
...convertAttributes(attributes, element.startTag, ctx),
384386
);
385387
element.children.push(...convertChildren(node, element, ctx));
386388
} else {
387389
ctx.scriptLet.nestBlock(element, letParams);
388390
element.startTag.attributes.push(
389-
...convertAttributes(attributes, element.startTag, elementName, ctx),
391+
...convertAttributes(attributes, element.startTag, ctx),
390392
);
391393
sortNodes(element.startTag.attributes);
392394
element.children.push(...convertChildren(node, element, ctx));
@@ -606,6 +608,7 @@ function convertComponentElement(
606608
parent,
607609
...locs,
608610
};
611+
ctx.elements.set(element, node);
609612
element.startTag.parent = element;
610613
const elementName = node.name;
611614

@@ -614,19 +617,19 @@ function convertComponentElement(
614617
if (letDirectives.length) {
615618
ctx.letDirCollections.beginExtract();
616619
element.startTag.attributes.push(
617-
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
620+
...convertAttributes(letDirectives, element.startTag, ctx),
618621
);
619622
letParams.push(...ctx.letDirCollections.extract().getLetParams());
620623
}
621624
if (!letParams.length && !needScopeByChildren(node)) {
622625
element.startTag.attributes.push(
623-
...convertAttributes(attributes, element.startTag, elementName, ctx),
626+
...convertAttributes(attributes, element.startTag, ctx),
624627
);
625628
element.children.push(...convertChildren(node, element, ctx));
626629
} else {
627630
ctx.scriptLet.nestBlock(element, letParams);
628631
element.startTag.attributes.push(
629-
...convertAttributes(attributes, element.startTag, elementName, ctx),
632+
...convertAttributes(attributes, element.startTag, ctx),
630633
);
631634
sortNodes(element.startTag.attributes);
632635
element.children.push(...convertChildren(node, element, ctx));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script lang="ts" context="module">
2+
export interface ListItem {
3+
title: string;
4+
link: string;
5+
}
6+
</script>
7+
8+
<script lang="ts">
9+
export let items: ListItem[] = [];
10+
</script>
11+
12+
<div>
13+
<ul>
14+
{#each items as item (item.title)}
15+
<li>
16+
<slot {item} />
17+
</li>
18+
{/each}
19+
</ul>
20+
</div>
21+
<slot name="count" count={items.length} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"parser": "typescript-eslint-parser-for-extra-files"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script lang="ts">
2+
import Component, { ListItem } from "./lib/Component.svelte";
3+
4+
const items: ListItem[] = [
5+
{
6+
title: "Svelte.dev",
7+
link: "https://svelte.dev",
8+
},
9+
{
10+
title: "TypeScript ESLint",
11+
link: "https://typescript-eslint.io",
12+
},
13+
{
14+
title: "TypeScript",
15+
link: "https://www.typescriptlang.org",
16+
},
17+
];
18+
</script>
19+
20+
<main>
21+
<Component {items} let:item>
22+
<div>
23+
{item.title}
24+
</div>
25+
</Component>
26+
<Component {items}>
27+
<div let:item>
28+
{item.title}
29+
</div>
30+
<span slot="count" let:count={foo}>
31+
{foo}
32+
</span>
33+
</Component>
34+
</main>

0 commit comments

Comments
 (0)