forked from avajs/eslint-plugin-ava
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhooks-order.js
More file actions
160 lines (135 loc) · 3.55 KB
/
Copy pathhooks-order.js
File metadata and controls
160 lines (135 loc) · 3.55 KB
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import createAvaRule from '../create-ava-rule.js';
import util from '../util.js';
const MESSAGE_ID = 'hooks-order';
const buildOrders = names => {
const orders = {};
for (const nameLater of names) {
for (const nameEarlier in orders) {
if (orders[nameEarlier]) {
orders[nameEarlier].push(nameLater);
}
}
orders[nameLater] = [];
}
return orders;
};
const buildMessage = (name, orders, visited) => {
const checks = orders[name];
for (const check of checks) {
const nodeEarlier = visited[check];
if (nodeEarlier) {
return {
messageId: MESSAGE_ID,
data: {
current: name,
invalid: check,
},
node: nodeEarlier,
};
}
}
return null;
};
const create = context => {
const ava = createAvaRule(context);
const orders = buildOrders([
'before',
'after',
'after.always',
'beforeEach',
'afterEach',
'afterEach.always',
'test',
]);
const visited = {};
const checks = [
{
selector: 'CallExpression[callee.object.name="test"][callee.property.name="before"]',
name: 'before',
},
{
selector: 'CallExpression[callee.object.name="test"][callee.property.name="after"]',
name: 'after',
},
{
selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="after"][callee.property.name="always"]',
name: 'after.always',
},
{
selector: 'CallExpression[callee.object.name="test"][callee.property.name="beforeEach"]',
name: 'beforeEach',
},
{
selector: 'CallExpression[callee.object.name="test"][callee.property.name="afterEach"]',
name: 'afterEach',
},
{
selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="afterEach"][callee.property.name="always"]',
name: 'afterEach.always',
},
{
selector: 'CallExpression[callee.name="test"]',
name: 'test',
},
];
const {sourceCode} = context;
const selectors = {};
for (const check of checks) {
selectors[check.selector] = node => {
if (!ava.isInTestFile() || !ava.isTestNode(node)) {
return;
}
visited[check.name] = node;
const message = buildMessage(check.name, orders, visited);
if (message) {
const nodeEarlier = message.node;
context.report({
node,
messageId: message.messageId,
data: message.data,
fix(fixer) {
const tokensBetween = sourceCode.getTokensBetween(nodeEarlier.parent, node.parent);
if (tokensBetween?.length > 0) {
return;
}
const source = sourceCode.getText();
let [insertStart, insertEnd] = nodeEarlier.parent.range;
// Grab the node and all comments and whitespace before the node
const start = nodeEarlier.parent.range[1];
const end = node.parent.range[1];
let text = sourceCode.getText().slice(start, end);
// Preserve newline previously between hooks
if (source.length >= (start + 1) && source[start + 1] === '\n') {
text = text.slice(1) + '\n';
}
// Preserve newline that was previously before hooks
if ((insertStart - 1) > 0 && source[insertStart - 1] === '\n') {
insertStart -= 1;
}
return [
fixer.insertTextBeforeRange([insertStart, insertEnd], text),
fixer.removeRange([start, end]),
];
},
});
}
};
}
return ava.merge(selectors);
};
export default {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Enforce test hook ordering.',
recommended: true,
url: util.getDocsUrl(import.meta.filename),
},
fixable: 'code',
schema: [],
messages: {
[MESSAGE_ID]: '`{{current}}` hook must come before `{{invalid}}`',
},
},
};