Skip to content

Commit 41d44d0

Browse files
G-RathSimenB
authored andcommitted
fix: handle ts as expression in marchers (#403)
1 parent a29f993 commit 41d44d0

File tree

7 files changed

+96
-6
lines changed

7 files changed

+96
-6
lines changed

src/rules/__tests__/prefer-to-be-null.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,23 @@ ruleTester.run('prefer-to-be-null', rule, {
4343
},
4444
],
4545
});
46+
47+
new TSESLint.RuleTester({
48+
parser: '@typescript-eslint/parser',
49+
}).run('prefer-to-be-null: typescript edition', rule, {
50+
valid: [
51+
"(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()",
52+
],
53+
invalid: [
54+
{
55+
code: 'expect(null).toBe(null as unknown as string as unknown as any);',
56+
errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }],
57+
output: 'expect(null).toBeNull();',
58+
},
59+
{
60+
code: 'expect("a string").not.toEqual(null as number);',
61+
errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }],
62+
output: 'expect("a string").not.toBeNull();',
63+
},
64+
],
65+
});

src/rules/__tests__/prefer-to-be-undefined.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,23 @@ ruleTester.run('prefer-to-be-undefined', rule, {
4141
},
4242
],
4343
});
44+
45+
new TSESLint.RuleTester({
46+
parser: '@typescript-eslint/parser',
47+
}).run('prefer-to-be-undefined: typescript edition', rule, {
48+
valid: [
49+
"(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()",
50+
],
51+
invalid: [
52+
{
53+
code: 'expect(undefined).toBe(undefined as unknown as string as any);',
54+
errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }],
55+
output: 'expect(undefined).toBeUndefined();',
56+
},
57+
{
58+
code: 'expect("a string").not.toEqual(undefined as number);',
59+
errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }],
60+
output: 'expect("a string").not.toBeUndefined();',
61+
},
62+
],
63+
});

src/rules/__tests__/prefer-to-contain.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,19 @@ ruleTester.run('prefer-to-contain', rule, {
114114
},
115115
],
116116
});
117+
118+
new TSESLint.RuleTester({
119+
parser: '@typescript-eslint/parser',
120+
}).run('prefer-to-be-null: typescript edition', rule, {
121+
valid: [
122+
"(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()",
123+
'expect(a.includes(b)).toEqual(0 as boolean);',
124+
],
125+
invalid: [
126+
{
127+
code: 'expect(a.includes(b)).toEqual(false as boolean);',
128+
errors: [{ messageId: 'useToContain', column: 23, line: 1 }],
129+
output: 'expect(a).not.toContain(b);',
130+
},
131+
],
132+
});

src/rules/prefer-to-be-null.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
TSESTree,
44
} from '@typescript-eslint/experimental-utils';
55
import {
6+
MaybeTypeCast,
67
ParsedEqualityMatcherCall,
78
ParsedExpectMatcher,
89
createRule,
10+
followTypeAssertionChain,
911
isExpectCall,
1012
isParsedEqualityMatcherCall,
1113
parseExpectCall,
@@ -24,12 +26,13 @@ const isNullLiteral = (node: TSESTree.Node): node is NullLiteral =>
2426
*
2527
* @param {ParsedExpectMatcher} matcher
2628
*
27-
* @return {matcher is ParsedEqualityMatcherCall<NullLiteral>}
29+
* @return {matcher is ParsedEqualityMatcherCall<MaybeTypeCast<NullLiteral>>}
2830
*/
2931
const isNullEqualityMatcher = (
3032
matcher: ParsedExpectMatcher,
31-
): matcher is ParsedEqualityMatcherCall<NullLiteral> =>
32-
isParsedEqualityMatcherCall(matcher) && isNullLiteral(matcher.arguments[0]);
33+
): matcher is ParsedEqualityMatcherCall<MaybeTypeCast<NullLiteral>> =>
34+
isParsedEqualityMatcherCall(matcher) &&
35+
isNullLiteral(followTypeAssertionChain(matcher.arguments[0]));
3336

3437
export default createRule({
3538
name: __filename,

src/rules/prefer-to-be-undefined.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ParsedEqualityMatcherCall,
77
ParsedExpectMatcher,
88
createRule,
9+
followTypeAssertionChain,
910
isExpectCall,
1011
isParsedEqualityMatcherCall,
1112
parseExpectCall,
@@ -26,13 +27,13 @@ const isUndefinedIdentifier = (
2627
*
2728
* @param {ParsedExpectMatcher} matcher
2829
*
29-
* @return {matcher is ParsedEqualityMatcherCall<UndefinedIdentifier>}
30+
* @return {matcher is ParsedEqualityMatcherCall<MaybeTypeCast<UndefinedIdentifier>>}
3031
*/
3132
const isUndefinedEqualityMatcher = (
3233
matcher: ParsedExpectMatcher,
3334
): matcher is ParsedEqualityMatcherCall<UndefinedIdentifier> =>
3435
isParsedEqualityMatcherCall(matcher) &&
35-
isUndefinedIdentifier(matcher.arguments[0]);
36+
isUndefinedIdentifier(followTypeAssertionChain(matcher.arguments[0]));
3637

3738
export default createRule({
3839
name: __filename,

src/rules/prefer-to-contain.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ParsedEqualityMatcherCall,
1212
ParsedExpectMatcher,
1313
createRule,
14+
followTypeAssertionChain,
1415
hasOnlyOneArgument,
1516
isExpectCall,
1617
isParsedEqualityMatcherCall,
@@ -45,7 +46,7 @@ const isBooleanEqualityMatcher = (
4546
matcher: ParsedExpectMatcher,
4647
): matcher is ParsedBooleanEqualityMatcherCall =>
4748
isParsedEqualityMatcherCall(matcher) &&
48-
isBooleanLiteral(matcher.arguments[0]);
49+
isBooleanLiteral(followTypeAssertionChain(matcher.arguments[0]));
4950

5051
type FixableIncludesCallExpression = KnownCallExpression<'includes'> &
5152
CallExpressionWithSingleArgument;

src/rules/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,35 @@ export const createRule = ESLintUtils.RuleCreator(name => {
1515
return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
1616
});
1717

18+
export type MaybeTypeCast<Expression extends TSESTree.Expression> =
19+
| TSTypeCastExpression<Expression>
20+
| Expression;
21+
22+
export type TSTypeCastExpression<
23+
Expression extends TSESTree.Expression = TSESTree.Expression
24+
> = AsExpressionChain<Expression> | TypeAssertionChain<Expression>;
25+
26+
interface AsExpressionChain<
27+
Expression extends TSESTree.Expression = TSESTree.Expression
28+
> extends TSESTree.TSAsExpression {
29+
expression: AsExpressionChain<Expression> | Expression;
30+
}
31+
32+
interface TypeAssertionChain<
33+
Expression extends TSESTree.Expression = TSESTree.Expression
34+
> extends TSESTree.TSTypeAssertion {
35+
// expression: TypeAssertionChain<Expression> | Expression;
36+
expression: any; // https://github.com/typescript-eslint/typescript-eslint/issues/802
37+
}
38+
39+
export const followTypeAssertionChain = (
40+
expression: TSESTree.Expression | TSTypeCastExpression,
41+
): TSESTree.Expression =>
42+
expression.type === AST_NODE_TYPES.TSAsExpression ||
43+
expression.type === AST_NODE_TYPES.TSTypeAssertion
44+
? followTypeAssertionChain(expression.expression)
45+
: expression;
46+
1847
/**
1948
* A `Literal` with a `value` of type `string`.
2049
*/

0 commit comments

Comments
 (0)