valid-expect-in-promise.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _experimentalUtils = require("@typescript-eslint/experimental-utils");
  7. var _utils = require("./utils");
  8. const isThenOrCatchCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(node.callee.property));
  9. const isExpectCallPresentInFunction = body => {
  10. if (body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
  11. return body.body.find(line => {
  12. if (line.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) {
  13. return isFullExpectCall(line.expression);
  14. }
  15. if (line.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && line.argument) {
  16. return isFullExpectCall(line.argument);
  17. }
  18. return false;
  19. });
  20. }
  21. return isFullExpectCall(body);
  22. };
  23. const isFullExpectCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(expression.callee);
  24. const reportReturnRequired = (context, node) => {
  25. context.report({
  26. loc: {
  27. end: {
  28. column: node.loc.end.column,
  29. line: node.loc.end.line
  30. },
  31. start: node.loc.start
  32. },
  33. messageId: 'returnPromise',
  34. node
  35. });
  36. };
  37. const isPromiseReturnedLater = (node, testFunctionBody) => {
  38. let promiseName;
  39. if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && node.expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.expression.callee.object)) {
  40. promiseName = (0, _utils.getAccessorValue)(node.expression.callee.object);
  41. } else if (node.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator && node.id.type === _experimentalUtils.AST_NODE_TYPES.Identifier) {
  42. promiseName = node.id.name;
  43. }
  44. const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
  45. return lastLineInTestFunc.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && lastLineInTestFunc.argument && ('name' in lastLineInTestFunc.argument && lastLineInTestFunc.argument.name === promiseName || !promiseName);
  46. };
  47. const isTestFunc = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isSupportedAccessor)(node.callee) && [_utils.TestCaseName.it, _utils.TestCaseName.test].includes((0, _utils.getAccessorValue)(node.callee));
  48. const findTestFunction = node => {
  49. while (node) {
  50. if ((0, _utils.isFunction)(node) && node.parent && isTestFunc(node.parent)) {
  51. return node;
  52. }
  53. node = node.parent;
  54. }
  55. return null;
  56. };
  57. const isParentThenOrPromiseReturned = (node, testFunctionBody) => node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement || isPromiseReturnedLater(node, testFunctionBody);
  58. const verifyExpectWithReturn = (promiseCallbacks, node, context, testFunctionBody) => {
  59. promiseCallbacks.some(promiseCallback => {
  60. if (promiseCallback && (0, _utils.isFunction)(promiseCallback)) {
  61. if (isExpectCallPresentInFunction(promiseCallback.body) && node.parent.parent && !isParentThenOrPromiseReturned(node.parent.parent, testFunctionBody)) {
  62. reportReturnRequired(context, node.parent.parent);
  63. return true;
  64. }
  65. }
  66. return false;
  67. });
  68. };
  69. const isHavingAsyncCallBackParam = testFunction => testFunction.params[0] && testFunction.params[0].type === _experimentalUtils.AST_NODE_TYPES.Identifier;
  70. var _default = (0, _utils.createRule)({
  71. name: __filename,
  72. meta: {
  73. docs: {
  74. category: 'Best Practices',
  75. description: 'Enforce having return statement when testing with promises',
  76. recommended: 'error'
  77. },
  78. messages: {
  79. returnPromise: 'Promise should be returned to test its fulfillment or rejection'
  80. },
  81. type: 'suggestion',
  82. schema: []
  83. },
  84. defaultOptions: [],
  85. create(context) {
  86. return {
  87. CallExpression(node) {
  88. if (!isThenOrCatchCall(node) || node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) {
  89. return;
  90. }
  91. const testFunction = findTestFunction(node);
  92. if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
  93. const {
  94. body
  95. } = testFunction;
  96. if (body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
  97. return;
  98. }
  99. const testFunctionBody = body.body;
  100. const [fulfillmentCallback, rejectionCallback] = node.arguments; // then block can have two args, fulfillment & rejection
  101. // then block can have one args, fulfillment
  102. // catch block can have one args, rejection
  103. // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
  104. verifyExpectWithReturn([fulfillmentCallback, rejectionCallback], node.callee, context, testFunctionBody);
  105. }
  106. }
  107. };
  108. }
  109. });
  110. exports.default = _default;