Skip to content

Commit

Permalink
feat: added the prefer-strict-boolean-matchers rule (#650)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
marekdedic and veritem authored Feb 4, 2025
1 parent ab29448 commit 4d86836
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 2 deletions.
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import paddingAroundDescribeBlocks, { RULE_NAME as paddingAroundDescribeBlocksNa
import paddingAroundExpectGroups, { RULE_NAME as paddingAroundExpectGroupsName } from './rules/padding-around-expect-groups'
import paddingAroundTestBlocks, { RULE_NAME as paddingAroundTestBlocksName } from './rules/padding-around-test-blocks'
import validExpectInPromise, { RULE_NAME as validExpectInPromiseName } from './rules/valid-expect-in-promise'
import preferStrictBooleanMatchers, { RULE_NAME as preferStrictBooleanMatchersName } from './rules/prefer-strict-boolean-matchers'

const createConfig = <R extends Linter.RulesRecord>(rules: R) => (
Object.keys(rules).reduce((acc, ruleName) => {
Expand Down Expand Up @@ -146,7 +147,8 @@ const allRules = {
[validExpectName]: 'warn',
[validDescribeCallbackName]: 'warn',
[requireLocalTestContextForConcurrentSnapshotsName]: 'warn',
[noImportNodeTestName]: 'warn'
[noImportNodeTestName]: 'warn',
[preferStrictBooleanMatchersName]: 'warn'
} as const

const recommended = {
Expand Down Expand Up @@ -227,7 +229,8 @@ const plugin = {
[paddingAroundDescribeBlocksName]: paddingAroundDescribeBlocks,
[paddingAroundExpectGroupsName]: paddingAroundExpectGroups,
[paddingAroundTestBlocksName]: paddingAroundTestBlocks,
[validExpectInPromiseName]: validExpectInPromise
[validExpectInPromiseName]: validExpectInPromise,
[preferStrictBooleanMatchersName]: preferStrictBooleanMatchers
},
environments: {
env: {
Expand Down
54 changes: 54 additions & 0 deletions src/rules/prefer-strict-boolean-matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createEslintRule, getAccessorValue } from '../utils'
import { parseVitestFnCall } from '../utils/parse-vitest-fn-call'

type MESSAGE_IDS = 'preferToBeTrue' | 'preferToBeFalse'
export const RULE_NAME = 'prefer-strict-boolean-matchers'
type Options = []

export default createEslintRule<Options, MESSAGE_IDS>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: 'enforce using `toBe(true)` and `toBe(false)` over matchers that coerce types to boolean',
recommended: false
},
messages: {
preferToBeTrue: 'Prefer using `toBe(true)` to test value is `true`',
preferToBeFalse: 'Prefer using `toBe(false)` to test value is `false`'
},
fixable: 'code',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const vitestFnCall = parseVitestFnCall(node, context)
if (!(vitestFnCall?.type === 'expect' || vitestFnCall?.type === 'expectTypeOf')) return

const accessor = getAccessorValue(vitestFnCall.matcher)
if (accessor === 'toBeFalsy') {
context.report({
node: vitestFnCall.matcher,
messageId: 'preferToBeFalse',
fix: fixer => [
fixer.replaceText(vitestFnCall.matcher, 'toBe'),
fixer.insertTextAfterRange([vitestFnCall.matcher.range[0], vitestFnCall.matcher.range[1] + 1], 'false')
]
})
}
if (accessor === 'toBeTruthy') {
context.report({
node: vitestFnCall.matcher,
messageId: 'preferToBeTrue',
fix: fixer => [
fixer.replaceText(vitestFnCall.matcher, 'toBe'),
fixer.insertTextAfterRange([vitestFnCall.matcher.range[0], vitestFnCall.matcher.range[1] + 1], 'true')
]
})
}
}
}
}
})
75 changes: 75 additions & 0 deletions tests/prefer-strict-boolean-matchers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import rule, { RULE_NAME } from '../src/rules/prefer-strict-boolean-matchers'
import { ruleTester } from './ruleTester'

const messageIdTrue = 'preferToBeTrue'
const messageIdFalse = 'preferToBeFalse'

ruleTester.run(RULE_NAME, rule, {
valid: [
'[].push(true)',
'[].push(false)',
'expect("something");',
'expect(true).toBe(true);',
'expect(true).toBe(false);',
'expect(false).toBe(true);',
'expect(false).toBe(false);',
'expect(fal,se).toBe(true);',
'expect(fal,se).toBe(false);',
'expect(value).toEqual();',
'expect(value).not.toBe(true);',
'expect(value).not.toBe(false);',
'expect(value).not.toEqual();',
'expect(value).toBe(undefined);',
'expect(value).not.toBe(undefined);',
'expect(value).toBe();',
'expect(true).toMatchSnapshot();',
'expect("a string").toMatchSnapshot(true);',
'expect("a string").toMatchSnapshot(false);',
'expect("a string").not.toMatchSnapshot();',
'expect(something).toEqual(\'a string\');',
'expect(true).toBe',
'expectTypeOf(true).toBe()'
],
invalid: [
{
code: 'expect(false).toBeTruthy();',
output: 'expect(false).toBe(true);',
errors: [{ messageId: messageIdTrue, column: 15, line: 1 }]
},
{
code: 'expect(false).toBeFalsy();',
output: 'expect(false).toBe(false);',
errors: [{ messageId: messageIdFalse, column: 15, line: 1 }]
},
{
code: 'expectTypeOf(false).toBeTruthy();',
output: 'expectTypeOf(false).toBe(true);',
errors: [{ messageId: messageIdTrue, column: 21, line: 1 }]
},
{
code: 'expectTypeOf(false).toBeFalsy();',
output: 'expectTypeOf(false).toBe(false);',
errors: [{ messageId: messageIdFalse, column: 21, line: 1 }]
},
{
code: 'expect(wasSuccessful).toBeTruthy();',
output: 'expect(wasSuccessful).toBe(true);',
errors: [{ messageId: messageIdTrue, column: 23, line: 1 }]
},
{
code: 'expect(wasSuccessful).toBeFalsy();',
output: 'expect(wasSuccessful).toBe(false);',
errors: [{ messageId: messageIdFalse, column: 23, line: 1 }]
},
{
code: 'expect("a string").not.toBeTruthy();',
output: 'expect("a string").not.toBe(true);',
errors: [{ messageId: messageIdTrue, column: 24, line: 1 }]
},
{
code: 'expect("a string").not.toBeFalsy();',
output: 'expect("a string").not.toBe(false);',
errors: [{ messageId: messageIdFalse, column: 24, line: 1 }]
}
]
})

0 comments on commit 4d86836

Please sign in to comment.