(param: T): U;
+ }
+ `,
+ errors: [
+ { messageId: 'sole', data: { name: 'T' } },
+ { messageId: 'sole', data: { name: 'U' } },
+ ],
+ },
+ {
+ code: `
+ declare class C {
+ prop: () => P;
+ }
+ `,
+ errors: [{ messageId: 'sole', data: { name: 'P' } }],
+ },
+ {
+ code: `
+ declare class Foo {
+ foo(this: T): void;
+ }
+ `,
+ errors: [
+ {
+ messageId: 'sole',
+ data: { name: 'T' },
+ },
+ ],
+ },
+ {
+ code: `
+ function third(a: A, b: B, c: C): C {
+ return c;
+ }
+ `,
+ errors: [
+ { messageId: 'sole', data: { name: 'A' } },
+ { messageId: 'sole', data: { name: 'B' } },
+ ],
+ },
+ {
+ code: `
+ function parseYAML(input: string): T {
+ return input as any as T;
+ }
+ `,
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: `
+ function printProperty(obj: T, key: K) {
+ console.log(obj[key]);
+ }
+ `,
+ errors: [{ messageId: 'sole', data: { name: 'K' } }],
+ },
+ {
+ code: `
+ function fn(param: string) {
+ let v: T = null!;
+ return v;
+ }
+ `,
+ errors: [
+ {
+ data: { name: 'T' },
+ messageId: 'sole',
+ },
+ ],
+ },
+ {
+ code: `
+ function both<
+ Args extends unknown[],
+ CB1 extends (...args: Args) => void,
+ CB2 extends (...args: Args) => void,
+ >(fn1: CB1, fn2: CB2): (...args: Args) => void {
+ return function (...args: Args) {
+ fn1(...args);
+ fn2(...args);
+ };
+ }
+ `,
+ errors: [
+ { messageId: 'sole', data: { name: 'CB1' } },
+ { messageId: 'sole', data: { name: 'CB2' } },
+ ],
+ },
+ {
+ code: `
+ function getLength(x: T) {
+ return x.length;
+ }
+ `,
+ errors: [{ messageId: 'sole' }],
+ },
+ {
+ code: `
+ interface Lengthy {
+ length: number;
+ }
+ function getLength(x: T) {
+ return x.length;
+ }
+ `,
+ errors: [{ messageId: 'sole' }],
+ },
+ {
+ code: 'declare function get(): T;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function get(): T;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function take(param: T): void;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function take(param: T): void;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function take(param1: T, param2: U): void;',
+ errors: [{ messageId: 'sole', data: { name: 'U' } }],
+ },
+ {
+ code: 'declare function take(param: T): U;',
+ errors: [{ messageId: 'sole', data: { name: 'U' } }],
+ },
+ {
+ code: 'declare function take(param: U): U;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function get(param: U): U;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'declare function get(param: T): U;',
+ errors: [{ messageId: 'sole', data: { name: 'U' } }],
+ },
+ {
+ code: 'declare function compare(param1: T, param2: U): boolean;',
+ errors: [{ messageId: 'sole', data: { name: 'U' } }],
+ },
+ {
+ code: 'declare function get(param: (param: U) => V): T;',
+ errors: [
+ { messageId: 'sole', data: { name: 'T' } },
+ { messageId: 'sole', data: { name: 'U' } },
+ { messageId: 'sole', data: { name: 'V' } },
+ ],
+ },
+ {
+ code: 'declare function get(param: (param: T) => U): T;',
+ errors: [
+ { messageId: 'sole', data: { name: 'T' } },
+ { messageId: 'sole', data: { name: 'T' } },
+ { messageId: 'sole', data: { name: 'U' } },
+ ],
+ },
+ {
+ code: 'type Fn = () => T;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'type Fn = () => [];',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: `
+ type Other = 0;
+ type Fn = () => Other;
+ `,
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: `
+ type Other = 0 | 1;
+ type Fn = () => Other;
+ `,
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'type Fn = (param: U) => void;',
+ errors: [{ messageId: 'sole', data: { name: 'U' } }],
+ },
+ {
+ code: 'type Ctr = new () => T;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'type Fn = () => { [K in keyof T]: K };',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: "type Fn = () => { [K in 'a']: T };",
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'type Fn = (value: unknown) => value is T;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ {
+ code: 'type Fn = () => `a${T}b`;',
+ errors: [{ messageId: 'sole', data: { name: 'T' } }],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-parameters.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-parameters.shot
new file mode 100644
index 000000000000..a1e7d6b72117
--- /dev/null
+++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-parameters.shot
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Rule schemas should be convertible to TS types for documentation purposes no-unnecessary-type-parameters 1`] = `
+"
+# SCHEMA:
+
+[]
+
+
+# TYPES:
+
+/** No options declared */
+type Options = [];"
+`;
diff --git a/packages/repo-tools/src/generate-contributors.mts b/packages/repo-tools/src/generate-contributors.mts
index 220750350dd5..7e27ce3345fc 100644
--- a/packages/repo-tools/src/generate-contributors.mts
+++ b/packages/repo-tools/src/generate-contributors.mts
@@ -35,6 +35,8 @@ interface User {
html_url: string;
}
+// This is an internal script, we're ok with the unsafe assertion. 🤫
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
async function getData(url: string | undefined): Promise {
if (url == null) {
return null;
diff --git a/packages/scope-manager/tests/test-utils/getSpecificNode.ts b/packages/scope-manager/tests/test-utils/getSpecificNode.ts
index d8189a9cdfed..aa075fb059fe 100644
--- a/packages/scope-manager/tests/test-utils/getSpecificNode.ts
+++ b/packages/scope-manager/tests/test-utils/getSpecificNode.ts
@@ -11,12 +11,13 @@ function getSpecificNode<
): Node;
function getSpecificNode<
Selector extends AST_NODE_TYPES,
- Node extends Extract,
ReturnType extends TSESTree.Node,
>(
ast: TSESTree.Node,
selector: Selector,
- cb: (node: Node) => ReturnType | null | undefined,
+ cb: (
+ node: Extract,
+ ) => ReturnType | null | undefined,
): ReturnType;
function getSpecificNode(
diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts
index dc899379b164..ba050418fbc9 100644
--- a/packages/typescript-eslint/src/configs/all.ts
+++ b/packages/typescript-eslint/src/configs/all.ts
@@ -104,6 +104,7 @@ export default (
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
+ '@typescript-eslint/no-unnecessary-type-parameters': 'error',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts
index 9df504415e37..e173917faaff 100644
--- a/packages/typescript-eslint/src/configs/disable-type-checked.ts
+++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts
@@ -35,6 +35,7 @@ export default (
'@typescript-eslint/no-unnecessary-template-expression': 'off',
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
+ '@typescript-eslint/no-unnecessary-type-parameters': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts
index 8b4496526d5c..8aeeaa8ff049 100644
--- a/packages/typescript-estree/src/parser-options.ts
+++ b/packages/typescript-estree/src/parser-options.ts
@@ -217,6 +217,8 @@ export type TSESTreeOptions = ParseAndGenerateServicesOptions;
// This lets us use generics to type the return value, and removes the need to
// handle the undefined type in the get method
export interface ParserWeakMap {
+ // This is unsafe internally, so it should only be exposed via safe wrappers.
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
get(key: Key): Value;
has(key: unknown): boolean;
}
diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts
index 306f25227409..59930729adbc 100644
--- a/packages/typescript-estree/tests/lib/convert.test.ts
+++ b/packages/typescript-estree/tests/lib/convert.test.ts
@@ -280,11 +280,17 @@ describe('convert', () => {
describe('suppressDeprecatedPropertyWarnings', () => {
const makeNodeGetter =
- (
+ <
+ // Small convenience for testing the nodes:
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+ S extends ts.Statement,
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+ TNode extends TSESTree.Node,
+ >(
code: string,
tsToEsNode: (statement: S) => TSNode,
) =>
- (converterOptions?: ConverterOptions): T => {
+ (converterOptions?: ConverterOptions): TNode => {
const ast = convertCode(code);
const instance = new Converter(ast, {
shouldPreserveNodeMaps: true,
diff --git a/packages/typescript-estree/tests/test-utils/test-utils.ts b/packages/typescript-estree/tests/test-utils/test-utils.ts
index 71a84f1691cb..8b08393ee5e7 100644
--- a/packages/typescript-estree/tests/test-utils/test-utils.ts
+++ b/packages/typescript-estree/tests/test-utils/test-utils.ts
@@ -77,7 +77,7 @@ export function isJSXFileType(fileType: string): boolean {
* @param ast the AST object
* @returns copy of the AST object
*/
-export function deeplyCopy(ast: T): T {
+export function deeplyCopy>(ast: T): T {
return omitDeep(ast) as T;
}
@@ -96,8 +96,8 @@ function isObjectLike(value: unknown): value is UnknownObject {
* @param selectors advance ast modifications
* @returns formatted object
*/
-export function omitDeep(
- root: T,
+export function omitDeep(
+ root: UnknownObject,
keysToOmit: { key: string; predicate: (value: unknown) => boolean }[] = [],
selectors: Record<
string,
@@ -152,5 +152,5 @@ export function omitDeep(
return node;
}
- return visit(root as UnknownObject, null);
+ return visit(root, null);
}
diff --git a/packages/utils/src/ast-utils/helpers.ts b/packages/utils/src/ast-utils/helpers.ts
index afe8a6fede89..c76d24f88246 100644
--- a/packages/utils/src/ast-utils/helpers.ts
+++ b/packages/utils/src/ast-utils/helpers.ts
@@ -40,6 +40,8 @@ export const isNodeOfTypeWithConditions = <
export const isTokenOfTypeWithConditions = <
TokenType extends AST_TOKEN_TYPES,
+ // This is technically unsafe, but we find it useful to extract out the type
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
ExtractedToken extends Extract,
Conditions extends Partial,
>(
diff --git a/packages/utils/src/eslint-utils/deepMerge.ts b/packages/utils/src/eslint-utils/deepMerge.ts
index f6513944c1bd..a4ace614df5d 100644
--- a/packages/utils/src/eslint-utils/deepMerge.ts
+++ b/packages/utils/src/eslint-utils/deepMerge.ts
@@ -4,7 +4,7 @@ type ObjectLike = Record;
* Check if the variable contains an object strictly rejecting arrays
* @returns `true` if obj is an object
*/
-function isObjectNotArray(obj: unknown): obj is T {
+function isObjectNotArray(obj: unknown): obj is ObjectLike {
return typeof obj === 'object' && obj != null && !Array.isArray(obj);
}
diff --git a/packages/website/src/globals.d.ts b/packages/website/src/globals.d.ts
index c7bf5f2aa0f6..966cee026906 100644
--- a/packages/website/src/globals.d.ts
+++ b/packages/website/src/globals.d.ts
@@ -3,6 +3,9 @@ import type * as ts from 'typescript';
declare global {
interface WindowRequire {
+ // We know it's an unsafe assertion. It's for window.require usage, so we
+ // don't have to use verbose type assertions on every call.
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
(
files: string[],
success?: (...arg: T) => void,