UNPKG

4.49 kBJavaScriptView Raw
1'use strict';
2
3const { getDocsUrl, isFunction } = require('./util');
4
5const reportMsg =
6 'Promise should be returned to test its fulfillment or rejection';
7
8const isThenOrCatch = node => {
9 return (
10 node.property &&
11 (node.property.name === 'then' || node.property.name === 'catch')
12 );
13};
14
15const 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
27const 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
37const 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
51const 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
65const isTestFunc = node => {
66 return (
67 node.type === 'CallExpression' &&
68 (node.callee.name === 'it' || node.callee.name === 'test')
69 );
70};
71
72const getFunctionBody = func => {
73 if (func.body.type === 'BlockStatement') return func.body.body;
74 return func.body; //arrow-short-hand-fn
75};
76
77const 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
87const 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
97const 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
116const isAwaitExpression = node => {
117 return node.parent.parent && node.parent.parent.type === 'AwaitExpression';
118};
119
120const isHavingAsyncCallBackParam = testFunction => {
121 try {
122 return testFunction.params[0].type === 'Identifier';
123 } catch (e) {
124 return false;
125 }
126};
127
128module.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 // then block can have two args, fulfillment & rejection
152 // then block can have one args, fulfillment
153 // catch block can have one args, rejection
154 // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
155 verifyExpectWithReturn(
156 [fulfillmentCallback, rejectionCallback],
157 node,
158 context,
159 testFunctionBody
160 );
161 }
162 }
163 },
164 };
165 },
166};