1 | const {docsUrl} = require('../utilities');
|
2 |
|
3 |
|
4 | const SINON_ASSERT = [
|
5 | 'alwaysCalledOn',
|
6 | 'alwaysCalledWith',
|
7 | 'alwaysCalledWithExactly',
|
8 | 'alwaysCalledWithMatch',
|
9 | 'alwaysThrew',
|
10 | 'callCount',
|
11 | 'called',
|
12 | 'calledOn',
|
13 | 'calledOnce',
|
14 | 'calledThrice',
|
15 | 'calledTwice',
|
16 | 'calledWith',
|
17 | 'calledWithExactly',
|
18 | 'calledWithMatch',
|
19 | 'callOrder',
|
20 | 'neverCalledWith',
|
21 | 'neverCalledWithMatch',
|
22 | 'notCalled',
|
23 | 'threw',
|
24 | ];
|
25 |
|
26 |
|
27 | const SINON_CHAI = [
|
28 | 'alwaysCalledOn',
|
29 | 'alwaysCalledWith',
|
30 | 'alwaysCalledWithExactly',
|
31 | 'alwaysCalledWithMatch',
|
32 | 'alwaysCalledWithNew',
|
33 | 'alwaysReturned',
|
34 | 'alwaysThrew',
|
35 | 'callCount',
|
36 | 'called',
|
37 | 'calledAfter',
|
38 | 'calledBefore',
|
39 | 'calledOn',
|
40 | 'calledOnce',
|
41 | 'calledThrice',
|
42 | 'calledTwice',
|
43 | 'calledWith',
|
44 | 'calledWithExactly',
|
45 | 'calledWithMatch',
|
46 | 'calledWithNew',
|
47 | 'notCalled',
|
48 | 'returned',
|
49 | 'threw',
|
50 | ];
|
51 |
|
52 | const PREFER_SINON_CHAI_MESSAGE =
|
53 | 'Use the more meaningful sinon-chai assertions.';
|
54 | const PREFER_SINON_ASSERT_MESSAGE =
|
55 | 'Use the more meaningful sinon.assert assertions.';
|
56 |
|
57 | module.exports = {
|
58 | meta: {
|
59 | docs: {
|
60 | description:
|
61 | 'Require the use of meaningful sinon assertions through sinon.assert or sinon-chai.',
|
62 | category: 'Best Practices',
|
63 | recommended: false,
|
64 | uri: docsUrl('sinon-prefer-meaningful-assertions'),
|
65 | },
|
66 | },
|
67 |
|
68 | create(context) {
|
69 | function getNearestFullExpression(node) {
|
70 | while (
|
71 | node.parent &&
|
72 | (node.parent.type === 'MemberExpression' ||
|
73 | node.parent.type === 'CallExpression')
|
74 | ) {
|
75 |
|
76 | node = node.parent;
|
77 | }
|
78 |
|
79 | return node;
|
80 | }
|
81 |
|
82 | function isAssertExpression(node) {
|
83 | return node.type === 'MemberExpression' && node.object.name === 'assert';
|
84 | }
|
85 |
|
86 | function isRewritableSinonExpression(node, methods) {
|
87 | if (!node) {
|
88 | return false;
|
89 | }
|
90 | return (
|
91 | (node.type === 'MemberExpression' &&
|
92 | methods.indexOf(node.property.name) >= 0) ||
|
93 | (node.type === 'CallExpression' &&
|
94 | isRewritableSinonExpression(node.callee, methods))
|
95 | );
|
96 | }
|
97 |
|
98 | function isInvalidAssertCallExpression(node) {
|
99 | return (
|
100 | isAssertExpression(node.callee) &&
|
101 | node.arguments.some((arg) => {
|
102 | return isRewritableSinonExpression(arg, SINON_ASSERT);
|
103 | })
|
104 | );
|
105 | }
|
106 |
|
107 | function isInvalidShouldExpression(node) {
|
108 | return (
|
109 | node.property.name === 'should' &&
|
110 | isRewritableSinonExpression(node.object, SINON_CHAI)
|
111 | );
|
112 | }
|
113 |
|
114 | function isInvalidExpectCallExpression(node) {
|
115 | return (
|
116 | node.callee.name === 'expect' &&
|
117 | isRewritableSinonExpression(node.arguments[0], SINON_CHAI)
|
118 | );
|
119 | }
|
120 |
|
121 | function handleCallExpression(node) {
|
122 | if (isInvalidAssertCallExpression(node)) {
|
123 | context.report({
|
124 | node,
|
125 | message: PREFER_SINON_ASSERT_MESSAGE,
|
126 | });
|
127 | }
|
128 |
|
129 | if (isInvalidExpectCallExpression(node)) {
|
130 | context.report({
|
131 | node: getNearestFullExpression(node),
|
132 | message: PREFER_SINON_CHAI_MESSAGE,
|
133 | });
|
134 | }
|
135 | }
|
136 |
|
137 | function handleMemberExpression(node) {
|
138 | if (isInvalidShouldExpression(node)) {
|
139 | context.report({
|
140 | node: getNearestFullExpression(node),
|
141 | message: PREFER_SINON_CHAI_MESSAGE,
|
142 | });
|
143 | }
|
144 | }
|
145 |
|
146 | return {
|
147 | CallExpression: handleCallExpression,
|
148 | MemberExpression: handleMemberExpression,
|
149 | };
|
150 | },
|
151 | };
|