Skip to content

Commit

Permalink
feat: support for use: directive parameter type (#325)
Browse files Browse the repository at this point in the history
* feat: support for `use:` directive parameter type

* Create wet-lizards-reflect.md
  • Loading branch information
ota-meshi committed Apr 26, 2023
1 parent 96a72a5 commit 36b01ec
Show file tree
Hide file tree
Showing 9 changed files with 15,685 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-lizards-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": minor
---

feat: support for `use:` directive parameter type
76 changes: 49 additions & 27 deletions src/context/script-let.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ScopeManager, Scope } from "eslint-scope";
import type * as ESTree from "estree";
import type { TSESTree } from "@typescript-eslint/types";
import type { Scope as TSScope } from "@typescript-eslint/scope-manager";
import type { Context, ScriptsSourceCode } from ".";
import type {
Comment,
Expand All @@ -20,19 +21,19 @@ import {
removeReference,
removeScope,
} from "../scope";
import { traverseNodes } from "../traverse";
import { getKeys, traverseNodes, getNodes } from "../traverse";
import { UniqueIdGenerator } from "./unique";

type TSAsExpression = {
type: "TSAsExpression";
expression: ESTree.Expression;
typeAnnotation: TSParenthesizedType | ESTree.Node;
typeAnnotation: TSParenthesizedType | TSESTree.TypeNode;
};

// TS ESLint v4 Node
type TSParenthesizedType = {
type: "TSParenthesizedType";
typeAnnotation: ESTree.Node;
typeAnnotation: TSESTree.TypeNode;
};

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

if (isTS) {
const blockNode =
tsAs!.typeAnnotation.type === "TSParenthesizedType"
? tsAs!.typeAnnotation.typeAnnotation
: tsAs!.typeAnnotation;
const targetScopes = [result.getScope(blockNode)];
let targetBlockNode: TSESTree.Node | TSParenthesizedType =
blockNode as any;
while (
targetBlockNode.type === "TSConditionalType" ||
targetBlockNode.type === "TSParenthesizedType"
) {
if (targetBlockNode.type === "TSParenthesizedType") {
targetBlockNode = targetBlockNode.typeAnnotation as any;
continue;
}
// TSConditionalType's `falseType` may not be a child scope.
const falseType: TSESTree.TypeNode = targetBlockNode.falseType;
const falseTypeScope = result.getScope(falseType as any);
if (!targetScopes.includes(falseTypeScope)) {
targetScopes.push(falseTypeScope);
}
targetBlockNode = falseType;
}
for (const scope of targetScopes) {
for (const scope of extractTypeNodeScopes(
tsAs!.typeAnnotation,
result
)) {
removeScope(result.scopeManager, scope);
}
this.remapNodes(
Expand Down Expand Up @@ -1039,3 +1020,44 @@ function getNodeToScope(

return nodeToScope;
}

/** Extract the type scope of the given node. */
function extractTypeNodeScopes(
node: TSESTree.TypeNode | TSParenthesizedType,
result: ScriptLetCallbackOption
): Iterable<Scope> {
const scopes = new Set<Scope>();
for (const scope of iterateTypeNodeScopes(node)) {
scopes.add(scope);
}

return scopes;

/** Iterate the type scope of the given node. */
function* iterateTypeNodeScopes(
node: TSESTree.TypeNode | TSParenthesizedType
): Iterable<Scope> {
if (node.type === "TSParenthesizedType") {
// Skip TSParenthesizedType.
yield* iterateTypeNodeScopes(node.typeAnnotation);
} else if (node.type === "TSConditionalType") {
yield result.getScope(node as any);
// `falseType` of `TSConditionalType` is sibling scope.
const falseType: TSESTree.TypeNode = node.falseType;
yield* iterateTypeNodeScopes(falseType);
} else if (
node.type === "TSFunctionType" ||
node.type === "TSMappedType" ||
node.type === "TSConstructorType"
) {
yield result.getScope(node as any);
} else {
const typeNode: Exclude<TSESTree.TypeNode, TSScope["block"]> = node;
for (const key of getKeys(typeNode, result.visitorKeys)) {
for (const child of getNodes(typeNode, key)) {
yield* iterateTypeNodeScopes(child as TSESTree.TypeNode);
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/parser/converts/attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ function convertActionDirective(
processExpression: buildProcessExpressionForExpression(
directive,
ctx,
null
`Parameters<typeof ${node.name}>[1]`
),
processName: (name) =>
ctx.scriptLet.addExpression(
Expand Down
18 changes: 18 additions & 0 deletions tests/fixtures/parser/ast/ts-use01-input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
type MyActionParam = () => (p: { foo: number }) => void;
function myAction(_node: HTMLElement, params: MyActionParam) {
const result = params();
result({ foo: 1 });
return {
destroy: () => {},
};
}
</script>

<div
use:myAction={() => {
return (param) => {
param.foo;
};
}}
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"ruleId": "no-unused-expressions",
"code": "param.foo;",
"line": 15,
"column": 7
}
]
8 changes: 8 additions & 0 deletions tests/fixtures/parser/ast/ts-use01-no-unused-vars-result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"ruleId": "no-unused-vars",
"code": "p: { foo: number }",
"line": 2,
"column": 31
}
]

0 comments on commit 36b01ec

Please sign in to comment.