mirror of
https://github.com/chenasraf/DefinitelyTyped-tools.git
synced 2026-05-17 17:48:07 +00:00
Converted no-unnecessary-generics from TSLint to ESLint (#539)
* 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>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
126
packages/dtslint/src/rules/no-unnecessary-generics.ts
Normal file
126
packages/dtslint/src/rules/no-unnecessary-generics.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import * as Lint from "tslint";
|
||||
import * as ts from "typescript";
|
||||
|
||||
import { failure } from "../util";
|
||||
|
||||
export class Rule extends Lint.Rules.TypedRule {
|
||||
static metadata: Lint.IRuleMetadata = {
|
||||
ruleName: "no-unnecessary-generics",
|
||||
description: "Forbids signatures using a generic parameter only once.",
|
||||
optionsDescription: "Not configurable.",
|
||||
options: null,
|
||||
type: "style",
|
||||
typescriptOnly: true,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static FAILURE_STRING(typeParameter: string) {
|
||||
return failure(Rule.metadata.ruleName, `Type parameter ${typeParameter} is used only once.`);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static FAILURE_STRING_NEVER(typeParameter: string) {
|
||||
return failure(Rule.metadata.ruleName, `Type parameter ${typeParameter} is never used.`);
|
||||
}
|
||||
|
||||
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
|
||||
return this.applyWithFunction(sourceFile, (ctx) => walk(ctx, program.getTypeChecker()));
|
||||
}
|
||||
}
|
||||
|
||||
function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
|
||||
const { sourceFile } = ctx;
|
||||
sourceFile.forEachChild(function cb(node) {
|
||||
if (ts.isFunctionLike(node)) {
|
||||
checkSignature(node);
|
||||
}
|
||||
node.forEachChild(cb);
|
||||
});
|
||||
|
||||
function checkSignature(sig: ts.SignatureDeclaration) {
|
||||
if (!sig.typeParameters) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const tp of sig.typeParameters) {
|
||||
const typeParameter = tp.name.text;
|
||||
const res = getSoleUse(sig, assertDefined(checker.getSymbolAtLocation(tp.name)), checker);
|
||||
switch (res.type) {
|
||||
case "ok":
|
||||
break;
|
||||
case "sole":
|
||||
ctx.addFailureAtNode(res.soleUse, Rule.FAILURE_STRING(typeParameter));
|
||||
break;
|
||||
case "never":
|
||||
ctx.addFailureAtNode(tp, Rule.FAILURE_STRING_NEVER(typeParameter));
|
||||
break;
|
||||
default:
|
||||
assertNever(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertDefined<T>(value: T | undefined): T {
|
||||
if (value === undefined) {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function assertNever(_: never) {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
152
packages/dtslint/test/no-unnecessary-generics.test.ts
Normal file
152
packages/dtslint/test/no-unnecessary-generics.test.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { ESLintUtils } from "@typescript-eslint/utils";
|
||||
|
||||
import * as rule from "../src/rules/no-unnecessary-generics";
|
||||
|
||||
const ruleTester = new ESLintUtils.RuleTester({
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
tsconfigRootDir: __dirname,
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
});
|
||||
|
||||
ruleTester.run("no-unnecessary-generics", rule, {
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
const f2 = <T>(): T => {};
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 19,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
class C {
|
||||
constructor<T>(x: T) {}
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 3,
|
||||
column: 21,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
function f<T>(): T { }
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 18,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
function f<T>(x: { T: number }): void;
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 12,
|
||||
messageId: "never",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
function f<T, U extends T>(u: U): U;
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 25,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const f = function<T>(): T {};
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 26,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
interface I {
|
||||
<T>(value: T): void;
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 3,
|
||||
column: 14,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
interface I {
|
||||
m<T>(x: T): void;
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 3,
|
||||
column: 11,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
type Fn = <T>() => T;
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 20,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
type Ctr = new<T>() => T;
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
line: 2,
|
||||
column: 24,
|
||||
messageId: "sole",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
valid: [
|
||||
`function example(a: string): string;`,
|
||||
`function example<T>(a: T): T;`,
|
||||
`function example<T>(a: T[]): T;`,
|
||||
`function example<T>(a: Set<T>): T;`,
|
||||
`function example<T>(a: Set<T>, b: T[]): void;`,
|
||||
`function example<T>(a: Map<T, T>): void;`,
|
||||
`function example<T, U extends T>(t: T, u: U): U;`,
|
||||
],
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
interface I {
|
||||
<T>(value: T): void;
|
||||
~ [0]
|
||||
m<T>(x: T): void;
|
||||
~ [0]
|
||||
}
|
||||
|
||||
class C {
|
||||
constructor<T>(x: T) {}
|
||||
~ [0]
|
||||
}
|
||||
|
||||
type Fn = <T>() => T;
|
||||
~ [0]
|
||||
type Ctr = new<T>() => T;
|
||||
~ [0]
|
||||
|
||||
function f<T>(): T { }
|
||||
~ [0]
|
||||
|
||||
const f = function<T>(): T {};
|
||||
~ [0]
|
||||
const f2 = <T>(): T => {};
|
||||
~ [0]
|
||||
|
||||
function f<T>(x: { T: number }): void;
|
||||
~ [Type parameter T is never used. See: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/docs/no-unnecessary-generics.md]
|
||||
|
||||
function f<T, U extends T>(u: U): U;
|
||||
~ [0]
|
||||
|
||||
// OK:
|
||||
// Uses type parameter twice
|
||||
function foo<T>(m: Map<T, T>): void {}
|
||||
// `T` appears in a constraint, so it appears twice.
|
||||
function f<T, U extends T>(t: T, u: U): U;
|
||||
|
||||
[0]: Type parameter T is used only once. See: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/docs/no-unnecessary-generics.md
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"rulesDirectory": ["../../dist/rules"],
|
||||
"rules": {
|
||||
"no-unnecessary-generics": true
|
||||
}
|
||||
}
|
||||
8
packages/dtslint/test/tsconfig.json
Normal file
8
packages/dtslint/test/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "esnext"
|
||||
},
|
||||
"files": ["file.ts"]
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export function createLanguageServiceHost(
|
||||
getNewLine: () => ts.sys.newLine,
|
||||
getScriptFileNames: () => testPaths,
|
||||
fileExists: ts.sys.fileExists,
|
||||
readFile: ts.sys.readFile,
|
||||
getDirectories: ts.sys.getDirectories,
|
||||
getScriptSnapshot: (fileName) => ts.ScriptSnapshot.fromString(ts.sys.readFile(ensureExists(fileName))!),
|
||||
getScriptVersion: () => (version++).toString(),
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -9165,21 +9165,11 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^4.1.0:
|
||||
version "4.5.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
|
||||
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
||||
|
||||
typescript@^4.7.4:
|
||||
typescript@4.7.4, typescript@^4.1.0:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
|
||||
typescript@next:
|
||||
version "4.6.0-dev.20211126"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211126.tgz#d27ce3a360dc4da1dcdebd80efe42b51afdeebdb"
|
||||
integrity sha512-m+LKstqVv6FYW363aIbO6bm8awsLbeSUCzU6FxPtzUF/WJkFieQfYmdVwEIzigeTpw4E2GETBXnk6P6AixcQJQ==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.13.5"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.5.tgz#5d71d6dbba64cf441f32929b1efce7365bb4f113"
|
||||
|
||||
Reference in New Issue
Block a user