eslint-plugin-mocha
Version:
Eslint rules for mocha.
178 lines • 7.28 kB
JavaScript
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