Skip to content

Commit

Permalink
Converted no-unnecessary-generics from TSLint to ESLint (#539)
Browse files Browse the repository at this point in the history
* Converted no-unnecessary-generics from TSLint to ESLint

* ...and don't forget dtslint.json

* Add missing readFile

* Add valid test case from original

* Fix spelling of recur

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
  • Loading branch information
JoshuaKGoldberg and sandersn committed Dec 29, 2022
1 parent 8f8ca80 commit c941b4a
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 174 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -28,6 +28,6 @@
"ts-jest": "^25.2.1",
"tslint": "^6.1.2",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^4.7.4"
"typescript": "4.7.4"
}
}
1 change: 0 additions & 1 deletion packages/dtslint/dtslint.json
Expand Up @@ -11,7 +11,6 @@
"strict-export-declare-modifiers": true,
"no-any-union": true,
"no-single-declare-module": true,
"no-unnecessary-generics": true,
"prefer-declare-function": true,
"unified-signatures": true,
"void-return": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/dtslint/package.json
Expand Up @@ -44,7 +44,7 @@
"@types/fs-extra": "^5.0.2",
"@types/json-stable-stringify": "^1.0.32",
"@types/strip-json-comments": "^0.0.28",
"typescript": "next"
"typescript": "4.7.4"
},
"engines": {
"node": ">=10.0.0"
Expand Down
126 changes: 126 additions & 0 deletions packages/dtslint/src/rules/no-unnecessary-generics.ts
@@ -0,0 +1,126 @@
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
import * as ts from "typescript";

import { createRule } from "../util";

type ESTreeFunctionLikeWithTypeParameters = TSESTree.FunctionLike & {
typeParameters: {};
};

type TSSignatureDeclarationWithTypeParameters = ts.SignatureDeclaration & {
typeParameters: {};
};

const rule = createRule({
defaultOptions: [],
meta: {
docs: {
description: "Forbids signatures using a generic parameter only once.",
recommended: "error",
},
messages: {
never: "Type parameter {{name}} is never used.",
sole: "Type parameter {{name}} is used only once.",
},
schema: [],
type: "problem",
},
name: "no-relative-import-in-test",
create(context) {
return {
[[
"ArrowFunctionExpression[typeParameters]",
"FunctionDeclaration[typeParameters]",
"FunctionExpression[typeParameters]",
"TSCallSignatureDeclaration[typeParameters]",
"TSConstructorType[typeParameters]",
"TSDeclareFunction[typeParameters]",
"TSFunctionType[typeParameters]",
"TSMethodSignature[typeParameters]",
].join(", ")](esNode: ESTreeFunctionLikeWithTypeParameters) {
const parserServices = ESLintUtils.getParserServices(context);
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(esNode) as TSSignatureDeclarationWithTypeParameters;
if (!tsNode.typeParameters) {
return;
}

const checker = parserServices.program.getTypeChecker();

for (const typeParameter of tsNode.typeParameters) {
const name = typeParameter.name.text;
const res = getSoleUse(tsNode, assertDefined(checker.getSymbolAtLocation(typeParameter.name)), checker);
switch (res.type) {
case "sole":
context.report({
data: { name },
messageId: "sole",
node: parserServices.tsNodeToESTreeNodeMap.get(res.soleUse),
});
break;
case "never":
context.report({
data: { name },
messageId: "never",
node: parserServices.tsNodeToESTreeNodeMap.get(typeParameter),
});
break;
}
}
},
};
},
});

type Result = { type: "ok" | "never" } | { type: "sole"; soleUse: ts.Identifier };
function getSoleUse(sig: ts.SignatureDeclaration, typeParameterSymbol: ts.Symbol, checker: ts.TypeChecker): Result {
const exit = {};
let soleUse: ts.Identifier | undefined;

try {
if (sig.typeParameters) {
for (const tp of sig.typeParameters) {
if (tp.constraint) {
recur(tp.constraint);
}
}
}
for (const param of sig.parameters) {
if (param.type) {
recur(param.type);
}
}
if (sig.type) {
recur(sig.type);
}
} catch (err) {
if (err === exit) {
return { type: "ok" };
}
throw err;
}

return soleUse ? { type: "sole", soleUse } : { type: "never" };

function recur(node: ts.Node): void {
if (ts.isIdentifier(node)) {
if (checker.getSymbolAtLocation(node) === typeParameterSymbol) {
if (soleUse === undefined) {
soleUse = node;
} else {
throw exit;
}
}
} else {
node.forEachChild(recur);
}
}
}

export = rule;

function assertDefined<T>(value: T | undefined): T {
if (value === undefined) {
throw new Error("unreachable");
}
return value;
}
115 changes: 0 additions & 115 deletions packages/dtslint/src/rules/noUnnecessaryGenericsRule.ts

This file was deleted.

0 comments on commit c941b4a

Please sign in to comment.