Skip to content

feat: improve let directive type #395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/six-deers-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": minor
---

feat: improve let directive type
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@
"prettier-plugin-svelte": "^3.0.0",
"rimraf": "^5.0.1",
"semver": "^7.5.1",
"svelte": "^4.0.0",
"svelte2tsx": "^0.6.15",
"svelte": "^4.2.0",
"svelte2tsx": "^0.6.20",
"typescript": "~5.1.3",
"typescript-eslint-parser-for-extra-files": "^0.5.0"
},
Expand Down
15 changes: 15 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
Token,
} from "../ast";
import type ESTree from "estree";
import type * as SvAST from "../parser/svelte-ast-types";
import { ScriptLetContext } from "./script-let";
import { LetDirectiveCollections } from "./let-directive-collection";
import { getParserForLang } from "../parser/resolve-parser";
Expand Down Expand Up @@ -135,6 +136,20 @@ export class Context {

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

public readonly elements = new Map<
SvelteElement,
| SvAST.InlineComponent
| SvAST.Element
| SvAST.Window
| SvAST.Document
| SvAST.Body
| SvAST.Head
| SvAST.Options
| SvAST.SlotTemplate
| SvAST.Slot
| SvAST.Title
>();

// ----- States ------
private readonly state: { isTypeScript?: boolean } = {};

Expand Down
82 changes: 73 additions & 9 deletions src/parser/converts/attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import type { AttributeToken } from "../html";
export function* convertAttributes(
attributes: SvAST.AttributeOrDirective[],
parent: SvelteStartTag,
elementName: string,
ctx: Context,
): IterableIterator<
| SvelteAttribute
Expand All @@ -59,7 +58,7 @@ export function* convertAttributes(
continue;
}
if (attr.type === "EventHandler") {
yield convertEventHandlerDirective(attr, parent, elementName, ctx);
yield convertEventHandlerDirective(attr, parent, ctx);
continue;
}
if (attr.type === "Class") {
Expand Down Expand Up @@ -318,7 +317,6 @@ function convertBindingDirective(
function convertEventHandlerDirective(
node: SvAST.DirectiveForExpression,
parent: SvelteDirective["parent"],
elementName: string,
ctx: Context,
): SvelteEventHandlerDirective {
const directive: SvelteEventHandlerDirective = {
Expand All @@ -329,7 +327,7 @@ function convertEventHandlerDirective(
parent,
...ctx.getConvertLocation(node),
};
const typing = buildEventHandlerType(parent.parent, elementName, node.name);
const typing = buildEventHandlerType(parent.parent, node.name, ctx);
processDirective(node, directive, ctx, {
processExpression: buildProcessExpressionForExpression(
directive,
Expand All @@ -343,8 +341,8 @@ function convertEventHandlerDirective(
/** Build event handler type */
function buildEventHandlerType(
element: SvelteElement | SvelteScriptElement | SvelteStyleElement,
elementName: string,
eventName: string,
ctx: Context,
) {
const nativeEventHandlerType = `(e:${conditional({
check: `'${eventName}'`,
Expand All @@ -360,6 +358,7 @@ function buildEventHandlerType(
if (element.type !== "SvelteElement") {
return nativeEventHandlerType;
}
const elementName = ctx.elements.get(element)!.name;
if (element.kind === "component") {
const componentEventsType = `import('svelte').ComponentEvents<${elementName}>`;
return `(e:${conditional({
Expand Down Expand Up @@ -608,23 +607,88 @@ function convertLetDirective(
processPattern(pattern) {
return ctx.letDirCollections
.getCollection()
.addPattern(pattern, directive, "any");
.addPattern(
pattern,
directive,
buildLetDirectiveType(parent.parent, node.name, ctx),
);
},
processName: node.expression
? undefined
: (name) => {
// shorthand
ctx.letDirCollections
.getCollection()
.addPattern(name, directive, "any", (es) => {
directive.expression = es;
});
.addPattern(
name,
directive,
buildLetDirectiveType(parent.parent, node.name, ctx),
(es) => {
directive.expression = es;
},
);
return [];
},
});
return directive;
}

/** Build let directive param type */
function buildLetDirectiveType(
element: SvelteElement | SvelteScriptElement | SvelteStyleElement,
letName: string,
ctx: Context,
) {
if (element.type !== "SvelteElement") {
return "any";
}
let slotName = "default";
let componentName: string;
const svelteNode = ctx.elements.get(element)!;
const slotAttr = svelteNode.attributes.find(
(attr): attr is SvAST.Attribute => {
return attr.type === "Attribute" && attr.name === "slot";
},
);
if (slotAttr) {
if (
Array.isArray(slotAttr.value) &&
slotAttr.value.length === 1 &&
slotAttr.value[0].type === "Text"
) {
slotName = slotAttr.value[0].data;
} else {
return "any";
}
const parent = findParentComponent(element);
if (parent == null) return "any";
componentName = ctx.elements.get(parent)!.name;
} else {
if (element.kind === "component") {
componentName = svelteNode.name;
} else {
const parent = findParentComponent(element);
if (parent == null) return "any";
componentName = ctx.elements.get(parent)!.name;
}
}
return `${String(componentName)}['$$slot_def'][${JSON.stringify(
slotName,
)}][${JSON.stringify(letName)}]`;

/** Find parent component element */
function findParentComponent(node: SvelteElement) {
let parent: SvelteElement["parent"] | null = node.parent;
while (parent && parent.type !== "SvelteElement") {
parent = node.parent;
}
if (!parent || parent.kind !== "component") {
return null;
}
return parent;
}
}

type DirectiveProcessors<
D extends SvAST.Directive,
S extends SvelteDirective,
Expand Down
21 changes: 12 additions & 9 deletions src/parser/converts/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ function convertHTMLElement(
parent,
...locs,
};
ctx.elements.set(element, node);
element.startTag.parent = element;
const elementName = node.name;

Expand All @@ -265,19 +266,19 @@ function convertHTMLElement(
if (letDirectives.length) {
ctx.letDirCollections.beginExtract();
element.startTag.attributes.push(
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
...convertAttributes(letDirectives, element.startTag, ctx),
);
letParams.push(...ctx.letDirCollections.extract().getLetParams());
}
if (!letParams.length && !needScopeByChildren(node)) {
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
element.children.push(...convertChildren(node, element, ctx));
} else {
ctx.scriptLet.nestBlock(element, letParams);
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
sortNodes(element.startTag.attributes);
element.children.push(...convertChildren(node, element, ctx));
Expand Down Expand Up @@ -366,6 +367,7 @@ function convertSpecialElement(
parent,
...locs,
};
ctx.elements.set(element, node);
element.startTag.parent = element;
const elementName = node.name;

Expand All @@ -374,19 +376,19 @@ function convertSpecialElement(
if (letDirectives.length) {
ctx.letDirCollections.beginExtract();
element.startTag.attributes.push(
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
...convertAttributes(letDirectives, element.startTag, ctx),
);
letParams.push(...ctx.letDirCollections.extract().getLetParams());
}
if (!letParams.length && !needScopeByChildren(node)) {
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
element.children.push(...convertChildren(node, element, ctx));
} else {
ctx.scriptLet.nestBlock(element, letParams);
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
sortNodes(element.startTag.attributes);
element.children.push(...convertChildren(node, element, ctx));
Expand Down Expand Up @@ -606,6 +608,7 @@ function convertComponentElement(
parent,
...locs,
};
ctx.elements.set(element, node);
element.startTag.parent = element;
const elementName = node.name;

Expand All @@ -614,19 +617,19 @@ function convertComponentElement(
if (letDirectives.length) {
ctx.letDirCollections.beginExtract();
element.startTag.attributes.push(
...convertAttributes(letDirectives, element.startTag, elementName, ctx),
...convertAttributes(letDirectives, element.startTag, ctx),
);
letParams.push(...ctx.letDirCollections.extract().getLetParams());
}
if (!letParams.length && !needScopeByChildren(node)) {
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
element.children.push(...convertChildren(node, element, ctx));
} else {
ctx.scriptLet.nestBlock(element, letParams);
element.startTag.attributes.push(
...convertAttributes(attributes, element.startTag, elementName, ctx),
...convertAttributes(attributes, element.startTag, ctx),
);
sortNodes(element.startTag.attributes);
element.children.push(...convertChildren(node, element, ctx));
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/parser/ast/ts-let/lib/Component.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts" context="module">
export interface ListItem {
title: string;
link: string;
}
</script>

<script lang="ts">
export let items: ListItem[] = [];
</script>

<div>
<ul>
{#each items as item (item.title)}
<li>
<slot {item} />
</li>
{/each}
</ul>
</div>
<slot name="count" count={items.length} />
3 changes: 3 additions & 0 deletions tests/fixtures/parser/ast/ts-let/ts-let01-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"parser": "typescript-eslint-parser-for-extra-files"
}
34 changes: 34 additions & 0 deletions tests/fixtures/parser/ast/ts-let/ts-let01-input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import Component, { ListItem } from "./lib/Component.svelte";

const items: ListItem[] = [
{
title: "Svelte.dev",
link: "https://svelte.dev",
},
{
title: "TypeScript ESLint",
link: "https://typescript-eslint.io",
},
{
title: "TypeScript",
link: "https://www.typescriptlang.org",
},
];
</script>

<main>
<Component {items} let:item>
<div>
{item.title}
</div>
</Component>
<Component {items}>
<div let:item>
{item.title}
</div>
<span slot="count" let:count={foo}>
{foo}
</span>
</Component>
</main>
Loading