diff --git a/package.json b/package.json index 703d67b994..1a6a7edcae 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/packages/dtslint/dtslint.json b/packages/dtslint/dtslint.json index a7eeeef2a6..d11d33e07d 100644 --- a/packages/dtslint/dtslint.json +++ b/packages/dtslint/dtslint.json @@ -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, "trim-file": true, "unified-signatures": true, diff --git a/packages/dtslint/package.json b/packages/dtslint/package.json index 8a65633182..d95a34fcf8 100644 --- a/packages/dtslint/package.json +++ b/packages/dtslint/package.json @@ -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" diff --git a/packages/dtslint/src/rules/no-unnecessary-generics.ts b/packages/dtslint/src/rules/no-unnecessary-generics.ts new file mode 100644 index 0000000000..70312b65fc --- /dev/null +++ b/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(value: T | undefined): T { + if (value === undefined) { + throw new Error("unreachable"); + } + return value; +} diff --git a/packages/dtslint/src/rules/noUnnecessaryGenericsRule.ts b/packages/dtslint/src/rules/noUnnecessaryGenericsRule.ts deleted file mode 100644 index 9496e4c64c..0000000000 --- a/packages/dtslint/src/rules/noUnnecessaryGenericsRule.ts +++ /dev/null @@ -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, 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(value: T | undefined): T { - if (value === undefined) { - throw new Error("unreachable"); - } - return value; -} -function assertNever(_: never) { - throw new Error("unreachable"); -} diff --git a/packages/dtslint/test/no-unnecessary-generics.test.ts b/packages/dtslint/test/no-unnecessary-generics.test.ts new file mode 100644 index 0000000000..3ec5fa0f8c --- /dev/null +++ b/packages/dtslint/test/no-unnecessary-generics.test.ts @@ -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 => {}; + `, + errors: [ + { + line: 2, + column: 19, + messageId: "sole", + }, + ], + }, + { + code: ` +class C { + constructor(x: T) {} +} + `, + errors: [ + { + line: 3, + column: 21, + messageId: "sole", + }, + ], + }, + { + code: ` +function f(): T { } + `, + errors: [ + { + line: 2, + column: 18, + messageId: "sole", + }, + ], + }, + { + code: ` +function f(x: { T: number }): void; + `, + errors: [ + { + line: 2, + column: 12, + messageId: "never", + }, + ], + }, + { + code: ` +function f(u: U): U; + `, + errors: [ + { + line: 2, + column: 25, + messageId: "sole", + }, + ], + }, + { + code: ` +const f = function(): T {}; + `, + errors: [ + { + line: 2, + column: 26, + messageId: "sole", + }, + ], + }, + { + code: ` +interface I { + (value: T): void; +} + `, + errors: [ + { + line: 3, + column: 14, + messageId: "sole", + }, + ], + }, + { + code: ` +interface I { + m(x: T): void; +} + `, + errors: [ + { + line: 3, + column: 11, + messageId: "sole", + }, + ], + }, + { + code: ` +type Fn = () => T; + `, + errors: [ + { + line: 2, + column: 20, + messageId: "sole", + }, + ], + }, + { + code: ` +type Ctr = new() => T; + `, + errors: [ + { + line: 2, + column: 24, + messageId: "sole", + }, + ], + }, + ], + valid: [ + `function example(a: string): string;`, + `function example(a: T): T;`, + `function example(a: T[]): T;`, + `function example(a: Set): T;`, + `function example(a: Set, b: T[]): void;`, + `function example(a: Map): void;`, + `function example(t: T, u: U): U;`, + ], +}); diff --git a/packages/dtslint/test/no-unnecessary-generics/test.ts.lint b/packages/dtslint/test/no-unnecessary-generics/test.ts.lint deleted file mode 100644 index 07c8bbb869..0000000000 --- a/packages/dtslint/test/no-unnecessary-generics/test.ts.lint +++ /dev/null @@ -1,38 +0,0 @@ -interface I { - (value: T): void; - ~ [0] - m(x: T): void; - ~ [0] -} - -class C { - constructor(x: T) {} - ~ [0] -} - -type Fn = () => T; - ~ [0] -type Ctr = new() => T; - ~ [0] - -function f(): T { } - ~ [0] - -const f = function(): T {}; - ~ [0] -const f2 = (): T => {}; - ~ [0] - -function f(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(u: U): U; - ~ [0] - -// OK: -// Uses type parameter twice -function foo(m: Map): void {} -// `T` appears in a constraint, so it appears twice. -function f(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 diff --git a/packages/dtslint/test/no-unnecessary-generics/tsconfig.json b/packages/dtslint/test/no-unnecessary-generics/tsconfig.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/packages/dtslint/test/no-unnecessary-generics/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/packages/dtslint/test/no-unnecessary-generics/tslint.json b/packages/dtslint/test/no-unnecessary-generics/tslint.json deleted file mode 100644 index 0ad24bae5d..0000000000 --- a/packages/dtslint/test/no-unnecessary-generics/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rulesDirectory": ["../../dist/rules"], - "rules": { - "no-unnecessary-generics": true - } -} diff --git a/packages/dtslint/test/tsconfig.json b/packages/dtslint/test/tsconfig.json new file mode 100644 index 0000000000..e5619d0a39 --- /dev/null +++ b/packages/dtslint/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "strict": true, + "target": "esnext" + }, + "files": ["file.ts"] +} diff --git a/packages/perf/src/measure/createLanguageServiceHost.ts b/packages/perf/src/measure/createLanguageServiceHost.ts index ce33e5b015..74761dfabc 100644 --- a/packages/perf/src/measure/createLanguageServiceHost.ts +++ b/packages/perf/src/measure/createLanguageServiceHost.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(), diff --git a/yarn.lock b/yarn.lock index 1a8d3cb64c..0294f02539 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9085,21 +9085,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"