Skip to content

Commit 36b01ec

Browse files
authored
feat: support for use: directive parameter type (#325)
* feat: support for `use:` directive parameter type * Create wet-lizards-reflect.md
1 parent 96a72a5 commit 36b01ec

9 files changed

+15685
-28
lines changed

.changeset/wet-lizards-reflect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
feat: support for `use:` directive parameter type

src/context/script-let.ts

+49-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ScopeManager, Scope } from "eslint-scope";
22
import type * as ESTree from "estree";
33
import type { TSESTree } from "@typescript-eslint/types";
4+
import type { Scope as TSScope } from "@typescript-eslint/scope-manager";
45
import type { Context, ScriptsSourceCode } from ".";
56
import type {
67
Comment,
@@ -20,19 +21,19 @@ import {
2021
removeReference,
2122
removeScope,
2223
} from "../scope";
23-
import { traverseNodes } from "../traverse";
24+
import { getKeys, traverseNodes, getNodes } from "../traverse";
2425
import { UniqueIdGenerator } from "./unique";
2526

2627
type TSAsExpression = {
2728
type: "TSAsExpression";
2829
expression: ESTree.Expression;
29-
typeAnnotation: TSParenthesizedType | ESTree.Node;
30+
typeAnnotation: TSParenthesizedType | TSESTree.TypeNode;
3031
};
3132

3233
// TS ESLint v4 Node
3334
type TSParenthesizedType = {
3435
type: "TSParenthesizedType";
35-
typeAnnotation: ESTree.Node;
36+
typeAnnotation: TSESTree.TypeNode;
3637
};
3738

3839
export type ScriptLetCallback<E extends ESTree.Node> = (
@@ -167,30 +168,10 @@ export class ScriptLetContext {
167168
}
168169

169170
if (isTS) {
170-
const blockNode =
171-
tsAs!.typeAnnotation.type === "TSParenthesizedType"
172-
? tsAs!.typeAnnotation.typeAnnotation
173-
: tsAs!.typeAnnotation;
174-
const targetScopes = [result.getScope(blockNode)];
175-
let targetBlockNode: TSESTree.Node | TSParenthesizedType =
176-
blockNode as any;
177-
while (
178-
targetBlockNode.type === "TSConditionalType" ||
179-
targetBlockNode.type === "TSParenthesizedType"
180-
) {
181-
if (targetBlockNode.type === "TSParenthesizedType") {
182-
targetBlockNode = targetBlockNode.typeAnnotation as any;
183-
continue;
184-
}
185-
// TSConditionalType's `falseType` may not be a child scope.
186-
const falseType: TSESTree.TypeNode = targetBlockNode.falseType;
187-
const falseTypeScope = result.getScope(falseType as any);
188-
if (!targetScopes.includes(falseTypeScope)) {
189-
targetScopes.push(falseTypeScope);
190-
}
191-
targetBlockNode = falseType;
192-
}
193-
for (const scope of targetScopes) {
171+
for (const scope of extractTypeNodeScopes(
172+
tsAs!.typeAnnotation,
173+
result
174+
)) {
194175
removeScope(result.scopeManager, scope);
195176
}
196177
this.remapNodes(
@@ -1039,3 +1020,44 @@ function getNodeToScope(
10391020

10401021
return nodeToScope;
10411022
}
1023+
1024+
/** Extract the type scope of the given node. */
1025+
function extractTypeNodeScopes(
1026+
node: TSESTree.TypeNode | TSParenthesizedType,
1027+
result: ScriptLetCallbackOption
1028+
): Iterable<Scope> {
1029+
const scopes = new Set<Scope>();
1030+
for (const scope of iterateTypeNodeScopes(node)) {
1031+
scopes.add(scope);
1032+
}
1033+
1034+
return scopes;
1035+
1036+
/** Iterate the type scope of the given node. */
1037+
function* iterateTypeNodeScopes(
1038+
node: TSESTree.TypeNode | TSParenthesizedType
1039+
): Iterable<Scope> {
1040+
if (node.type === "TSParenthesizedType") {
1041+
// Skip TSParenthesizedType.
1042+
yield* iterateTypeNodeScopes(node.typeAnnotation);
1043+
} else if (node.type === "TSConditionalType") {
1044+
yield result.getScope(node as any);
1045+
// `falseType` of `TSConditionalType` is sibling scope.
1046+
const falseType: TSESTree.TypeNode = node.falseType;
1047+
yield* iterateTypeNodeScopes(falseType);
1048+
} else if (
1049+
node.type === "TSFunctionType" ||
1050+
node.type === "TSMappedType" ||
1051+
node.type === "TSConstructorType"
1052+
) {
1053+
yield result.getScope(node as any);
1054+
} else {
1055+
const typeNode: Exclude<TSESTree.TypeNode, TSScope["block"]> = node;
1056+
for (const key of getKeys(typeNode, result.visitorKeys)) {
1057+
for (const child of getNodes(typeNode, key)) {
1058+
yield* iterateTypeNodeScopes(child as TSESTree.TypeNode);
1059+
}
1060+
}
1061+
}
1062+
}
1063+
}

src/parser/converts/attr.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ function convertActionDirective(
577577
processExpression: buildProcessExpressionForExpression(
578578
directive,
579579
ctx,
580-
null
580+
`Parameters<typeof ${node.name}>[1]`
581581
),
582582
processName: (name) =>
583583
ctx.scriptLet.addExpression(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
type MyActionParam = () => (p: { foo: number }) => void;
3+
function myAction(_node: HTMLElement, params: MyActionParam) {
4+
const result = params();
5+
result({ foo: 1 });
6+
return {
7+
destroy: () => {},
8+
};
9+
}
10+
</script>
11+
12+
<div
13+
use:myAction={() => {
14+
return (param) => {
15+
param.foo;
16+
};
17+
}}
18+
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"ruleId": "no-unused-expressions",
4+
"code": "param.foo;",
5+
"line": 15,
6+
"column": 7
7+
}
8+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"ruleId": "no-unused-vars",
4+
"code": "p: { foo: number }",
5+
"line": 2,
6+
"column": 31
7+
}
8+
]

0 commit comments

Comments
 (0)