From 8d111e349282c836cf6c2d2b0566c34a9071c6a6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 3 Sep 2024 00:54:53 -0400 Subject: [PATCH 1/3] feat(eslint-plugin): [no-unsafe-argument] differentiate error types --- .../src/rules/no-unsafe-argument.ts | 47 +++++- .../tests/rules/no-unsafe-argument.test.ts | 137 +++++++++++++----- 2 files changed, 138 insertions(+), 46 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index 08950fa8d732..8e2db0547064 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -1,5 +1,6 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; import type * as ts from 'typescript'; import { @@ -150,11 +151,11 @@ export default createRule<[], MessageIds>({ }, messages: { unsafeArgument: - 'Unsafe argument of type `{{sender}}` assigned to a parameter of type `{{receiver}}`.', + 'Unsafe argument of type {{sender}} assigned to a parameter of type {{receiver}}.', unsafeTupleSpread: - 'Unsafe spread of a tuple type. The argument is of type `{{sender}}` and is assigned to a parameter of type `{{receiver}}`.', - unsafeArraySpread: 'Unsafe spread of an `any` array type.', - unsafeSpread: 'Unsafe spread of an `any` type.', + 'Unsafe spread of a tuple type. The argument is {{sender}} and is assigned to a parameter of type {{receiver}}.', + unsafeArraySpread: 'Unsafe spread of an {{sender}} array type.', + unsafeSpread: 'Unsafe spread of an {{sender}} type.', }, schema: [], }, @@ -163,6 +164,34 @@ export default createRule<[], MessageIds>({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + function describeType(type: ts.Type): string { + if (tsutils.isIntrinsicErrorType(type)) { + return 'error typed'; + } + + return `\`${checker.typeToString(type)}\``; + } + + function describeTypeForSpread(type: ts.Type): string { + if ( + checker.isArrayType(type) && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + tsutils.isIntrinsicErrorType(type.typeArguments![0]) + ) { + return 'error'; + } + + return describeType(type); + } + + function describeTypeForTuple(type: ts.Type): string { + if (tsutils.isIntrinsicErrorType(type)) { + return 'error typed'; + } + + return `of type \`${checker.typeToString(type)}\``; + } + function checkUnsafeArguments( args: TSESTree.Expression[] | TSESTree.CallExpressionArgument[], callee: TSESTree.Expression, @@ -200,6 +229,7 @@ export default createRule<[], MessageIds>({ if (isTypeAnyType(spreadArgType)) { // foo(...any) context.report({ + data: { sender: describeType(spreadArgType) }, node: argument, messageId: 'unsafeSpread', }); @@ -208,6 +238,7 @@ export default createRule<[], MessageIds>({ // TODO - we could break down the spread and compare the array type against each argument context.report({ + data: { sender: describeTypeForSpread(spreadArgType) }, node: argument, messageId: 'unsafeArraySpread', }); @@ -233,8 +264,8 @@ export default createRule<[], MessageIds>({ node: argument, messageId: 'unsafeTupleSpread', data: { - sender: checker.typeToString(tupleType), - receiver: checker.typeToString(parameterType), + sender: describeTypeForTuple(tupleType), + receiver: describeType(parameterType), }, }); } @@ -270,8 +301,8 @@ export default createRule<[], MessageIds>({ node: argument, messageId: 'unsafeArgument', data: { - sender: checker.typeToString(argumentType), - receiver: checker.typeToString(parameterType), + sender: describeType(argumentType), + receiver: describeType(parameterType), }, }); } diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts index 731bd440879b..64ab79433488 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-argument.test.ts @@ -132,8 +132,26 @@ foo(1 as any); column: 5, endColumn: 13, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', + }, + }, + ], + }, + { + code: ` +declare function foo(arg: number): void; +foo(error); + `, + errors: [ + { + messageId: 'unsafeArgument', + line: 3, + column: 5, + endColumn: 10, + data: { + sender: 'error typed', + receiver: '`number`', }, }, ], @@ -150,8 +168,8 @@ foo(1, 1 as any); column: 8, endColumn: 16, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -168,8 +186,8 @@ foo(1, 2, 3, 1 as any); column: 14, endColumn: 22, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, ], @@ -186,8 +204,8 @@ foo(1 as any, 1 as any); column: 5, endColumn: 13, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, { @@ -196,8 +214,8 @@ foo(1 as any, 1 as any); column: 15, endColumn: 23, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, ], @@ -225,6 +243,7 @@ foo(...(x as any[])); `, errors: [ { + data: { sender: '`any[]`' }, messageId: 'unsafeArraySpread', line: 4, column: 5, @@ -236,6 +255,24 @@ foo(...(x as any[])); code: ` declare function foo(arg1: string, arg2: number): void; +declare const errors: error[]; + +foo(...errors); + `, + errors: [ + { + data: { sender: 'error' }, + messageId: 'unsafeArraySpread', + line: 6, + column: 5, + endColumn: 14, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number): void; + const x = ['a', 1 as any] as const; foo(...x); `, @@ -246,8 +283,28 @@ foo(...x); column: 5, endColumn: 9, data: { - sender: 'any', - receiver: 'number', + sender: 'of type `any`', + receiver: '`number`', + }, + }, + ], + }, + { + code: ` +declare function foo(arg1: string, arg2: number): void; + +const x = ['a', error] as const; +foo(...x); + `, + errors: [ + { + messageId: 'unsafeTupleSpread', + line: 5, + column: 5, + endColumn: 9, + data: { + sender: 'error typed', + receiver: '`number`', }, }, ], @@ -259,6 +316,10 @@ foo(...(['foo', 1, 2] as [string, any, number])); `, errors: [ { + data: { + sender: 'of type `any`', + receiver: '`number`', + }, messageId: 'unsafeTupleSpread', line: 3, column: 5, @@ -280,8 +341,8 @@ foo('a', ...x, 1 as any); column: 16, endColumn: 24, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -300,8 +361,8 @@ foo('a', ...x, 1 as any); column: 16, endColumn: 24, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -320,8 +381,8 @@ foo(new Set(), ...x); column: 5, endColumn: 19, data: { - sender: 'Set', - receiver: 'Set', + sender: '`Set`', + receiver: '`Set`', }, }, { @@ -330,8 +391,8 @@ foo(new Set(), ...x); column: 21, endColumn: 25, data: { - sender: 'Map', - receiver: 'Map', + sender: 'of type `Map`', + receiver: '`Map`', }, }, ], @@ -348,8 +409,8 @@ foo(1 as any, 'a' as any, 1 as any); column: 5, endColumn: 13, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, { @@ -358,8 +419,8 @@ foo(1 as any, 'a' as any, 1 as any); column: 15, endColumn: 25, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -376,8 +437,8 @@ foo('a', 1 as any, 'a' as any, 1 as any); column: 10, endColumn: 18, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, { @@ -386,8 +447,8 @@ foo('a', 1 as any, 'a' as any, 1 as any); column: 20, endColumn: 30, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -406,8 +467,8 @@ foo(t as any); column: 5, endColumn: 13, data: { - sender: 'any', - receiver: 'T', + sender: '`any`', + receiver: '`T`', }, }, ], @@ -430,8 +491,8 @@ foo\`\${arg}\${arg}\${arg}\`; column: 15, endColumn: 18, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, { @@ -440,8 +501,8 @@ foo\`\${arg}\${arg}\${arg}\`; column: 27, endColumn: 30, data: { - sender: 'any', - receiver: 'string', + sender: '`any`', + receiver: '`string`', }, }, ], @@ -459,8 +520,8 @@ foo\`\${arg}\`; column: 7, endColumn: 10, data: { - sender: 'any', - receiver: 'number', + sender: '`any`', + receiver: '`number`', }, }, ], @@ -479,8 +540,8 @@ foo\`\${arg}\`; column: 7, endColumn: 10, data: { - sender: 'any', - receiver: 'T', + sender: '`any`', + receiver: '`T`', }, }, ], From 00160e3d2ea7487ae4514187128b1d45d5c7e60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 3 Sep 2024 09:53:14 -0400 Subject: [PATCH 2/3] Update packages/eslint-plugin/src/rules/no-unsafe-argument.ts Co-authored-by: Brad Zacher --- packages/eslint-plugin/src/rules/no-unsafe-argument.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index 8e2db0547064..4617446bac38 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -175,8 +175,7 @@ export default createRule<[], MessageIds>({ function describeTypeForSpread(type: ts.Type): string { if ( checker.isArrayType(type) && - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - tsutils.isIntrinsicErrorType(type.typeArguments![0]) + tsutils.isIntrinsicErrorType(checker.getTypeArguments(type)[0]) ) { return 'error'; } From 0262c0c353ede879afd394afdcf1c58abfa9fa7d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 8 Sep 2024 19:57:09 -0400 Subject: [PATCH 3/3] Update docs snapshot --- packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx | 2 +- .../docs-eslint-output-snapshots/no-unsafe-argument.shot | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx index a86575008075..696bc4c551fd 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx +++ b/packages/eslint-plugin/docs/rules/no-unsafe-argument.mdx @@ -41,7 +41,7 @@ const tuple1 = ['a', anyTyped, 'b'] as const; foo(...tuple1); const tuple2 = [1] as const; -foo('a', ...tuple, anyTyped); +foo('a', ...tuple2, anyTyped); declare function bar(arg1: string, arg2: number, ...rest: string[]): void; const x = [1, 2] as [number, ...number[]]; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-argument.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-argument.shot index 5faa832727bc..62e5ba9126c3 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-argument.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-argument.shot @@ -14,16 +14,15 @@ foo(anyTyped, 1, 'a'); const anyArray: any[] = []; foo(...anyArray); - ~~~~~~~~~~~ Unsafe spread of an \`any\` array type. + ~~~~~~~~~~~ Unsafe spread of an \`any[]\` array type. const tuple1 = ['a', anyTyped, 'b'] as const; foo(...tuple1); ~~~~~~~~~ Unsafe spread of a tuple type. The argument is of type \`any\` and is assigned to a parameter of type \`number\`. const tuple2 = [1] as const; -foo('a', ...tuple, anyTyped); - ~~~~~~~~ Unsafe spread of an \`any\` type. - ~~~~~~~~ Unsafe argument of type \`any\` assigned to a parameter of type \`number\`. +foo('a', ...tuple2, anyTyped); + ~~~~~~~~ Unsafe argument of type \`any\` assigned to a parameter of type \`string\`. declare function bar(arg1: string, arg2: number, ...rest: string[]): void; const x = [1, 2] as [number, ...number[]];