diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index a14b3ced2fd5..7e956af57420 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -133,6 +133,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Enforces that type arguments will not be used if not required | | :wrench: | :thought_balloon: | | [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: | | [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | | | :thought_balloon: | +| [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | | | :thought_balloon: | | [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | :thought_balloon: | | [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :heavy_check_mark: | | | | [`@typescript-eslint/prefer-as-const`](./docs/rules/prefer-as-const.md) | Prefer usage of `as const` over literal type | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-return.md b/packages/eslint-plugin/docs/rules/no-unsafe-return.md new file mode 100644 index 000000000000..507abc3dfa28 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-return.md @@ -0,0 +1,75 @@ +# Disallows returning any from a function (`no-unsafe-return`) + +Despite your best intentions, the `any` type can sometimes leak into your codebase. +Returned `any` typed values not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. + +## Rule Details + +This rule disallows returning `any` or `any[]` from a function. +This rule also compares the return type to the function's declared/inferred return type to ensure you don't return an unsafe `any` in a generic position to a receiver that's expecting a specific type. For example, it will error if you return `Set` from a function declared as returning `Set`. + +Examples of **incorrect** code for this rule: + +```ts +function foo1() { + return 1 as any; +} +function foo2() { + return Object.create(null); +} +const foo3 = () => { + return 1 as any; +}; +const foo4 = () => Object.create(null); + +function foo5() { + return [] as any[]; +} +function foo6() { + return [] as Array; +} +function foo7() { + return [] as readonly any[]; +} +function foo8() { + return [] as Readonly; +} +const foo9 = () => { + return [] as any[]; +}; +const foo10 = () => [] as any[]; + +const foo11 = (): string[] => [1, 2, 3] as any[]; + +// generic position examples +function assignability1(): Set { + return new Set([1]); +} +type TAssign = () => Set; +const assignability2: TAssign = () => new Set([true]); +``` + +Examples of **correct** code for this rule: + +```ts +function foo1() { + return 1; +} +function foo2() { + return Object.create(null) as Record; +} + +const foo3 = () => []; +const foo4 = () => ['a']; + +function assignability1(): Set { + return new Set(['foo']); +} +type TAssign = () => Set; +const assignability2: TAssign = () => new Set(['foo']); +``` + +## Related to + +- [`no-explicit-any`](./no-explicit-any.md) +- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 47f4e509068a..f4a7bd435621 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -62,6 +62,7 @@ "@typescript-eslint/no-unnecessary-type-arguments": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unsafe-member-access": "error", + "@typescript-eslint/no-unsafe-return": "error", "no-unused-expressions": "off", "@typescript-eslint/no-unused-expressions": "error", "no-unused-vars": "off", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index f09033aa2d73..0befe184fbea 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -54,6 +54,7 @@ import noUnnecessaryQualifier from './no-unnecessary-qualifier'; import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments'; import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; import noUnsafeMemberAccess from './no-unsafe-member-access'; +import noUnsafeReturn from './no-unsafe-return'; import noUntypedPublicSignature from './no-untyped-public-signature'; import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; @@ -146,6 +147,7 @@ export default { 'no-unnecessary-type-arguments': noUnnecessaryTypeArguments, 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, 'no-unsafe-member-access': noUnsafeMemberAccess, + 'no-unsafe-return': noUnsafeReturn, 'no-untyped-public-signature': noUntypedPublicSignature, 'no-unused-expressions': noUnusedExpressions, 'no-unused-vars-experimental': noUnusedVarsExperimental, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 943beb459d7e..dd4395b290b8 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,12 +1,7 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; import { - isCallExpression, - isJsxExpression, - isNewExpression, isObjectType, isObjectFlagSet, - isParameterDeclaration, - isPropertyDeclaration, isStrictCompilerOptionEnabled, isTypeFlagSet, isVariableDeclaration, @@ -91,48 +86,6 @@ export default util.createRule({ return true; } - /** - * Returns the contextual type of a given node. - * Contextual type is the type of the target the node is going into. - * i.e. the type of a called function's parameter, or the defined type of a variable declaration - */ - function getContextualType( - checker: ts.TypeChecker, - node: ts.Expression, - ): ts.Type | undefined { - const parent = node.parent; - if (!parent) { - return; - } - - if (isCallExpression(parent) || isNewExpression(parent)) { - if (node === parent.expression) { - // is the callee, so has no contextual type - return; - } - } else if ( - isVariableDeclaration(parent) || - isPropertyDeclaration(parent) || - isParameterDeclaration(parent) - ) { - return parent.type - ? checker.getTypeFromTypeNode(parent.type) - : undefined; - } else if (isJsxExpression(parent)) { - return checker.getContextualType(parent); - } else if ( - ![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes( - parent.kind, - ) - ) { - // parent is not something we know we can get the contextual type of - return; - } - // TODO - support return statement checking - - return checker.getContextualType(node); - } - /** * Returns true if there's a chance the variable has been used before a value has been assigned to it */ @@ -196,7 +149,7 @@ export default util.createRule({ // we know it's a nullable type // so figure out if the variable is used in a place that accepts nullable types - const contextualType = getContextualType(checker, originalNode); + const contextualType = util.getContextualType(checker, originalNode); if (contextualType) { // in strict mode you can't assign null to undefined, so we have to make sure that // the two types share a nullable type diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts new file mode 100644 index 000000000000..fef04cd6365f --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -0,0 +1,135 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import { isExpression } from 'tsutils'; +import * as util from '../util'; + +export default util.createRule({ + name: 'no-unsafe-return', + meta: { + type: 'problem', + docs: { + description: 'Disallows returning any from a function', + category: 'Possible Errors', + recommended: false, + requiresTypeChecking: true, + }, + messages: { + unsafeReturn: 'Unsafe return of an {{type}} typed value', + unsafeReturnAssignment: + 'Unsafe return of type {{sender}} from function with return type {{receiver}}', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); + const checker = program.getTypeChecker(); + + function getParentFunctionNode( + node: TSESTree.Node, + ): + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | null { + let current = node.parent; + while (current) { + if ( + current.type === AST_NODE_TYPES.ArrowFunctionExpression || + current.type === AST_NODE_TYPES.FunctionDeclaration || + current.type === AST_NODE_TYPES.FunctionExpression + ) { + return current; + } + + current = current.parent; + } + + // this shouldn't happen in correct code, but someone may attempt to parse bad code + // the parser won't error, so we shouldn't throw here + /* istanbul ignore next */ return null; + } + + function checkReturn( + returnNode: TSESTree.Node, + reportingNode: TSESTree.Node = returnNode, + ): void { + const tsNode = esTreeNodeToTSNodeMap.get(returnNode); + const anyType = util.isAnyOrAnyArrayTypeDiscriminated(tsNode, checker); + if (anyType !== util.AnyType.Safe) { + return context.report({ + node: reportingNode, + messageId: 'unsafeReturn', + data: { + type: anyType === util.AnyType.Any ? 'any' : 'any[]', + }, + }); + } + + const functionNode = getParentFunctionNode(returnNode); + /* istanbul ignore if */ if (!functionNode) { + return; + } + + // function has an explicit return type, so ensure it's a safe return + const returnNodeType = checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(returnNode), + ); + const functionTSNode = esTreeNodeToTSNodeMap.get(functionNode); + + // function expressions will not have their return type modified based on receiver typing + // so we have to use the contextual typing in these cases, i.e. + // const foo1: () => Set = () => new Set(); + // the return type of the arrow function is Set even though the variable is typed as Set + let functionType = isExpression(functionTSNode) + ? util.getContextualType(checker, functionTSNode) + : checker.getTypeAtLocation(functionTSNode); + if (!functionType) { + functionType = checker.getTypeAtLocation(functionTSNode); + } + + for (const signature of functionType.getCallSignatures()) { + const functionReturnType = signature.getReturnType(); + if (returnNodeType === functionReturnType) { + // don't bother checking if they're the same + // either the function is explicitly declared to return the same type + // or there was no declaration, so the return type is implicit + return; + } + + const result = util.isUnsafeAssignment( + returnNodeType, + functionReturnType, + checker, + ); + if (!result) { + return; + } + + const { sender, receiver } = result; + return context.report({ + node: reportingNode, + messageId: 'unsafeReturnAssignment', + data: { + sender: checker.typeToString(sender), + receiver: checker.typeToString(receiver), + }, + }); + } + } + + return { + ReturnStatement(node): void { + const argument = node.argument; + if (!argument) { + return; + } + + checkReturn(argument, node); + }, + 'ArrowFunctionExpression > :not(BlockStatement).body': checkReturn, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index f59f3d7c53e5..924c68e151da 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,6 +1,12 @@ import { + isCallExpression, + isJsxExpression, + isNewExpression, + isParameterDeclaration, + isPropertyDeclaration, isTypeReference, isUnionOrIntersectionType, + isVariableDeclaration, unionTypeParts, } from 'tsutils'; import * as ts from 'typescript'; @@ -297,3 +303,126 @@ export function getEqualsKind(operator: string): EqualsKind | undefined { export function isTypeAnyType(type: ts.Type): boolean { return isTypeFlagSet(type, ts.TypeFlags.Any); } + +export const enum AnyType { + Any, + AnyArray, + Safe, +} +/** + * @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`, + * otherwise it returns `AnyType.Safe`. + */ +export function isAnyOrAnyArrayTypeDiscriminated( + node: ts.Node, + checker: ts.TypeChecker, +): AnyType { + const type = checker.getTypeAtLocation(node); + if (isTypeAnyType(type)) { + return AnyType.Any; + } + if ( + checker.isArrayType(type) && + isTypeAnyType(checker.getTypeArguments(type)[0]) + ) { + return AnyType.AnyArray; + } + return AnyType.Safe; +} + +/** + * Does a simple check to see if there is an any being assigned to a non-any type. + * + * This also checks generic positions to ensure there's no unsafe sub-assignments. + * Note: in the case of generic positions, it makes the assumption that the two types are the same. + * + * @example See tests for examples + * + * @returns false if it's safe, or an object with the two types if it's unsafe + */ +export function isUnsafeAssignment( + type: ts.Type, + receiver: ts.Type, + checker: ts.TypeChecker, +): false | { sender: ts.Type; receiver: ts.Type } { + if (isTypeReference(type) && isTypeReference(receiver)) { + // TODO - figure out how to handle cases like this, + // where the types are assignable, but not the same type + /* + function foo(): ReadonlySet { return new Set(); } + + // and + + type Test = { prop: T } + type Test2 = { prop: string } + declare const a: Test; + const b: Test2 = a; + */ + + if (type.target !== receiver.target) { + // if the type references are different, assume safe, as we won't know how to compare the two types + // the generic positions might not be equivalent for both types + return false; + } + + const typeArguments = type.typeArguments ?? []; + const receiverTypeArguments = receiver.typeArguments ?? []; + + for (let i = 0; i < typeArguments.length; i += 1) { + const arg = typeArguments[i]; + const receiverArg = receiverTypeArguments[i]; + + const unsafe = isUnsafeAssignment(arg, receiverArg, checker); + if (unsafe) { + return { sender: type, receiver }; + } + } + + return false; + } + + if (isTypeAnyType(type) && !isTypeAnyType(receiver)) { + return { sender: type, receiver }; + } + return false; +} + +/** + * Returns the contextual type of a given node. + * Contextual type is the type of the target the node is going into. + * i.e. the type of a called function's parameter, or the defined type of a variable declaration + */ +export function getContextualType( + checker: ts.TypeChecker, + node: ts.Expression, +): ts.Type | undefined { + const parent = node.parent; + if (!parent) { + return; + } + + if (isCallExpression(parent) || isNewExpression(parent)) { + if (node === parent.expression) { + // is the callee, so has no contextual type + return; + } + } else if ( + isVariableDeclaration(parent) || + isPropertyDeclaration(parent) || + isParameterDeclaration(parent) + ) { + return parent.type ? checker.getTypeFromTypeNode(parent.type) : undefined; + } else if (isJsxExpression(parent)) { + return checker.getContextualType(parent); + } else if ( + ![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes( + parent.kind, + ) + ) { + // parent is not something we know we can get the contextual type of + return; + } + // TODO - support return statement checking + + return checker.getContextualType(node); +} diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts new file mode 100644 index 000000000000..0fdcaddf36b6 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts @@ -0,0 +1,235 @@ +import rule from '../../src/rules/no-unsafe-return'; +import { + RuleTester, + batchedSingleLineTests, + getFixturesRootDir, +} from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: getFixturesRootDir(), + }, +}); + +ruleTester.run('no-unsafe-return', rule, { + valid: [ + 'function foo() { return; }', + 'function foo() { return 1; }', + 'function foo() { return ""; }', + 'function foo() { return true; }', + // this actually types as `never[]` + 'function foo() { return []; }', + // explicit any generic return type is allowed, if you want to be unsafe like that + 'function foo(): Set { return new Set(); }', + // TODO - this should error, but it's hard to detect, as the type references are different + 'function foo(): ReadonlySet { return new Set(); }', + 'function foo(): Set { return new Set([1]); }', + ` + type Foo = { prop: T }; + function foo(): Foo { return ({ prop: 1 } as Foo)} + `, + ` + type Foo = { prop: any }; + function foo(): Foo { return { prop: '' } as Foo; } + `, + ], + invalid: [ + ...batchedSingleLineTests({ + code: ` +function foo() { return (1 as any); } +function foo() { return Object.create(null); } +const foo = () => { return (1 as any) }; +const foo = () => Object.create(null); + `, + errors: [ + { + messageId: 'unsafeReturn', + data: { + type: 'any', + }, + line: 2, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any', + }, + line: 3, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any', + }, + line: 4, + column: 21, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any', + }, + line: 5, + column: 19, + }, + ], + }), + ...batchedSingleLineTests({ + code: ` +function foo() { return ([] as any[]); } +function foo() { return ([] as Array); } +function foo() { return ([] as readonly any[]); } +function foo() { return ([] as Readonly); } +const foo = () => { return ([] as any[]) }; +const foo = () => ([] as any[]); + `, + errors: [ + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 2, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 3, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 4, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 5, + column: 18, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 6, + column: 21, + }, + { + messageId: 'unsafeReturn', + data: { + type: 'any[]', + }, + line: 7, + column: 20, + }, + ], + }), + ...batchedSingleLineTests({ + code: ` +function foo(): Set { return new Set(); } +function foo(): Map { return new Map(); } +function foo(): Set { return new Set(); } +function foo(): Set>> { return new Set>>(); } + `, + errors: [ + { + messageId: 'unsafeReturnAssignment', + data: { + sender: 'Set', + receiver: 'Set', + }, + line: 2, + }, + { + messageId: 'unsafeReturnAssignment', + data: { + sender: 'Map', + receiver: 'Map', + }, + line: 3, + }, + { + messageId: 'unsafeReturnAssignment', + data: { + sender: 'Set', + receiver: 'Set', + }, + line: 4, + }, + { + messageId: 'unsafeReturnAssignment', + data: { + sender: 'Set>>', + receiver: 'Set>>', + }, + line: 5, + }, + ], + }), + { + code: ` + type Fn = () => Set; + const foo1: Fn = () => new Set(); + const foo2: Fn = function test() { return new Set() }; + `, + errors: [ + { + messageId: 'unsafeReturnAssignment', + line: 3, + data: { + sender: 'Set', + receiver: 'Set', + }, + }, + { + messageId: 'unsafeReturnAssignment', + line: 4, + data: { + sender: 'Set', + receiver: 'Set', + }, + }, + ], + }, + { + code: ` + type Fn = () => Set; + function receiver(arg: Fn) {} + receiver(() => new Set()); + receiver(function test() { return new Set() }); + `, + errors: [ + { + messageId: 'unsafeReturnAssignment', + line: 4, + data: { + sender: 'Set', + receiver: 'Set', + }, + }, + { + messageId: 'unsafeReturnAssignment', + line: 5, + data: { + sender: 'Set', + receiver: 'Set', + }, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts b/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts new file mode 100644 index 000000000000..bd644832b30f --- /dev/null +++ b/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts @@ -0,0 +1,170 @@ +import * as ts from 'typescript'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { parseForESLint } from '@typescript-eslint/parser'; +import path from 'path'; +import { getFixturesRootDir } from '../RuleTester'; +import { isUnsafeAssignment } from '../../src/util/types'; + +describe('isUnsafeAssignment', () => { + const rootDir = getFixturesRootDir(); + + function getTypes( + code: string, + ): { sender: ts.Type; receiver: ts.Type; checker: ts.TypeChecker } { + const { ast, services } = parseForESLint(code, { + project: './tsconfig.json', + filePath: path.join(rootDir, 'file.ts'), + tsconfigRootDir: rootDir, + }); + const checker = services.program!.getTypeChecker(); + const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap!; + + const declaration = ast.body[0] as TSESTree.VariableDeclaration; + const declarator = declaration.declarations[0]; + return { + receiver: checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(declarator.id), + ), + sender: checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(declarator.init!), + ), + checker, + }; + } + + describe('unsafe', () => { + function expectTypesAre( + result: ReturnType, + checker: ts.TypeChecker, + senderStr: string, + receiverStr: string, + ): void { + expect(result).toBeTruthy(); + const { sender, receiver } = result as Exclude; + + expect(checker.typeToString(sender)).toBe(senderStr); + expect(checker.typeToString(receiver)).toBe(receiverStr); + } + + it('any to a non-any', () => { + const { sender, receiver, checker } = getTypes( + 'const test: string = (1 as any);', + ); + + expectTypesAre( + isUnsafeAssignment(sender, receiver, checker), + checker, + 'any', + 'string', + ); + }); + + it('any in a generic position to a non-any', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set = new Set();', + ); + + expectTypesAre( + isUnsafeAssignment(sender, receiver, checker), + checker, + 'Set', + 'Set', + ); + }); + + it('any in a generic position to a non-any (multiple generics)', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Map = new Map();', + ); + + expectTypesAre( + isUnsafeAssignment(sender, receiver, checker), + checker, + 'Map', + 'Map', + ); + }); + + it('any[] in a generic position to a non-any[]', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set = new Set();', + ); + + expectTypesAre( + isUnsafeAssignment(sender, receiver, checker), + checker, + 'Set', + 'Set', + ); + }); + + it('any in a generic position to a non-any (nested)', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set>> = new Set>>();', + ); + + expectTypesAre( + isUnsafeAssignment(sender, receiver, checker), + checker, + 'Set>>', + 'Set>>', + ); + }); + }); + + describe('safe', () => { + it('non-any to a non-any', () => { + const { sender, receiver, checker } = getTypes( + 'const test: string = "";', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any to a any', () => { + const { sender, receiver, checker } = getTypes('const test: any = "";'); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any in a generic position to a non-any', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set = new Set();', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any in a generic position to a non-any (multiple generics)', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Map = new Map();', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any[] in a generic position to a non-any[]', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set = new Set();', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any in a generic position to a non-any (nested)', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set>> = new Set>>();', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + + it('non-any in a generic position to a any (nested)', () => { + const { sender, receiver, checker } = getTypes( + 'const test: Set>> = new Set>>();', + ); + + expect(isUnsafeAssignment(sender, receiver, checker)).toBeFalsy(); + }); + }); +}); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy