Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): new ruleno-unsafe-type-assertion#10051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Changes fromall commits
8f38cec037978ca50625b15ce7f87c44606cc4ea8be31cf398a8965f5bbe0c9f0c429e08577a82ab1c878f29e89a5dbd5bff73c4d913529f88d15dec4f7ce7c34f7c1e93cca51d32280d4c523603ae3bc9980c9e8b0912f07ae8905d8c521033fd0bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| <Tabs> | ||
| <TabItem value="❌ Incorrect"> | ||
| ```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; | ||
| ``` | ||
| </TabItem> | ||
| <TabItem value="✅ Correct"> | ||
| ```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; | ||
| ``` | ||
| </TabItem> | ||
| </Tabs> | ||
| ## When Not To Use It | ||
kirkwaiblinger marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| 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) | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| }, | ||
| }; | ||
| }, | ||
| }); |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.