Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [no-unnecessary-condition] check type predicates based on assignability#11799

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

Draft
kirkwaiblinger wants to merge5 commits intotypescript-eslint:main
base:main
Choose a base branch
Loading
fromkirkwaiblinger:nuc-check-type-predicates-assignability
Draft
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,6 +188,12 @@ function assertIsString(value: unknown): asserts value is string {
}

assertIsString(s); // Unnecessary; s is always a string.

declare function assertIsStringOrNumber(
value: unknown,
): asserts value is string | number;

assertIsStringOrNumber(s); // Unnecessary; s is assignable to string | number.
```

Whether this option makes sense for your project may vary.
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,7 +173,7 @@ export default createRule<Options, MessageIds>({
*/
function isIncludedInstanceMethod(
node: NonNullable<Stack['member']>,
):node is NonNullable<Stack['member']> {
):boolean {
if (
node.static ||
(node.type === AST_NODE_TYPES.MethodDefinition &&
Expand Down
56 changes: 47 additions & 9 deletionspackages/eslint-plugin/src/rules/no-unnecessary-condition.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,11 @@ import {
isPossiblyTruthy,
isTypeAnyType,
isTypeFlagSet,
isTypeNeverType,
isTypeUnknownType,
nullThrows,
NullThrowsReasons,
toWidenedType,
} from '../util';
import {
findTruthinessAssertedArgument,
Expand DownExpand Up@@ -153,7 +155,7 @@ export type MessageId =
| 'noOverlapBooleanExpression'
| 'noStrictNullCheck'
| 'suggestRemoveOptionalChain'
| 'typeGuardAlreadyIsType';
| 'typeAssertionArgumentAlreadyAssignable';

export default createRule<Options, MessageId>({
name: 'no-unnecessary-condition',
Expand DownExpand Up@@ -186,8 +188,8 @@ export default createRule<Options, MessageId>({
noStrictNullCheck:
'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',
suggestRemoveOptionalChain: 'Remove unnecessary optional chain',
typeGuardAlreadyIsType:
'Unnecessary conditional, expression alreadyhasthe type being checked by the {{typeGuardOrAssertionFunction}}.',
typeAssertionArgumentAlreadyAssignable:
'Unnecessary conditional, expressionisalreadyassignable tothe type being checked by {{typeGuardOrAssertionFunction}}.',
},
schema: [
{
Expand DownExpand Up@@ -595,14 +597,50 @@ export default createRule<Options, MessageId>({
node,
);
if (typeGuardAssertedArgument != null) {
const typeOfArgument = getConstrainedTypeAtLocation(
services,
typeGuardAssertedArgument.argument,
);
if (typeOfArgument === typeGuardAssertedArgument.type) {
const shouldReport = (() => {
const argumentType = services.getTypeAtLocation(
typeGuardAssertedArgument.argument,
);

const assertedType = typeGuardAssertedArgument.type;
if (isTypeAnyType(argumentType) && isTypeAnyType(assertedType)) {
return true;
}

if (isTypeAnyType(argumentType)) {
return false;
}

if (
isTypeNeverType(argumentType) &&
isTypeNeverType(assertedType)
) {
return true;
}

if (isTypeNeverType(argumentType)) {
return false;
}

if (
checker.isTypeAssignableTo(
// Use the widened type to bypass excess property checking
toWidenedType(checker, argumentType),
assertedType,
)
) {
// ... unless the asserted type actually does narrow the argument,
// for example by granting it additional optional properties?
return true;
}

return false;
})();

if (shouldReport) {
context.report({
node: typeGuardAssertedArgument.argument,
messageId: 'typeGuardAlreadyIsType',
messageId: 'typeAssertionArgumentAlreadyAssignable',
data: {
typeGuardOrAssertionFunction: typeGuardAssertedArgument.asserts
? 'assertion function'
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -452,6 +452,7 @@ function collectTypeParameterUsageCounts(
const properties = type.getProperties();
visitSymbolsListOnce(properties, false);

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- TODO revisit this
if (isMappedType(type)) {
visitType(type.typeParameter, false);
if (properties.length === 0) {
Expand Down
18 changes: 5 additions & 13 deletionspackages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
import type { TSESTree } from '@typescript-eslint/utils';
import type * as ts from 'typescript';

import * as tsutils from 'ts-api-utils';
import * as ts from 'typescript';

import {
createRule,
getParserServices,
isObjectLiteralType,
isTypeAnyType,
isTypeUnknownType,
isUnsafeAssignment,
toWidenedType,
} from '../util';

export default createRule({
Expand DownExpand Up@@ -42,13 +44,6 @@ export default createRule({
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 {
Expand DownExpand Up@@ -110,11 +105,8 @@ export default createRule({
return;
}

// Use the widened type in case of an object literal so `isTypeAssignableTo()`
// won't fail on excess property check.
const expressionWidenedType = isObjectLiteralType(expressionType)
? checker.getWidenedType(expressionType)
: expressionType;
// Use the widened type to bypass excess property checking
const expressionWidenedType = toWidenedType(checker, expressionType);

const isAssertionSafe = checker.isTypeAssignableTo(
expressionWidenedType,
Expand Down
1 change: 1 addition & 0 deletionspackages/eslint-plugin/src/util/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ export * from './getValueOfLiteralType';
export * from './isHigherPrecedenceThanAwait';
export * from './skipChainExpression';
export * from './truthinessUtils';
export * from './isObjectType.js';

// this is done for convenience - saves migrating all of the old rules
export * from '@typescript-eslint/type-utils';
Expand Down
37 changes: 37 additions & 0 deletionspackages/eslint-plugin/src/util/isObjectType.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
import * as tsutils from 'ts-api-utils';
import * as ts from 'typescript';

/**
* Returns whether the type is a fresh object literal, which will be subject to
* excess property checking.
*
* For example, in the following snippet, the first function call has an object
* literal type, and therefore is disallowed by excess property checking,
* whereas the second function call is allowed, because the argument is not an
* object literal type:
*
* ```ts
* declare function f(x: { a: string });
*
* f({ a: 'foo', excess: 'property' }); // TS error
*
* const allowed = { a: 'foo', excess: 'property' };
*
* f(allowed); // allowed
* ```
*/
export function isObjectLiteralType(type: ts.Type): boolean {
return (
tsutils.isObjectType(type) &&
tsutils.isObjectFlagSet(type, ts.ObjectFlags.ObjectLiteral)
);
}

/**
* Maps object literal types into ordinary types, in order to be able to avoid
* spurious results from `checker.isTypeAssignableTo()` due to excess property
* checking.
*/
export function toWidenedType(checker: ts.TypeChecker, type: ts.Type): ts.Type {
return isObjectLiteralType(type) ? checker.getWidenedType(type) : type;
}
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp