Skip to content

Commit 4d86836

Browse files
marekdedicveritem
andauthoredFeb 4, 2025··
feat: added the prefer-strict-boolean-matchers rule (#650)
* test(prefer-strict-boolean-matchers): added tests * feat(prefer-strict-boolean-matchers): implemented the rule * chore: export the rule --------- Co-authored-by: Verite Mugabo <mugaboverite@gmail.com>
1 parent ab29448 commit 4d86836

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed
 

‎src/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import paddingAroundDescribeBlocks, { RULE_NAME as paddingAroundDescribeBlocksNa
6363
import paddingAroundExpectGroups, { RULE_NAME as paddingAroundExpectGroupsName } from './rules/padding-around-expect-groups'
6464
import paddingAroundTestBlocks, { RULE_NAME as paddingAroundTestBlocksName } from './rules/padding-around-test-blocks'
6565
import validExpectInPromise, { RULE_NAME as validExpectInPromiseName } from './rules/valid-expect-in-promise'
66+
import preferStrictBooleanMatchers, { RULE_NAME as preferStrictBooleanMatchersName } from './rules/prefer-strict-boolean-matchers'
6667

6768
const createConfig = <R extends Linter.RulesRecord>(rules: R) => (
6869
Object.keys(rules).reduce((acc, ruleName) => {
@@ -146,7 +147,8 @@ const allRules = {
146147
[validExpectName]: 'warn',
147148
[validDescribeCallbackName]: 'warn',
148149
[requireLocalTestContextForConcurrentSnapshotsName]: 'warn',
149-
[noImportNodeTestName]: 'warn'
150+
[noImportNodeTestName]: 'warn',
151+
[preferStrictBooleanMatchersName]: 'warn'
150152
} as const
151153

152154
const recommended = {
@@ -227,7 +229,8 @@ const plugin = {
227229
[paddingAroundDescribeBlocksName]: paddingAroundDescribeBlocks,
228230
[paddingAroundExpectGroupsName]: paddingAroundExpectGroups,
229231
[paddingAroundTestBlocksName]: paddingAroundTestBlocks,
230-
[validExpectInPromiseName]: validExpectInPromise
232+
[validExpectInPromiseName]: validExpectInPromise,
233+
[preferStrictBooleanMatchersName]: preferStrictBooleanMatchers
231234
},
232235
environments: {
233236
env: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { createEslintRule, getAccessorValue } from '../utils'
2+
import { parseVitestFnCall } from '../utils/parse-vitest-fn-call'
3+
4+
type MESSAGE_IDS = 'preferToBeTrue' | 'preferToBeFalse'
5+
export const RULE_NAME = 'prefer-strict-boolean-matchers'
6+
type Options = []
7+
8+
export default createEslintRule<Options, MESSAGE_IDS>({
9+
name: RULE_NAME,
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description: 'enforce using `toBe(true)` and `toBe(false)` over matchers that coerce types to boolean',
14+
recommended: false
15+
},
16+
messages: {
17+
preferToBeTrue: 'Prefer using `toBe(true)` to test value is `true`',
18+
preferToBeFalse: 'Prefer using `toBe(false)` to test value is `false`'
19+
},
20+
fixable: 'code',
21+
schema: []
22+
},
23+
defaultOptions: [],
24+
create(context) {
25+
return {
26+
CallExpression(node) {
27+
const vitestFnCall = parseVitestFnCall(node, context)
28+
if (!(vitestFnCall?.type === 'expect' || vitestFnCall?.type === 'expectTypeOf')) return
29+
30+
const accessor = getAccessorValue(vitestFnCall.matcher)
31+
if (accessor === 'toBeFalsy') {
32+
context.report({
33+
node: vitestFnCall.matcher,
34+
messageId: 'preferToBeFalse',
35+
fix: fixer => [
36+
fixer.replaceText(vitestFnCall.matcher, 'toBe'),
37+
fixer.insertTextAfterRange([vitestFnCall.matcher.range[0], vitestFnCall.matcher.range[1] + 1], 'false')
38+
]
39+
})
40+
}
41+
if (accessor === 'toBeTruthy') {
42+
context.report({
43+
node: vitestFnCall.matcher,
44+
messageId: 'preferToBeTrue',
45+
fix: fixer => [
46+
fixer.replaceText(vitestFnCall.matcher, 'toBe'),
47+
fixer.insertTextAfterRange([vitestFnCall.matcher.range[0], vitestFnCall.matcher.range[1] + 1], 'true')
48+
]
49+
})
50+
}
51+
}
52+
}
53+
}
54+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import rule, { RULE_NAME } from '../src/rules/prefer-strict-boolean-matchers'
2+
import { ruleTester } from './ruleTester'
3+
4+
const messageIdTrue = 'preferToBeTrue'
5+
const messageIdFalse = 'preferToBeFalse'
6+
7+
ruleTester.run(RULE_NAME, rule, {
8+
valid: [
9+
'[].push(true)',
10+
'[].push(false)',
11+
'expect("something");',
12+
'expect(true).toBe(true);',
13+
'expect(true).toBe(false);',
14+
'expect(false).toBe(true);',
15+
'expect(false).toBe(false);',
16+
'expect(fal,se).toBe(true);',
17+
'expect(fal,se).toBe(false);',
18+
'expect(value).toEqual();',
19+
'expect(value).not.toBe(true);',
20+
'expect(value).not.toBe(false);',
21+
'expect(value).not.toEqual();',
22+
'expect(value).toBe(undefined);',
23+
'expect(value).not.toBe(undefined);',
24+
'expect(value).toBe();',
25+
'expect(true).toMatchSnapshot();',
26+
'expect("a string").toMatchSnapshot(true);',
27+
'expect("a string").toMatchSnapshot(false);',
28+
'expect("a string").not.toMatchSnapshot();',
29+
'expect(something).toEqual(\'a string\');',
30+
'expect(true).toBe',
31+
'expectTypeOf(true).toBe()'
32+
],
33+
invalid: [
34+
{
35+
code: 'expect(false).toBeTruthy();',
36+
output: 'expect(false).toBe(true);',
37+
errors: [{ messageId: messageIdTrue, column: 15, line: 1 }]
38+
},
39+
{
40+
code: 'expect(false).toBeFalsy();',
41+
output: 'expect(false).toBe(false);',
42+
errors: [{ messageId: messageIdFalse, column: 15, line: 1 }]
43+
},
44+
{
45+
code: 'expectTypeOf(false).toBeTruthy();',
46+
output: 'expectTypeOf(false).toBe(true);',
47+
errors: [{ messageId: messageIdTrue, column: 21, line: 1 }]
48+
},
49+
{
50+
code: 'expectTypeOf(false).toBeFalsy();',
51+
output: 'expectTypeOf(false).toBe(false);',
52+
errors: [{ messageId: messageIdFalse, column: 21, line: 1 }]
53+
},
54+
{
55+
code: 'expect(wasSuccessful).toBeTruthy();',
56+
output: 'expect(wasSuccessful).toBe(true);',
57+
errors: [{ messageId: messageIdTrue, column: 23, line: 1 }]
58+
},
59+
{
60+
code: 'expect(wasSuccessful).toBeFalsy();',
61+
output: 'expect(wasSuccessful).toBe(false);',
62+
errors: [{ messageId: messageIdFalse, column: 23, line: 1 }]
63+
},
64+
{
65+
code: 'expect("a string").not.toBeTruthy();',
66+
output: 'expect("a string").not.toBe(true);',
67+
errors: [{ messageId: messageIdTrue, column: 24, line: 1 }]
68+
},
69+
{
70+
code: 'expect("a string").not.toBeFalsy();',
71+
output: 'expect("a string").not.toBe(false);',
72+
errors: [{ messageId: messageIdFalse, column: 24, line: 1 }]
73+
}
74+
]
75+
})

0 commit comments

Comments
 (0)
Please sign in to comment.