diff --git a/src/rules/__tests__/prefer-to-be-null.test.ts b/src/rules/__tests__/prefer-to-be-null.test.ts index 4798181b1..212f9e50c 100644 --- a/src/rules/__tests__/prefer-to-be-null.test.ts +++ b/src/rules/__tests__/prefer-to-be-null.test.ts @@ -43,3 +43,23 @@ ruleTester.run('prefer-to-be-null', rule, { }, ], }); + +new TSESLint.RuleTester({ + parser: '@typescript-eslint/parser', +}).run('prefer-to-be-null: typescript edition', rule, { + valid: [ + "(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()", + ], + invalid: [ + { + code: 'expect(null).toBe(null as unknown as string as unknown as any);', + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + output: 'expect(null).toBeNull();', + }, + { + code: 'expect("a string").not.toEqual(null as number);', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + output: 'expect("a string").not.toBeNull();', + }, + ], +}); diff --git a/src/rules/__tests__/prefer-to-be-undefined.test.ts b/src/rules/__tests__/prefer-to-be-undefined.test.ts index 5e1134b56..c07a30cd6 100644 --- a/src/rules/__tests__/prefer-to-be-undefined.test.ts +++ b/src/rules/__tests__/prefer-to-be-undefined.test.ts @@ -41,3 +41,23 @@ ruleTester.run('prefer-to-be-undefined', rule, { }, ], }); + +new TSESLint.RuleTester({ + parser: '@typescript-eslint/parser', +}).run('prefer-to-be-undefined: typescript edition', rule, { + valid: [ + "(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()", + ], + invalid: [ + { + code: 'expect(undefined).toBe(undefined as unknown as string as any);', + errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], + output: 'expect(undefined).toBeUndefined();', + }, + { + code: 'expect("a string").not.toEqual(undefined as number);', + errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + output: 'expect("a string").not.toBeUndefined();', + }, + ], +}); diff --git a/src/rules/__tests__/prefer-to-contain.test.ts b/src/rules/__tests__/prefer-to-contain.test.ts index 85d8c68d4..588a2d309 100644 --- a/src/rules/__tests__/prefer-to-contain.test.ts +++ b/src/rules/__tests__/prefer-to-contain.test.ts @@ -114,3 +114,19 @@ ruleTester.run('prefer-to-contain', rule, { }, ], }); + +new TSESLint.RuleTester({ + parser: '@typescript-eslint/parser', +}).run('prefer-to-be-null: typescript edition', rule, { + valid: [ + "(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()", + 'expect(a.includes(b)).toEqual(0 as boolean);', + ], + invalid: [ + { + code: 'expect(a.includes(b)).toEqual(false as boolean);', + errors: [{ messageId: 'useToContain', column: 23, line: 1 }], + output: 'expect(a).not.toContain(b);', + }, + ], +}); diff --git a/src/rules/prefer-to-be-null.ts b/src/rules/prefer-to-be-null.ts index 4d2d67f5a..7ee1a848b 100644 --- a/src/rules/prefer-to-be-null.ts +++ b/src/rules/prefer-to-be-null.ts @@ -3,9 +3,11 @@ import { TSESTree, } from '@typescript-eslint/experimental-utils'; import { + MaybeTypeCast, ParsedEqualityMatcherCall, ParsedExpectMatcher, createRule, + followTypeAssertionChain, isExpectCall, isParsedEqualityMatcherCall, parseExpectCall, @@ -24,12 +26,13 @@ const isNullLiteral = (node: TSESTree.Node): node is NullLiteral => * * @param {ParsedExpectMatcher} matcher * - * @return {matcher is ParsedEqualityMatcherCall} + * @return {matcher is ParsedEqualityMatcherCall>} */ const isNullEqualityMatcher = ( matcher: ParsedExpectMatcher, -): matcher is ParsedEqualityMatcherCall => - isParsedEqualityMatcherCall(matcher) && isNullLiteral(matcher.arguments[0]); +): matcher is ParsedEqualityMatcherCall> => + isParsedEqualityMatcherCall(matcher) && + isNullLiteral(followTypeAssertionChain(matcher.arguments[0])); export default createRule({ name: __filename, diff --git a/src/rules/prefer-to-be-undefined.ts b/src/rules/prefer-to-be-undefined.ts index fc31c4647..859429cc6 100644 --- a/src/rules/prefer-to-be-undefined.ts +++ b/src/rules/prefer-to-be-undefined.ts @@ -6,6 +6,7 @@ import { ParsedEqualityMatcherCall, ParsedExpectMatcher, createRule, + followTypeAssertionChain, isExpectCall, isParsedEqualityMatcherCall, parseExpectCall, @@ -26,13 +27,13 @@ const isUndefinedIdentifier = ( * * @param {ParsedExpectMatcher} matcher * - * @return {matcher is ParsedEqualityMatcherCall} + * @return {matcher is ParsedEqualityMatcherCall>} */ const isUndefinedEqualityMatcher = ( matcher: ParsedExpectMatcher, ): matcher is ParsedEqualityMatcherCall => isParsedEqualityMatcherCall(matcher) && - isUndefinedIdentifier(matcher.arguments[0]); + isUndefinedIdentifier(followTypeAssertionChain(matcher.arguments[0])); export default createRule({ name: __filename, diff --git a/src/rules/prefer-to-contain.ts b/src/rules/prefer-to-contain.ts index 4ae5a370b..1bd1dbbd4 100644 --- a/src/rules/prefer-to-contain.ts +++ b/src/rules/prefer-to-contain.ts @@ -11,6 +11,7 @@ import { ParsedEqualityMatcherCall, ParsedExpectMatcher, createRule, + followTypeAssertionChain, hasOnlyOneArgument, isExpectCall, isParsedEqualityMatcherCall, @@ -45,7 +46,7 @@ const isBooleanEqualityMatcher = ( matcher: ParsedExpectMatcher, ): matcher is ParsedBooleanEqualityMatcherCall => isParsedEqualityMatcherCall(matcher) && - isBooleanLiteral(matcher.arguments[0]); + isBooleanLiteral(followTypeAssertionChain(matcher.arguments[0])); type FixableIncludesCallExpression = KnownCallExpression<'includes'> & CallExpressionWithSingleArgument; diff --git a/src/rules/utils.ts b/src/rules/utils.ts index 6a0facdbe..8900d2d66 100644 --- a/src/rules/utils.ts +++ b/src/rules/utils.ts @@ -15,6 +15,35 @@ export const createRule = ESLintUtils.RuleCreator(name => { return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`; }); +export type MaybeTypeCast = + | TSTypeCastExpression + | Expression; + +export type TSTypeCastExpression< + Expression extends TSESTree.Expression = TSESTree.Expression +> = AsExpressionChain | TypeAssertionChain; + +interface AsExpressionChain< + Expression extends TSESTree.Expression = TSESTree.Expression +> extends TSESTree.TSAsExpression { + expression: AsExpressionChain | Expression; +} + +interface TypeAssertionChain< + Expression extends TSESTree.Expression = TSESTree.Expression +> extends TSESTree.TSTypeAssertion { + // expression: TypeAssertionChain | Expression; + expression: any; // https://github.com/typescript-eslint/typescript-eslint/issues/802 +} + +export const followTypeAssertionChain = ( + expression: TSESTree.Expression | TSTypeCastExpression, +): TSESTree.Expression => + expression.type === AST_NODE_TYPES.TSAsExpression || + expression.type === AST_NODE_TYPES.TSTypeAssertion + ? followTypeAssertionChain(expression.expression) + : expression; + /** * A `Literal` with a `value` of type `string`. */