UNPKG

eslint-plugin-mocha

Version:

Eslint rules for mocha.

178 lines 7.28 kB
import { createMochaVisitors } from "../ast/mocha-visitors.js"; import { expectNodeRange } from "../ast/node-location.js"; import { expectCallExpression, expectMemberExpression, getParentNode, isFunction, isIdentifier, isLiteral, isMemberExpression } from "../ast/node-types.js"; import { asRuleNode } from "../ast/rule-node.js"; import { visitChildNodes } from "../ast/visit-child-nodes.js"; import { getRuleOption } from "../rule-options.js"; const optionSchema = { type: 'object', properties: { allowSkippedWithComment: { type: 'boolean' } }, additionalProperties: false }; const defaultOption = { allowSkippedWithComment: false }; function isCallbackMissing(node) { const [firstArgument] = node.arguments; return firstArgument?.type === 'Literal' && node.arguments.length === 1; } function isNamedSkipProperty(property) { return isIdentifier(property) && property.name === 'skip'; } function isLiteralSkipProperty(property) { return isLiteral(property) && property.value === 'skip'; } function isPendingMemberExpression(callee) { if (!isMemberExpression(callee)) { return false; } const { property } = callee; return !callee.computed && isNamedSkipProperty(property) || callee.computed && isLiteralSkipProperty(property); } function isThisSkipMemberExpression(callee) { return isPendingMemberExpression(callee) && callee.object.type === 'ThisExpression'; } function isThisSkipCall(node) { return isThisSkipMemberExpression(node.callee); } function fixPendingMemberExpression(fixer, sourceCode, callee) { const { object } = callee; const range = expectNodeRange(callee); return fixer.replaceTextRange(range, sourceCode.getText(object)); } function fixPendingTest(fixer, sourceCode, node) { if (isIdentifier(node.callee)) { return fixer.replaceText(node.callee, node.callee.name.slice(1)); } return fixPendingMemberExpression(fixer, sourceCode, expectMemberExpression(asRuleNode(node.callee))); } function hasKnownLocation(value) { return value.loc !== undefined && value.loc !== null; } function isAdjacentToComment(comment, node) { return hasKnownLocation(comment) && hasKnownLocation(node) && node.loc.start.line - comment.loc.end.line <= 1; } function isStandaloneLeadingComment(sourceCode, comment) { const tokenBeforeComment = sourceCode.getTokenBefore(comment, { includeComments: true }); return tokenBeforeComment === null || !hasKnownLocation(tokenBeforeComment) || tokenBeforeComment.loc.end.line < comment.loc.start.line; } function hasAdjacentLeadingComment(sourceCode, node) { const lastComment = sourceCode.getCommentsBefore(node).at(-1); return lastComment !== undefined && hasKnownLocation(lastComment) && isAdjacentToComment(lastComment, node) && isStandaloneLeadingComment(sourceCode, lastComment); } function shouldAllowSkippedWithComment(context, node, configuration) { return configuration.allowSkippedWithComment && hasAdjacentLeadingComment(context.sourceCode, asRuleNode(node)); } function canRemovePendingModifier(node) { return isIdentifier(node.callee) || isPendingMemberExpression(node.callee) && !isThisSkipMemberExpression(node.callee); } function visitThisSkipCalls(sourceCode, node, visitor) { if (node.type === 'CallExpression' && isThisSkipCall(node)) { visitor(node); } if (isFunction(node) && node.type !== 'ArrowFunctionExpression') { return; } visitChildNodes(sourceCode, node, function (childNode) { visitThisSkipCalls(sourceCode, childNode, visitor); }); } function reportSkipped(context, node, messageId) { const nodeToReport = node.callee.type === 'MemberExpression' ? node.callee.property : node.callee; const reportDescriptor = canRemovePendingModifier(node) ? { node: nodeToReport, messageId, suggest: [{ messageId: 'removePendingModifier', fix(fixer) { return fixPendingTest(fixer, context.sourceCode, node); } }] } : { node: nodeToReport, messageId }; context.report(reportDescriptor); } function checkPendingTestCase(context, visitorContext, configuration) { const node = expectCallExpression(visitorContext.node); if (isCallbackMissing(node)) { context.report({ node, messageId: 'unexpectedPendingTest' }); } else if (visitorContext.modifier === 'pending') { if (shouldAllowSkippedWithComment(context, node, configuration)) { return; } reportSkipped(context, node, configuration.allowSkippedWithComment ? 'unexpectedSkippedTestWithoutComment' : 'unexpectedPendingTest'); } } function checkPendingSuite(context, visitorContext, configuration) { const node = expectCallExpression(visitorContext.node); if (visitorContext.modifier === 'pending') { if (shouldAllowSkippedWithComment(context, node, configuration)) { return; } reportSkipped(context, node, configuration.allowSkippedWithComment ? 'unexpectedSkippedTestWithoutComment' : 'unexpectedPendingTest'); } } function checkPendingCallback(context, callbackNode, configuration) { const callbackParent = getParentNode(callbackNode); visitThisSkipCalls(context.sourceCode, callbackNode.body, function (thisSkipCall) { if (configuration.allowSkippedWithComment && (hasAdjacentLeadingComment(context.sourceCode, asRuleNode(thisSkipCall)) || hasAdjacentLeadingComment(context.sourceCode, callbackParent))) { return; } reportSkipped(context, thisSkipCall, configuration.allowSkippedWithComment ? 'unexpectedSkippedTestWithoutComment' : 'unexpectedPendingTest'); }); } export const noPendingTestsRule = { meta: { type: 'suggestion', docs: { description: 'Disallow pending tests', recommended: true, url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/documentation/rules/no-pending-tests.md' }, hasSuggestions: true, schema: [optionSchema], defaultOptions: [defaultOption], messages: { unexpectedPendingTest: 'Unexpected pending mocha test.', unexpectedSkippedTestWithoutComment: 'Unexpected skipped mocha test without a preceding comment.', removePendingModifier: 'Remove the pending modifier from this Mocha call.' }, languages: ['js/js'] }, create(context) { const configuration = getRuleOption(context); return createMochaVisitors(context, { testCase(visitorContext) { checkPendingTestCase(context, visitorContext, configuration); }, suite(visitorContext) { checkPendingSuite(context, visitorContext, configuration); }, anyTestEntityCallback(visitorContext) { checkPendingCallback(context, visitorContext.node, configuration); } }); } }; //# sourceMappingURL=no-pending-tests.js.map