Skip to content

Commit 7508680

Browse files
authored
feat: improve props type (#435)
1 parent 0ef067b commit 7508680

Some content is hidden

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

42 files changed

+13584
-17
lines changed

.changeset/yellow-cooks-end.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 props type

src/parser/converts/attr.ts

+81-14
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { ParseError } from "../../errors";
3434
import type { ScriptLetCallback } from "../../context/script-let";
3535
import type { AttributeToken } from "../html";
36+
import { svelteVersion } from "../svelte-version";
3637

3738
/** Convert for Attributes */
3839
export function* convertAttributes(
@@ -200,6 +201,7 @@ function convertAttribute(
200201
processAttributeValue(
201202
node.value as (SvAST.Text | SvAST.MustacheTag)[],
202203
attribute,
204+
parent,
203205
ctx,
204206
);
205207

@@ -213,27 +215,51 @@ function convertAttribute(
213215
function processAttributeValue(
214216
nodeValue: (SvAST.Text | SvAST.MustacheTag)[],
215217
attribute: SvelteAttribute | SvelteStyleDirectiveLongform,
218+
attributeParent: (SvelteAttribute | SvelteStyleDirectiveLongform)["parent"],
216219
ctx: Context,
217220
) {
218-
for (let index = 0; index < nodeValue.length; index++) {
219-
const v = nodeValue[index];
220-
if (v.type === "Text") {
221-
if (v.start === v.end) {
222-
// Empty
221+
const nodes = nodeValue
222+
.filter(
223+
(v) =>
224+
v.type !== "Text" ||
225+
// ignore empty
223226
// https://github.com/sveltejs/svelte/pull/6539
224-
continue;
225-
}
226-
const next = nodeValue[index + 1];
227-
if (next && next.start < v.end) {
228-
// Maybe bug in Svelte can cause the completion index to shift.
229-
// console.log(ctx.getText(v), v.data)
230-
v.end = next.start;
227+
v.start < v.end,
228+
)
229+
.map((v, index, array) => {
230+
if (v.type === "Text") {
231+
const next = array[index + 1];
232+
if (next && next.start < v.end) {
233+
// Maybe bug in Svelte can cause the completion index to shift.
234+
return {
235+
...v,
236+
end: next.start,
237+
};
238+
}
231239
}
240+
return v;
241+
});
242+
if (
243+
nodes.length === 1 &&
244+
nodes[0].type === "MustacheTag" &&
245+
attribute.type === "SvelteAttribute"
246+
) {
247+
const typing = buildAttributeType(
248+
attributeParent.parent,
249+
attribute.key.name,
250+
ctx,
251+
);
252+
const mustache = convertMustacheTag(nodes[0], attribute, typing, ctx);
253+
attribute.value.push(mustache);
254+
return;
255+
}
256+
for (const v of nodes) {
257+
if (v.type === "Text") {
232258
attribute.value.push(convertTextToLiteral(v, attribute, ctx));
233259
continue;
234260
}
235261
if (v.type === "MustacheTag") {
236-
const mustache = convertMustacheTag(v, attribute, ctx);
262+
const mustache = convertMustacheTag(v, attribute, null, ctx);
237263
attribute.value.push(mustache);
238264
continue;
239265
}
@@ -246,6 +272,47 @@ function processAttributeValue(
246272
}
247273
}
248274

275+
/** Build attribute type */
276+
function buildAttributeType(
277+
element: SvelteElement | SvelteScriptElement | SvelteStyleElement,
278+
attrName: string,
279+
ctx: Context,
280+
) {
281+
if (
282+
svelteVersion.gte(5) &&
283+
attrName.startsWith("on") &&
284+
(element.type !== "SvelteElement" || element.kind === "html")
285+
) {
286+
return buildEventHandlerType(element, attrName.slice(2), ctx);
287+
}
288+
if (element.type !== "SvelteElement" || element.kind !== "component") {
289+
return null;
290+
}
291+
const elementName = ctx.elements.get(element)!.name;
292+
const componentPropsType = `import('svelte').ComponentProps<${elementName}>`;
293+
return conditional({
294+
check: `'${attrName}'`,
295+
extends: `infer PROP`,
296+
true: conditional({
297+
check: `PROP`,
298+
extends: `keyof ${componentPropsType}`,
299+
true: `${componentPropsType}[PROP]`,
300+
false: `never`,
301+
}),
302+
false: `never`,
303+
});
304+
305+
/** Generate `C extends E ? T : F` type. */
306+
function conditional(types: {
307+
check: string;
308+
extends: string;
309+
true: string;
310+
false: string;
311+
}) {
312+
return `${types.check} extends ${types.extends}?(${types.true}):(${types.false})`;
313+
}
314+
}
315+
249316
/** Convert for Spread */
250317
function convertSpreadAttribute(
251318
node: SvAST.Spread,
@@ -491,7 +558,7 @@ function convertStyleDirective(
491558
end: keyName.range[1],
492559
});
493560

494-
processAttributeValue(node.value, directive, ctx);
561+
processAttributeValue(node.value, directive, parent, ctx);
495562

496563
return directive;
497564
}

src/parser/converts/element.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function* convertChildren(
117117
continue;
118118
}
119119
if (child.type === "MustacheTag") {
120-
yield convertMustacheTag(child, parent, ctx);
120+
yield convertMustacheTag(child, parent, null, ctx);
121121
continue;
122122
}
123123
if (child.type === "RawMustacheTag") {

src/parser/converts/mustache.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import type * as SvAST from "../svelte-ast-types";
1010
export function convertMustacheTag(
1111
node: SvAST.MustacheTag,
1212
parent: SvelteMustacheTag["parent"],
13+
typing: string | null,
1314
ctx: Context,
1415
): SvelteMustacheTagText {
15-
return convertMustacheTag0(node, "text", parent, ctx);
16+
return convertMustacheTag0(node, "text", parent, typing, ctx);
1617
}
1718
/** Convert for MustacheTag */
1819
export function convertRawMustacheTag(
@@ -24,6 +25,7 @@ export function convertRawMustacheTag(
2425
node,
2526
"raw",
2627
parent,
28+
null,
2729
ctx,
2830
);
2931
const atHtmlStart = ctx.code.indexOf("@html", mustache.range[0]);
@@ -64,6 +66,7 @@ function convertMustacheTag0<T extends SvelteMustacheTag>(
6466
node: SvAST.MustacheTag | SvAST.RawMustacheTag,
6567
kind: T["kind"],
6668
parent: T["parent"],
69+
typing: string | null,
6770
ctx: Context,
6871
): T {
6972
const mustache = {
@@ -73,7 +76,7 @@ function convertMustacheTag0<T extends SvelteMustacheTag>(
7376
parent,
7477
...ctx.getConvertLocation(node),
7578
} as T;
76-
ctx.scriptLet.addExpression(node.expression, mustache, null, (es) => {
79+
ctx.scriptLet.addExpression(node.expression, mustache, typing, (es) => {
7780
mustache.expression = es;
7881
});
7982
return mustache;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import Component from 'foo.svelte'
3+
</script>
4+
<button onclick="{e=>{}}"></button>
5+
<Component onclick="{e=>{}}"></Component>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"ruleId": "no-unused-vars",
4+
"code": "e",
5+
"line": 4,
6+
"column": 19
7+
},
8+
{
9+
"ruleId": "no-unused-vars",
10+
"code": "e",
11+
"line": 5,
12+
"column": 22
13+
}
14+
]

0 commit comments

Comments
 (0)