blob: 29a7d6b510c02dd5cec262770364e9bfd4b6904d [file] [log] [blame]
Jack Franklin8b9aa2f2020-02-12 16:35:15 +00001'use strict';
2
3const find = require('ramda/src/find');
Tim van der Lippe16aca392020-11-13 11:37:13 +00004const createAstUtils = require('../util/ast');
Jack Franklin8b9aa2f2020-02-12 16:35:15 +00005
Tim van der Lippe16aca392020-11-13 11:37:13 +00006module.exports = {
7 meta: {
8 type: 'problem',
9 docs: {
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010010 description: 'Enforces handling of callbacks for async tests',
11 url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/handle-done-callback.md'
Tim van der Lippe16aca392020-11-13 11:37:13 +000012 },
13 schema: [
14 {
15 type: 'object',
16 properties: {
17 ignoreSkipped: {
18 type: 'boolean',
19 default: false
20 }
21 },
22 additionalProperties: false
23 }
24 ]
25 },
26 create(context) {
27 const astUtils = createAstUtils(context.settings);
28 const [ { ignoreSkipped = false } = {} ] = context.options;
29 const modifiersToCheck = ignoreSkipped ? [ 'only' ] : [ 'only', 'skip' ];
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000030
Tim van der Lippe16aca392020-11-13 11:37:13 +000031 function isAsyncFunction(functionExpression) {
32 return functionExpression.params.length === 1;
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000033 }
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000034
Tim van der Lippe16aca392020-11-13 11:37:13 +000035 function findParamInScope(paramName, scope) {
36 return find(function (variable) {
37 return variable.name === paramName && variable.defs[0].type === 'Parameter';
38 }, scope.variables);
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000039 }
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000040
Tim van der Lippe16aca392020-11-13 11:37:13 +000041 function isReferenceHandled(reference) {
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010042 const parent = context.getSourceCode().getNodeByRangeIndex(reference.identifier.range[0]).parent;
Tim van der Lippe16aca392020-11-13 11:37:13 +000043
44 return parent.type === 'CallExpression';
45 }
46
47 function hasHandledReferences(references) {
48 return references.some(isReferenceHandled);
49 }
50
51 function checkAsyncMochaFunction(functionExpression) {
52 const scope = context.getScope();
53 const callback = functionExpression.params[0];
54 const callbackName = callback.name;
55 const callbackVariable = findParamInScope(callbackName, scope);
56
57 if (callbackVariable && !hasHandledReferences(callbackVariable.references)) {
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010058 context.report({
59 node: callback,
60 message: 'Expected "{{name}}" callback to be handled.', data: { name: callbackName }
61 });
Tim van der Lippe16aca392020-11-13 11:37:13 +000062 }
63 }
64
65 function check(node) {
66 if (astUtils.hasParentMochaFunctionCall(node, { modifiers: modifiersToCheck }) && isAsyncFunction(node)) {
67 checkAsyncMochaFunction(node);
68 }
69 }
70
71 return {
72 FunctionExpression: check,
73 ArrowFunctionExpression: check
74 };
75 }
Jack Franklin8b9aa2f2020-02-12 16:35:15 +000076};