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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
8f38cec
037978c
a50625b
15ce7f8
7c44606
cc4ea8b
e31cf39
8a8965f
5bbe0c9
f0c429e
08577a8
2ab1c87
8f29e89
a5dbd5b
ff73c4d
913529f
88d15de
c4f7ce7
c34f7c1
e93cca5
1d32280
d4c5236
03ae3bc
9980c9e
8b0912f
07ae890
5d8c521
033fd0b
File 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.