1 | 'use strict';
|
2 |
|
3 | const { getDocsUrl, isFunction } = require('./util');
|
4 |
|
5 | const reportMsg =
|
6 | 'Promise should be returned to test its fulfillment or rejection';
|
7 |
|
8 | const isThenOrCatch = node => {
|
9 | return (
|
10 | node.property &&
|
11 | (node.property.name === 'then' || node.property.name === 'catch')
|
12 | );
|
13 | };
|
14 |
|
15 | const isExpectCallPresentInFunction = body => {
|
16 | if (body.type === 'BlockStatement') {
|
17 | return body.body.find(line => {
|
18 | if (line.type === 'ExpressionStatement')
|
19 | return isExpectCall(line.expression);
|
20 | if (line.type === 'ReturnStatement') return isExpectCall(line.argument);
|
21 | });
|
22 | } else {
|
23 | return isExpectCall(body);
|
24 | }
|
25 | };
|
26 |
|
27 | const isExpectCall = expression => {
|
28 | return (
|
29 | expression &&
|
30 | expression.type === 'CallExpression' &&
|
31 | expression.callee.type === 'MemberExpression' &&
|
32 | expression.callee.object.type === 'CallExpression' &&
|
33 | expression.callee.object.callee.name === 'expect'
|
34 | );
|
35 | };
|
36 |
|
37 | const reportReturnRequired = (context, node) => {
|
38 | context.report({
|
39 | loc: {
|
40 | end: {
|
41 | column: node.parent.parent.loc.end.column,
|
42 | line: node.parent.parent.loc.end.line,
|
43 | },
|
44 | start: node.parent.parent.loc.start,
|
45 | },
|
46 | message: reportMsg,
|
47 | node,
|
48 | });
|
49 | };
|
50 |
|
51 | const isPromiseReturnedLater = (node, testFunctionBody) => {
|
52 | let promiseName;
|
53 | if (node.parent.parent.type === 'ExpressionStatement') {
|
54 | promiseName = node.parent.parent.expression.callee.object.name;
|
55 | } else if (node.parent.parent.type === 'VariableDeclarator') {
|
56 | promiseName = node.parent.parent.id.name;
|
57 | }
|
58 | const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
|
59 | return (
|
60 | lastLineInTestFunc.type === 'ReturnStatement' &&
|
61 | lastLineInTestFunc.argument.name === promiseName
|
62 | );
|
63 | };
|
64 |
|
65 | const isTestFunc = node => {
|
66 | return (
|
67 | node.type === 'CallExpression' &&
|
68 | (node.callee.name === 'it' || node.callee.name === 'test')
|
69 | );
|
70 | };
|
71 |
|
72 | const getFunctionBody = func => {
|
73 | if (func.body.type === 'BlockStatement') return func.body.body;
|
74 | return func.body;
|
75 | };
|
76 |
|
77 | const getTestFunction = node => {
|
78 | let { parent } = node;
|
79 | while (parent) {
|
80 | if (isFunction(parent) && isTestFunc(parent.parent)) {
|
81 | return parent;
|
82 | }
|
83 | parent = parent.parent;
|
84 | }
|
85 | };
|
86 |
|
87 | const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
|
88 | return (
|
89 | testFunctionBody.type === 'CallExpression' ||
|
90 | testFunctionBody.type === 'NewExpression' ||
|
91 | node.parent.parent.type === 'ReturnStatement' ||
|
92 | isPromiseReturnedLater(node, testFunctionBody) ||
|
93 | isThenOrCatch(node.parent.parent)
|
94 | );
|
95 | };
|
96 |
|
97 | const verifyExpectWithReturn = (
|
98 | promiseCallbacks,
|
99 | node,
|
100 | context,
|
101 | testFunctionBody
|
102 | ) => {
|
103 | promiseCallbacks.some(promiseCallback => {
|
104 | if (promiseCallback && isFunction(promiseCallback)) {
|
105 | if (
|
106 | isExpectCallPresentInFunction(promiseCallback.body) &&
|
107 | !isParentThenOrPromiseReturned(node, testFunctionBody)
|
108 | ) {
|
109 | reportReturnRequired(context, node);
|
110 | return true;
|
111 | }
|
112 | }
|
113 | });
|
114 | };
|
115 |
|
116 | const isAwaitExpression = node => {
|
117 | return node.parent.parent && node.parent.parent.type === 'AwaitExpression';
|
118 | };
|
119 |
|
120 | const isHavingAsyncCallBackParam = testFunction => {
|
121 | try {
|
122 | return testFunction.params[0].type === 'Identifier';
|
123 | } catch (e) {
|
124 | return false;
|
125 | }
|
126 | };
|
127 |
|
128 | module.exports = {
|
129 | meta: {
|
130 | docs: {
|
131 | url: getDocsUrl(__filename),
|
132 | },
|
133 | },
|
134 | create(context) {
|
135 | return {
|
136 | MemberExpression(node) {
|
137 | if (
|
138 | node.type === 'MemberExpression' &&
|
139 | isThenOrCatch(node) &&
|
140 | node.parent.type === 'CallExpression' &&
|
141 | !isAwaitExpression(node)
|
142 | ) {
|
143 | const testFunction = getTestFunction(node);
|
144 | if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
|
145 | const testFunctionBody = getFunctionBody(testFunction);
|
146 | const [
|
147 | fulfillmentCallback,
|
148 | rejectionCallback,
|
149 | ] = node.parent.arguments;
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | verifyExpectWithReturn(
|
156 | [fulfillmentCallback, rejectionCallback],
|
157 | node,
|
158 | context,
|
159 | testFunctionBody
|
160 | );
|
161 | }
|
162 | }
|
163 | },
|
164 | };
|
165 | },
|
166 | };
|