🌐 AI搜索 & 代理 主页
Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff 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
2 changes: 1 addition & 1 deletion packages/eslint-plugin/src/rules/class-methods-use-this.ts
Original file line number Diff line number Diff 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 deletions packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import {
isPossiblyTruthy,
isTypeAnyType,
isTypeFlagSet,
isTypeNeverType,
isTypeUnknownType,
nullThrows,
NullThrowsReasons,
toWidenedType,
} from '../util';
import {
findTruthinessAssertedArgument,
Expand Down Expand Up @@ -153,7 +155,7 @@ export type MessageId =
| 'noOverlapBooleanExpression'
| 'noStrictNullCheck'
| 'suggestRemoveOptionalChain'
| 'typeGuardAlreadyIsType';
| 'typeAssertionArgumentAlreadyAssignable';

export default createRule<Options, MessageId>({
name: 'no-unnecessary-condition',
Expand Down Expand 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 already has the type being checked by the {{typeGuardOrAssertionFunction}}.',
typeAssertionArgumentAlreadyAssignable:
'Unnecessary conditional, expression is already assignable to the type being checked by {{typeGuardOrAssertionFunction}}.',
},
schema: [
{
Expand Down Expand 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
Original file line number Diff line number Diff 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 deletions packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts
Original file line number Diff line number Diff 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 Down Expand 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 Down Expand 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 deletions packages/eslint-plugin/src/util/index.ts
Original file line number Diff line number Diff 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 deletions packages/eslint-plugin/src/util/isObjectType.ts
Original file line number Diff line number Diff 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;
}

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

Loading
Loading