-
Notifications
You must be signed in to change notification settings - Fork 54
/
valid-describe-callback.ts
134 lines (113 loc) · 3.91 KB
/
valid-describe-callback.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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'
import { createEslintRule, FunctionExpression, getAccessorValue, isFunction } from '../utils'
import { ParsedVitestFnCall, parseVitestFnCall } from '../utils/parse-vitest-fn-call'
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
export const RULE_NAME = 'valid-describe-callback'
type MESSAGE_IDS =
| 'nameAndCallback'
| 'secondArgumentMustBeFunction'
| 'unexpectedDescribeArgument'
| 'unexpectedReturnInDescribe'
type Options = []
const paramsLocation = (params: TSESTree.CallExpressionArgument[] | TSESTree.Parameter[]) => {
const [first] = params
const last = params[params.length - 1]
return {
start: first.loc.start,
end: last.loc.end
}
}
const hasNonEachMembersAndParams = (vitestFnCall: ParsedVitestFnCall, functionExpression: FunctionExpression) => {
return vitestFnCall.members.every(s => getAccessorValue(s) !== 'each') && functionExpression.params.length
}
const reportUnexpectedReturnInDescribe = (blockStatement: TSESTree.BlockStatement, context: Readonly<RuleContext<MESSAGE_IDS, []>>) => {
blockStatement.body.forEach((node) => {
if (node.type !== AST_NODE_TYPES.ReturnStatement) return
context.report({
messageId: 'unexpectedReturnInDescribe',
node
})
})
}
export default createEslintRule<Options, MESSAGE_IDS>({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: 'enforce valid describe callback',
recommended: false
},
messages: {
nameAndCallback: 'Describe requires a name and callback arguments',
secondArgumentMustBeFunction: 'Second argument must be a function',
unexpectedDescribeArgument: 'Unexpected argument in describe callback',
unexpectedReturnInDescribe: 'Unexpected return statement in describe callback'
},
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const vitestFnCall = parseVitestFnCall(node, context)
if (vitestFnCall?.type !== 'describe') return
if (vitestFnCall?.members[0]?.type === AST_NODE_TYPES.Identifier && vitestFnCall.members[0].name === 'todo')
return
if (node.arguments.length < 1) {
return context.report({
messageId: 'nameAndCallback',
loc: node.loc
})
}
const [, arg2, arg3] = node.arguments
if (!arg2) {
context.report({
messageId: 'nameAndCallback',
loc: paramsLocation(node.arguments)
})
return
}
if (!isFunction(arg2)) {
if (arg3 && isFunction(arg3)) {
if (hasNonEachMembersAndParams(vitestFnCall, arg3)) {
context.report({
messageId: 'unexpectedDescribeArgument',
node: arg3
})
}
if (arg3.body.type === AST_NODE_TYPES.CallExpression) {
context.report({
messageId: 'unexpectedReturnInDescribe',
node: arg3
})
}
if (arg3.body.type === AST_NODE_TYPES.BlockStatement) {
reportUnexpectedReturnInDescribe(arg3.body, context)
}
return
}
context.report({
messageId: 'secondArgumentMustBeFunction',
loc: paramsLocation(node.arguments)
})
return
}
if (hasNonEachMembersAndParams(vitestFnCall, arg2)) {
context.report({
messageId: 'unexpectedDescribeArgument',
node: arg2
})
}
if (arg2.body.type === AST_NODE_TYPES.CallExpression) {
context.report({
messageId: 'unexpectedReturnInDescribe',
node: arg2
})
}
if (arg2.body.type === AST_NODE_TYPES.BlockStatement) {
reportUnexpectedReturnInDescribe(arg2.body, context)
}
}
}
}
})