/
prefer-to-be-object.ts
99 lines (84 loc) · 2.67 KB
/
prefer-to-be-object.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
import { createEslintRule, getAccessorValue, isParsedInstanceOfMatcherCall } from '../utils'
import { isBooleanEqualityMatcher, isInstanceOfBinaryExpression } from '../utils/msc'
import { followTypeAssertionChain, parseVitestFnCall } from '../utils/parseVitestFnCall'
export const RULE_NAME = 'prefer-to-be-object'
export type MESSAGE_IDS = 'preferToBeObject';
export type Options = []
export default createEslintRule<Options, MESSAGE_IDS>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer toBeObject()',
recommended: 'error'
},
fixable: 'code',
messages: {
preferToBeObject: 'Prefer toBeObject() to test if a value is an object.'
},
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const vitestFnCall = parseVitestFnCall(node, context)
if (vitestFnCall?.type !== 'expectTypeOf')
return
if (isParsedInstanceOfMatcherCall(vitestFnCall, 'Object')) {
context.report({
node: vitestFnCall.matcher,
messageId: 'preferToBeObject',
fix: fixer => [
fixer.replaceTextRange(
[
vitestFnCall.matcher.range[0],
vitestFnCall.matcher.range[1] + '(Object)'.length
],
'toBeObject()'
)
]
})
return
}
const { parent: expectTypeOf } = vitestFnCall.head.node
if (expectTypeOf?.type !== AST_NODE_TYPES.CallExpression)
return
const [expectTypeOfArgs] = expectTypeOf.arguments
if (!expectTypeOfArgs ||
!isBooleanEqualityMatcher(vitestFnCall) ||
!isInstanceOfBinaryExpression(expectTypeOfArgs, 'Object'))
return
context.report({
node: vitestFnCall.matcher,
messageId: 'preferToBeObject',
fix(fixer) {
const fixes = [
fixer.replaceText(vitestFnCall.matcher, 'toBeObject'),
fixer.removeRange([expectTypeOfArgs.left.range[1], expectTypeOfArgs.range[1]])
]
let invertCondition = getAccessorValue(vitestFnCall.matcher) === 'toBeFalsy'
if (vitestFnCall.args.length) {
const [matcherArg] = vitestFnCall.args
fixes.push(fixer.remove(matcherArg))
invertCondition = matcherArg.type === AST_NODE_TYPES.Literal &&
followTypeAssertionChain(matcherArg).value === false
}
if (invertCondition) {
const notModifier = vitestFnCall.modifiers.find(node => getAccessorValue(node) === 'not')
fixes.push(notModifier
? fixer.removeRange([
notModifier.range[0] - 1,
notModifier.range[1]
])
: fixer.insertTextBefore(vitestFnCall.matcher, 'not.')
)
}
return fixes
}
})
}
}
}
})