🌐 AI搜索 & 代理 主页
Skip to content
Open
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
39 changes: 39 additions & 0 deletions packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I replace this entire diff with

-          if (typeOfArgument === typeGuardAssertedArgument.type) {
+          if (
+            checker.isTypeAssignableTo(
+              typeOfArgument,
+              typeGuardAssertedArgument.type,
+            )
+          ) {

(which is what was intended in #11716 (comment)), the only test case that doesn't pass (apart from spurious reasons) is

function processArray(arr: readonly string[]) {
  if (Array.isArray(arr)) {
    return arr.length;
  }
}

So, I'm still thinking that changing to the assignability API makes sense, at least as a first step. In other words, let's take on the assignability API change before worrying about the edge case of readonly arrays with Array.isArray() (which, FWIW, I think is basically a corollary of microsoft/TypeScript#17002). What do you think?

Also cc @ronami

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Changed to use checker.isTypeAssignableTo() for Array.isArray

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@azat-io almost! I really mean the entire diff, though, not just the Array.isArray case. The thought is to change all unnecessary type predicate detection to be based on assignability.

Note that this change would require accompanying report message changes and additional testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Changed to use isTypeAssignableTo() for all type predicates. Removed isArrayIsArrayCall() function and updated the tests accordingly.

Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ function toStaticValue(
return undefined;
}

function typeContainsAnyOrUnknown(type: ts.Type): boolean {
return tsutils
.unionConstituents(type)
.some(part => isTypeAnyType(part) || isTypeUnknownType(part));
}

function isArrayIsArrayCall(node: TSESTree.CallExpression): boolean {
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
return false;
}

const memberExpr = node.callee;
if (memberExpr.computed || memberExpr.optional) {
return false;
}

return (
memberExpr.object.type === AST_NODE_TYPES.Identifier &&
memberExpr.object.name === 'Array' &&
memberExpr.property.type === AST_NODE_TYPES.Identifier &&
memberExpr.property.name === 'isArray'
);
}

const BOOL_OPERATORS = new Set([
'<',
'>',
Expand Down Expand Up @@ -609,6 +633,21 @@ export default createRule<Options, MessageId>({
: 'type guard',
},
});
} else if (
isArrayIsArrayCall(node) &&
!typeContainsAnyOrUnknown(typeOfArgument) &&
checker.isTypeAssignableTo(
typeOfArgument,
typeGuardAssertedArgument.type,
)
) {
context.report({
node: typeGuardAssertedArgument.argument,
messageId: 'typeGuardAlreadyIsType',
data: {
typeGuardOrAssertionFunction: 'type guard',
},
});
}
}
}
Expand Down
123 changes: 123 additions & 0 deletions packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,71 @@ isString('falafel');
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: number[] | null;
if (Array.isArray(items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: number[] | string;
if (Array.isArray(items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: unknown;
if (Array.isArray(items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: any;
if (Array.isArray(items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const MaybeArray: typeof Array | undefined;
declare const items: number[];
if (MaybeArray?.isArray(items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: number[];
if (Array['isArray'](items)) {
console.log(items.length);
}
`,
options: [{ checkTypePredicates: true }],
},
{
code: `
function process<T>(value: T) {
if (Array.isArray(value)) {
console.log(value.length);
}
}
`,
options: [{ checkTypePredicates: true }],
},
`
type A = { [name in Lowercase<string>]?: A };
declare const a: A;
Expand Down Expand Up @@ -3518,6 +3583,64 @@ isString('fa' + 'lafel');
],
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: number[];
if (Array.isArray(items)) {
console.log(items.length);
}
`,
errors: [
{
line: 3,
messageId: 'typeGuardAlreadyIsType',
},
],
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: string[];
Array.isArray(items);
`,
errors: [
{
line: 3,
messageId: 'typeGuardAlreadyIsType',
},
],
options: [{ checkTypePredicates: true }],
},
{
code: `
const tuple: [string, number] = ['a', 1];
if (Array.isArray(tuple)) {
console.log(tuple[0]);
}
`,
errors: [
{
line: 3,
messageId: 'typeGuardAlreadyIsType',
},
],
options: [{ checkTypePredicates: true }],
},
{
code: `
declare const items: string[] | number[];
if (Array.isArray(items)) {
console.log(items.length);
}
`,
errors: [
{
line: 3,
messageId: 'typeGuardAlreadyIsType',
},
],
options: [{ checkTypePredicates: true }],
},

// "branded" types
unnecessaryConditionTest('"" & {}', 'alwaysFalsy'),
Expand Down
2 changes: 1 addition & 1 deletion packages/rule-tester/src/utils/flat-config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ const processorSchema: ObjectPropertySchema<Processor.LooseProcessorModule> = {
},
};

type ConfigRules = Record<string, SharedConfig.RuleLevelAndOptions>;
type ConfigRules = Record<string, SharedConfig.RuleEntry>;

const rulesSchema = {
merge(first: ConfigRules = {}, second: ConfigRules = {}): ConfigRules {
Expand Down
Loading