diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-type-assertion.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-type-assertion.mdx
new file mode 100644
index 000000000000..53c1e51ee970
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-unsafe-type-assertion.mdx
@@ -0,0 +1,63 @@
+---
+description: 'Disallow type assertions that narrow a type.'
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+> 🛑 This file is source code, not the primary documentation location! 🛑
+>
+> See **https://typescript-eslint.io/rules/no-unsafe-type-assertion** for documentation.
+
+Type assertions are a way to tell TypeScript what the type of a value is. This can be useful but also unsafe if you use type assertions to narrow down a type.
+
+This rule forbids using type assertions to narrow a type, as this bypasses TypeScript's type-checking. Type assertions that broaden a type are safe because TypeScript essentially knows _less_ about a type.
+
+Instead of using type assertions to narrow a type, it's better to rely on type guards, which help avoid potential runtime errors caused by unsafe type assertions.
+
+## Examples
+
+
+
+
+```ts
+function f() {
+ return Math.random() < 0.5 ? 42 : 'oops';
+}
+
+const z = f() as number;
+
+const items = [1, '2', 3, '4'];
+
+const number = items[0] as number;
+```
+
+
+
+
+```ts
+function f() {
+ return Math.random() < 0.5 ? 42 : 'oops';
+}
+
+const z = f() as number | string | boolean;
+
+const items = [1, '2', 3, '4'];
+
+const number = items[0] as number | string | undefined;
+```
+
+
+
+
+## When Not To Use It
+
+If your codebase has many unsafe type assertions, then it may be difficult to enable this rule.
+It may be easier to skip the `no-unsafe-*` rules pending increasing type safety in unsafe areas of your project.
+You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule.
+
+If your project frequently stubs objects in test files, the rule may trigger a lot of reports. Consider disabling the rule for such files to reduce frequent warnings.
+
+## Further Reading
+
+- More on TypeScript's [type assertions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions)
diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts
index 107f369260ec..0bb7560c49a6 100644
--- a/packages/eslint-plugin/src/configs/all.ts
+++ b/packages/eslint-plugin/src/configs/all.ts
@@ -106,6 +106,7 @@ export = {
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
+ '@typescript-eslint/no-unsafe-type-assertion': 'error',
'@typescript-eslint/no-unsafe-unary-minus': 'error',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 'error',
diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts
index 7cf867b382f2..4e25bccd8405 100644
--- a/packages/eslint-plugin/src/configs/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts
@@ -40,6 +40,7 @@ export = {
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
+ '@typescript-eslint/no-unsafe-type-assertion': 'off',
'@typescript-eslint/no-unsafe-unary-minus': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/only-throw-error': 'off',
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 72c62f3e0122..843e3d7f9a32 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -83,6 +83,7 @@ import noUnsafeEnumComparison from './no-unsafe-enum-comparison';
import noUnsafeFunctionType from './no-unsafe-function-type';
import noUnsafeMemberAccess from './no-unsafe-member-access';
import noUnsafeReturn from './no-unsafe-return';
+import noUnsafeTypeAssertion from './no-unsafe-type-assertion';
import noUnsafeUnaryMinus from './no-unsafe-unary-minus';
import noUnusedExpressions from './no-unused-expressions';
import noUnusedVars from './no-unused-vars';
@@ -213,6 +214,7 @@ const rules = {
'no-unsafe-function-type': noUnsafeFunctionType,
'no-unsafe-member-access': noUnsafeMemberAccess,
'no-unsafe-return': noUnsafeReturn,
+ 'no-unsafe-type-assertion': noUnsafeTypeAssertion,
'no-unsafe-unary-minus': noUnsafeUnaryMinus,
'no-unused-expressions': noUnusedExpressions,
'no-unused-vars': noUnusedVars,
diff --git a/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts
new file mode 100644
index 000000000000..06c3dd44615a
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts
@@ -0,0 +1,146 @@
+import type { TSESTree } from '@typescript-eslint/utils';
+
+import * as tsutils from 'ts-api-utils';
+import * as ts from 'typescript';
+
+import {
+ createRule,
+ getConstrainedTypeAtLocation,
+ getParserServices,
+ isTypeAnyType,
+ isTypeUnknownType,
+ isUnsafeAssignment,
+} from '../util';
+
+export default createRule({
+ name: 'no-unsafe-type-assertion',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'Disallow type assertions that narrow a type',
+ requiresTypeChecking: true,
+ },
+ messages: {
+ unsafeOfAnyTypeAssertion:
+ 'Unsafe cast from {{type}} detected: consider using type guards or a safer cast.',
+ unsafeToAnyTypeAssertion:
+ 'Unsafe cast to {{type}} detected: consider using a more specific type to ensure safety.',
+ unsafeTypeAssertion:
+ "Unsafe type assertion: type '{{type}}' is more narrow than the original type.",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+ create(context) {
+ const services = getParserServices(context);
+ const checker = services.program.getTypeChecker();
+
+ function getAnyTypeName(type: ts.Type): string {
+ return tsutils.isIntrinsicErrorType(type) ? 'error typed' : '`any`';
+ }
+
+ function isObjectLiteralType(type: ts.Type): boolean {
+ return (
+ tsutils.isObjectType(type) &&
+ tsutils.isObjectFlagSet(type, ts.ObjectFlags.ObjectLiteral)
+ );
+ }
+
+ function checkExpression(
+ node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
+ ): void {
+ const expressionType = getConstrainedTypeAtLocation(
+ services,
+ node.expression,
+ );
+ const assertedType = getConstrainedTypeAtLocation(
+ services,
+ node.typeAnnotation,
+ );
+
+ if (expressionType === assertedType) {
+ return;
+ }
+
+ // handle cases when casting unknown ==> any.
+ if (isTypeAnyType(assertedType) && isTypeUnknownType(expressionType)) {
+ context.report({
+ node,
+ messageId: 'unsafeToAnyTypeAssertion',
+ data: {
+ type: '`any`',
+ },
+ });
+
+ return;
+ }
+
+ const unsafeExpressionAny = isUnsafeAssignment(
+ expressionType,
+ assertedType,
+ checker,
+ node.expression,
+ );
+
+ if (unsafeExpressionAny) {
+ context.report({
+ node,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ data: {
+ type: getAnyTypeName(unsafeExpressionAny.sender),
+ },
+ });
+
+ return;
+ }
+
+ const unsafeAssertedAny = isUnsafeAssignment(
+ assertedType,
+ expressionType,
+ checker,
+ node.typeAnnotation,
+ );
+
+ if (unsafeAssertedAny) {
+ context.report({
+ node,
+ messageId: 'unsafeToAnyTypeAssertion',
+ data: {
+ type: getAnyTypeName(unsafeAssertedAny.sender),
+ },
+ });
+
+ return;
+ }
+
+ // Use the widened type in case of an object literal so `isTypeAssignableTo()`
+ // won't fail on excess property check.
+ const nodeWidenedType = isObjectLiteralType(expressionType)
+ ? checker.getWidenedType(expressionType)
+ : expressionType;
+
+ const isAssertionSafe = checker.isTypeAssignableTo(
+ nodeWidenedType,
+ assertedType,
+ );
+
+ if (!isAssertionSafe) {
+ context.report({
+ node,
+ messageId: 'unsafeTypeAssertion',
+ data: {
+ type: checker.typeToString(assertedType),
+ },
+ });
+ }
+ }
+
+ return {
+ 'TSAsExpression, TSTypeAssertion'(
+ node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
+ ): void {
+ checkExpression(node);
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-type-assertion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-type-assertion.shot
new file mode 100644
index 000000000000..bcc2b982cc4d
--- /dev/null
+++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-type-assertion.shot
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Validating rule docs no-unsafe-type-assertion.mdx code examples ESLint output 1`] = `
+"Incorrect
+
+function f() {
+ return Math.random() < 0.5 ? 42 : 'oops';
+}
+
+const z = f() as number;
+ ~~~~~~~~~~~~~ Unsafe type assertion: type 'number' is more narrow than the original type.
+
+const items = [1, '2', 3, '4'];
+
+const number = items[0] as number;
+ ~~~~~~~~~~~~~~~~~~ Unsafe type assertion: type 'number' is more narrow than the original type.
+"
+`;
+
+exports[`Validating rule docs no-unsafe-type-assertion.mdx code examples ESLint output 2`] = `
+"Correct
+
+function f() {
+ return Math.random() < 0.5 ? 42 : 'oops';
+}
+
+const z = f() as number | string | boolean;
+
+const items = [1, '2', 3, '4'];
+
+const number = items[0] as number | string | undefined;
+"
+`;
diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-type-assertion.test.ts
new file mode 100644
index 000000000000..aa73bc2dae4d
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-unsafe-type-assertion.test.ts
@@ -0,0 +1,1043 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+
+import rule from '../../src/rules/no-unsafe-type-assertion';
+import { getFixturesRootDir } from '../RuleTester';
+
+const rootPath = getFixturesRootDir();
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: rootPath,
+ },
+ },
+});
+
+describe('basic assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const a: string;
+a as string | number;
+ `,
+ `
+declare const a: string;
+a;
+ `,
+ `
+declare const a: string;
+a as string | number as string | number | boolean;
+ `,
+ `
+declare const a: string;
+a as string;
+ `,
+ `
+declare const a: { hello: 'world' };
+a as { hello: string };
+ `,
+ `
+'hello' as const;
+ `,
+ `
+function foo(a: T) {
+ return a as T | number;
+}
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const a: string | number;
+a as string;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'string',
+ },
+ endColumn: 12,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: string | number;
+a satisfies string as string;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'string',
+ },
+ endColumn: 29,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: string | number;
+a;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'string',
+ },
+ endColumn: 10,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: string | undefined;
+a as string | boolean;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'string | boolean',
+ },
+ endColumn: 22,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ // multiple failures
+ {
+ code: `
+declare const a: string;
+a as 'foo' as 'bar';
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '"bar"',
+ },
+ endColumn: 20,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ {
+ column: 1,
+ data: {
+ type: '"foo"',
+ },
+ endColumn: 11,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ // type constraint
+ {
+ code: `
+function foo(a: T) {
+ return a as true;
+}
+ `,
+ errors: [
+ {
+ column: 10,
+ data: {
+ type: 'true',
+ },
+ endColumn: 19,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ // long/complex asserted type
+ {
+ code: `
+declare const a: string;
+a as Omit>, 'foo'>;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'Omit>, "foo">',
+ },
+ endColumn: 69,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const foo: readonly number[];
+const bar = foo as number[];
+ `,
+ errors: [
+ {
+ column: 13,
+ data: {
+ type: 'number[]',
+ },
+ endColumn: 28,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('any assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const _any_: any;
+_any_ as any;
+ `,
+ `
+declare const _any_: any;
+_any_ as unknown;
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const _any_: any;
+_any_ as string;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 16,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _unknown_: unknown;
+_unknown_ as any;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 17,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _any_: any;
+_any_ as Function;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 18,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _any_: any;
+_any_ as never;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 15,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+'foo' as any;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 13,
+ endLine: 2,
+ line: 2,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ // an error type `any`
+ {
+ code: `
+const bar = foo as number;
+ `,
+ errors: [
+ {
+ column: 13,
+ data: {
+ type: 'error typed',
+ },
+ endColumn: 26,
+ endLine: 2,
+ line: 2,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+const bar = 'foo' as errorType;
+ `,
+ errors: [
+ {
+ column: 13,
+ data: {
+ type: 'error typed',
+ },
+ endColumn: 31,
+ endLine: 2,
+ line: 2,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('never assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const _never_: never;
+_never_ as never;
+ `,
+ `
+declare const _never_: never;
+_never_ as unknown;
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const _never_: never;
+_never_ as any;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 15,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _string_: string;
+_string_ as never;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 18,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('function assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const _function_: Function;
+_function_ as Function;
+ `,
+ `
+declare const _function_: Function;
+_function_ as unknown;
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const _function_: Function;
+_function_ as () => void;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 25,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _function_: Function;
+_function_ as any;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 18,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const _function_: Function;
+_function_ as never;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 20,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('object assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+// additional properties should be allowed
+export const foo = { bar: 1, bazz: 1 } as {
+ bar: number;
+};
+ `,
+ `
+declare const a: { hello: string } & { world: string };
+a as { hello: string };
+ `,
+ `
+declare const a: { hello: any };
+a as { hello: unknown };
+ `,
+ `
+declare const a: { hello: string };
+a as { hello?: string };
+ `,
+ `
+declare const a: { hello: string };
+a satisfies Record as { hello?: string };
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+var foo = {} as {
+ bar: number;
+ bas: string;
+};
+ `,
+ errors: [
+ {
+ column: 11,
+ data: {
+ type: '{ bar: number; bas: string; }',
+ },
+ endColumn: 2,
+ endLine: 5,
+ line: 2,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: { hello: string };
+a satisfies Record as { hello: string; world: string };
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '{ hello: string; world: string; }',
+ },
+ endColumn: 71,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: { hello?: string };
+a as { hello: string };
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 23,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('array assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const a: string[];
+a as (string | number)[];
+ `,
+ `
+declare const a: number[];
+a as unknown[];
+ `,
+ `
+declare const a: { hello: 'world'; foo: 'bar' }[];
+a as { hello: 'world' }[];
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const a: (string | number)[];
+a as string[];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'string[]',
+ },
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: any[];
+a as number[];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: number[];
+a as any[];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 11,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: unknown[];
+a as number[];
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: number[];
+a as never[];
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 13,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('tuple assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const a: [string];
+a as [string | number];
+ `,
+ `
+declare const a: [string, number];
+a as [string, string | number];
+ `,
+ `
+declare const a: [string];
+a as [unknown];
+ `,
+ `
+declare const a: [{ hello: 'world'; foo: 'bar' }];
+a as [{ hello: 'world' }];
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const a: [string | number];
+a as [string];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '[string]',
+ },
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [string, number];
+a as [string, string];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '[string, string]',
+ },
+ endColumn: 22,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [string];
+a as [string, number];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '[string, number]',
+ },
+ endColumn: 22,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [string, number];
+a as [string];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '[string]',
+ },
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [any];
+a as [number];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [number, any];
+a as [number, number];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 22,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [number];
+a as [any];
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 11,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [unknown];
+a as [number];
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 14,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [number];
+a as [never];
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 13,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: [Promise];
+a as [Promise];
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 23,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('promise assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+declare const a: Promise;
+a as Promise;
+ `,
+ `
+declare const a: Promise;
+a as Promise;
+ `,
+ `
+declare const a: Promise<{ hello: 'world'; foo: 'bar' }>;
+a as Promise<{ hello: 'world' }>;
+ `,
+ `
+declare const a: Promise;
+a as Promise | string;
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: 'Promise',
+ },
+ endColumn: 21,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 21,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeOfAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 18,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ data: {
+ type: '`any`',
+ },
+ endColumn: 20,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeToAnyTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 21,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ {
+ code: `
+declare const a: Promise;
+a as Promise;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 20,
+ endLine: 3,
+ line: 3,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
+
+describe('class assertions', () => {
+ ruleTester.run('no-unsafe-type-assertion', rule, {
+ valid: [
+ `
+class Foo {}
+declare const a: Foo;
+a as Foo | number;
+ `,
+ `
+class Foo {}
+class Bar {}
+declare const a: Foo;
+a as Bar;
+ `,
+ `
+class Foo {
+ hello() {}
+}
+class Bar {}
+declare const a: Foo;
+a as Bar;
+ `,
+ `
+class Foo {
+ hello() {}
+}
+class Bar extends Foo {}
+declare const a: Bar;
+a as Foo;
+ `,
+ `
+class Foo {
+ hello() {}
+}
+class Bar extends Foo {}
+declare const a: Foo;
+a as Bar;
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+class Foo {
+ hello() {}
+}
+class Bar extends Foo {
+ world() {}
+}
+declare const a: Foo;
+a as Bar;
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 9,
+ endLine: 9,
+ line: 9,
+ messageId: 'unsafeTypeAssertion',
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-type-assertion.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-type-assertion.shot
new file mode 100644
index 000000000000..92a34949c0cb
--- /dev/null
+++ b/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-type-assertion.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-unsafe-type-assertion 1`] = `
+"
+# SCHEMA:
+
+[]
+
+
+# TYPES:
+
+/** No options declared */
+type Options = [];"
+`;
diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts
index 1c177f1943bf..4cfc2df7f640 100644
--- a/packages/typescript-eslint/src/configs/all.ts
+++ b/packages/typescript-eslint/src/configs/all.ts
@@ -120,6 +120,7 @@ export default (
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
+ '@typescript-eslint/no-unsafe-type-assertion': 'error',
'@typescript-eslint/no-unsafe-unary-minus': 'error',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 'error',
diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts
index b4c2afd20ec8..f1185e195ac5 100644
--- a/packages/typescript-eslint/src/configs/disable-type-checked.ts
+++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts
@@ -47,6 +47,7 @@ export default (
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
+ '@typescript-eslint/no-unsafe-type-assertion': 'off',
'@typescript-eslint/no-unsafe-unary-minus': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/only-throw-error': 'off',