prefer-explicit-assert.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.RULE_NAME = void 0;
  4. const utils_1 = require("@typescript-eslint/utils");
  5. const create_testing_library_rule_1 = require("../create-testing-library-rule");
  6. const node_utils_1 = require("../node-utils");
  7. const utils_2 = require("../utils");
  8. exports.RULE_NAME = 'prefer-explicit-assert';
  9. const isAtTopLevel = (node) => {
  10. var _a, _b, _c;
  11. return (!!((_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent) &&
  12. node.parent.parent.type === 'ExpressionStatement') ||
  13. (((_c = (_b = node.parent) === null || _b === void 0 ? void 0 : _b.parent) === null || _c === void 0 ? void 0 : _c.type) === 'AwaitExpression' &&
  14. !!node.parent.parent.parent &&
  15. node.parent.parent.parent.type === 'ExpressionStatement');
  16. };
  17. const isVariableDeclaration = (node) => {
  18. if ((0, node_utils_1.isCallExpression)(node.parent) &&
  19. utils_1.ASTUtils.isAwaitExpression(node.parent.parent) &&
  20. utils_1.ASTUtils.isVariableDeclarator(node.parent.parent.parent)) {
  21. return true;
  22. }
  23. if ((0, node_utils_1.isCallExpression)(node.parent) &&
  24. utils_1.ASTUtils.isVariableDeclarator(node.parent.parent)) {
  25. return true;
  26. }
  27. if ((0, node_utils_1.isMemberExpression)(node.parent) &&
  28. (0, node_utils_1.isCallExpression)(node.parent.parent) &&
  29. utils_1.ASTUtils.isAwaitExpression(node.parent.parent.parent) &&
  30. utils_1.ASTUtils.isVariableDeclarator(node.parent.parent.parent.parent)) {
  31. return true;
  32. }
  33. if ((0, node_utils_1.isMemberExpression)(node.parent) &&
  34. (0, node_utils_1.isCallExpression)(node.parent.parent) &&
  35. utils_1.ASTUtils.isVariableDeclarator(node.parent.parent.parent)) {
  36. return true;
  37. }
  38. return false;
  39. };
  40. exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
  41. name: exports.RULE_NAME,
  42. meta: {
  43. type: 'suggestion',
  44. docs: {
  45. description: 'Suggest using explicit assertions rather than standalone queries',
  46. recommendedConfig: {
  47. dom: false,
  48. angular: false,
  49. react: false,
  50. vue: false,
  51. marko: false,
  52. },
  53. },
  54. messages: {
  55. preferExplicitAssert: 'Wrap stand-alone `{{queryType}}` query with `expect` function for better explicit assertion',
  56. preferExplicitAssertAssertion: '`getBy*` queries must be asserted with `{{assertion}}`',
  57. },
  58. schema: [
  59. {
  60. type: 'object',
  61. additionalProperties: false,
  62. properties: {
  63. assertion: {
  64. type: 'string',
  65. enum: utils_2.PRESENCE_MATCHERS,
  66. },
  67. includeFindQueries: { type: 'boolean' },
  68. },
  69. },
  70. ],
  71. },
  72. defaultOptions: [{ includeFindQueries: true }],
  73. create(context, [options], helpers) {
  74. const { assertion, includeFindQueries } = options;
  75. const getQueryCalls = [];
  76. const findQueryCalls = [];
  77. return {
  78. 'CallExpression Identifier'(node) {
  79. if (helpers.isGetQueryVariant(node)) {
  80. getQueryCalls.push(node);
  81. }
  82. if (helpers.isFindQueryVariant(node)) {
  83. findQueryCalls.push(node);
  84. }
  85. },
  86. 'Program:exit'() {
  87. if (includeFindQueries) {
  88. findQueryCalls.forEach((queryCall) => {
  89. const memberExpression = (0, node_utils_1.isMemberExpression)(queryCall.parent)
  90. ? queryCall.parent
  91. : queryCall;
  92. if (isVariableDeclaration(queryCall) ||
  93. !isAtTopLevel(memberExpression)) {
  94. return;
  95. }
  96. context.report({
  97. node: queryCall,
  98. messageId: 'preferExplicitAssert',
  99. data: {
  100. queryType: 'findBy*',
  101. },
  102. });
  103. });
  104. }
  105. getQueryCalls.forEach((queryCall) => {
  106. const node = (0, node_utils_1.isMemberExpression)(queryCall.parent)
  107. ? queryCall.parent
  108. : queryCall;
  109. if (isAtTopLevel(node)) {
  110. context.report({
  111. node: queryCall,
  112. messageId: 'preferExplicitAssert',
  113. data: {
  114. queryType: 'getBy*',
  115. },
  116. });
  117. }
  118. if (assertion) {
  119. const expectCallNode = (0, node_utils_1.findClosestCallNode)(node, 'expect');
  120. if (!expectCallNode)
  121. return;
  122. const expectStatement = expectCallNode.parent;
  123. if (!(0, node_utils_1.isMemberExpression)(expectStatement)) {
  124. return;
  125. }
  126. const property = expectStatement.property;
  127. if (!utils_1.ASTUtils.isIdentifier(property)) {
  128. return;
  129. }
  130. let matcher = property.name;
  131. let isNegatedMatcher = false;
  132. if (matcher === 'not' &&
  133. (0, node_utils_1.isMemberExpression)(expectStatement.parent) &&
  134. utils_1.ASTUtils.isIdentifier(expectStatement.parent.property)) {
  135. isNegatedMatcher = true;
  136. matcher = expectStatement.parent.property.name;
  137. }
  138. const shouldEnforceAssertion = (!isNegatedMatcher && utils_2.PRESENCE_MATCHERS.includes(matcher)) ||
  139. (isNegatedMatcher && utils_2.ABSENCE_MATCHERS.includes(matcher));
  140. if (shouldEnforceAssertion && matcher !== assertion) {
  141. context.report({
  142. node: property,
  143. messageId: 'preferExplicitAssertAssertion',
  144. data: {
  145. assertion,
  146. },
  147. });
  148. }
  149. }
  150. });
  151. },
  152. };
  153. },
  154. });