Socket
Socket
Sign inDemoInstall

eslint-plugin-jest

Package Overview
Dependencies
Maintainers
11
Versions
325
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-jest - npm Package Compare versions

Comparing version 26.7.0 to 26.8.0

2

lib/rules/max-expects.js

@@ -59,3 +59,3 @@ "use strict";

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['expect'])) {
return;

@@ -62,0 +62,0 @@ }

@@ -44,3 +44,5 @@ "use strict";

CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;

@@ -51,10 +53,5 @@ }

matcher
} = (0, _utils.parseExpectCall)(node);
} = jestFnCall;
const alias = (0, _utils.getAccessorValue)(matcher);
if (!matcher) {
return;
}
const alias = matcher.name;
if (alias in methodNames) {

@@ -68,4 +65,4 @@ const canonical = methodNames[alias];

},
node: matcher.node.property,
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher.node.property, canonical)]
node: matcher,
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher, canonical)]
});

@@ -72,0 +69,0 @@ }

@@ -50,3 +50,9 @@ "use strict";

CallExpression(node) {
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) {
var _parseJestFnCall;
const {
type: jestFnCallType
} = (_parseJestFnCall = (0, _utils2.parseJestFnCall)(node, context)) !== null && _parseJestFnCall !== void 0 ? _parseJestFnCall : {};
if (jestFnCallType === 'test') {
inTestCase = true;

@@ -59,3 +65,3 @@ }

if (inTestCase && (0, _utils2.isExpectCall)(node) && conditionalDepth > 0) {
if (inTestCase && jestFnCallType === 'expect' && conditionalDepth > 0) {
context.report({

@@ -67,3 +73,3 @@ messageId: 'conditionalExpect',

if (inPromiseCatch && (0, _utils2.isExpectCall)(node)) {
if (inPromiseCatch && jestFnCallType === 'expect') {
context.report({

@@ -70,0 +76,0 @@ messageId: 'conditionalExpect',

@@ -31,19 +31,11 @@ "use strict";

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
return;
}
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
const {
matcher
} = (0, _utils2.parseExpectCall)(node);
if (!matcher) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)) {
var _matcher$arguments;
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes((0, _utils2.getAccessorValue)(jestFnCall.matcher))) {
// Check all since the optional 'propertyMatchers' argument might be present
(_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.forEach(argument => {
jestFnCall.args.forEach(argument => {
if (argument.type === _utils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) {

@@ -50,0 +42,0 @@ context.report({

@@ -29,3 +29,3 @@ "use strict";

if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && (0, _utils2.isExpectMember)(node.expression.left)) {
if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && node.expression.left.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.expression.left.property)) {
const fileName = context.getFilename();

@@ -104,20 +104,12 @@ const allowedSnapshotsInFile = allowedSnapshots[fileName];

CallExpression(node) {
var _matcher$arguments;
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if (!(0, _utils2.isExpectCall)(node)) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
const {
matcher
} = (0, _utils2.parseExpectCall)(node);
if ((matcher === null || matcher === void 0 ? void 0 : matcher.node.parent.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name) && (_matcher$arguments = matcher.arguments) !== null && _matcher$arguments !== void 0 && _matcher$arguments.length) {
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes((0, _utils2.getAccessorValue)(jestFnCall.matcher)) && jestFnCall.args.length) {
var _options$inlineMaxSiz;
reportOnViolation(context, matcher.arguments[0], { ...options,
reportOnViolation(context, jestFnCall.args[0], { ...options,
maxSize: (_options$inlineMaxSiz = options.inlineMaxSize) !== null && _options$inlineMaxSiz !== void 0 ? _options$inlineMaxSiz : options.maxSize

@@ -124,0 +116,0 @@ });

@@ -33,61 +33,39 @@ "use strict";

create(context, [restrictedChains]) {
const reportIfRestricted = (loc, chain) => {
if (!(chain in restrictedChains)) {
return false;
}
const message = restrictedChains[chain];
context.report({
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
data: {
message,
chain
},
loc
});
return true;
};
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
const {
matcher,
modifier
} = (0, _utils.parseExpectCall)(node);
const permutations = [jestFnCall.members];
if (matcher && reportIfRestricted(matcher.node.property.loc, matcher.name)) {
return;
if (jestFnCall.members.length > 2) {
permutations.push([jestFnCall.members[0], jestFnCall.members[1]]);
permutations.push([jestFnCall.members[1], jestFnCall.members[2]]);
}
if (modifier) {
if (reportIfRestricted(modifier.node.property.loc, modifier.name)) {
return;
}
if (modifier.negation) {
if (reportIfRestricted(modifier.negation.property.loc, 'not') || reportIfRestricted({
start: modifier.node.property.loc.start,
end: modifier.negation.property.loc.end
}, `${modifier.name}.not`)) {
return;
}
}
if (jestFnCall.members.length > 1) {
permutations.push(...jestFnCall.members.map(nod => [nod]));
}
if (matcher && modifier) {
let chain = modifier.name;
for (const permutation of permutations) {
const chain = permutation.map(nod => (0, _utils.getAccessorValue)(nod)).join('.');
if (modifier.negation) {
chain += '.not';
if (chain in restrictedChains) {
const message = restrictedChains[chain];
context.report({
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
data: {
message,
chain
},
loc: {
start: permutation[0].loc.start,
end: permutation[permutation.length - 1].loc.end
}
});
break;
}
chain += `.${matcher.name}`;
reportIfRestricted({
start: modifier.node.property.loc.start,
end: matcher.node.property.loc.end
}, chain);
}

@@ -94,0 +72,0 @@ }

@@ -76,7 +76,11 @@ "use strict";

const isTestBlock = node => (0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) || isCustomTestBlockFunction(node);
return {
CallExpression(node) {
if ((0, _utils2.isExpectCall)(node)) {
var _parseJestFnCall;
const {
type: jestFnCallType
} = (_parseJestFnCall = (0, _utils2.parseJestFnCall)(node, context)) !== null && _parseJestFnCall !== void 0 ? _parseJestFnCall : {};
if (jestFnCallType === 'expect') {
const parent = callStack[callStack.length - 1];

@@ -94,3 +98,3 @@

if (isTestBlock(node)) {
if (jestFnCallType === 'test' || isCustomTestBlockFunction(node)) {
callStack.push('test');

@@ -107,3 +111,3 @@ }

if (top === 'test' && isTestBlock(node) && node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || top === 'template' && node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
if (top === 'test' && ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) || isCustomTestBlockFunction(node)) && node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || top === 'template' && node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
callStack.pop();

@@ -110,0 +114,0 @@ }

@@ -29,22 +29,24 @@ "use strict";

CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
if (jestFnCall.modifiers.some(nod => (0, _utils.getAccessorValue)(nod) === 'not')) {
return;
}
const {
modifier,
matcher
} = (0, _utils.parseExpectCall)(node);
} = jestFnCall;
const matcherName = (0, _utils.getAccessorValue)(matcher);
if (!matcher || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.not || modifier !== null && modifier !== void 0 && modifier.negation) {
return;
}
if (['toBeCalled', 'toHaveBeenCalled'].includes(matcher.name)) {
if (['toBeCalled', 'toHaveBeenCalled'].includes(matcherName)) {
context.report({
data: {
matcherName: matcher.name
matcherName
},
messageId: 'preferCalledWith',
node: matcher.node.property
node: matcher
});

@@ -51,0 +53,0 @@ }

@@ -12,18 +12,2 @@ "use strict";

const isBooleanLiteral = node => node.type === _utils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a boolean literal as the sole argument.
*
* @example javascript
* toBe(true);
* toEqual(false);
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedBooleanEqualityMatcher}
*/
const isBooleanEqualityMatcher = matcher => (0, _utils2.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils2.followTypeAssertionChain)(matcher.arguments[0]));
const isString = node => {

@@ -95,3 +79,5 @@ return (0, _utils2.isStringNode)(node) || node.type === _utils.AST_NODE_TYPES.TemplateLiteral;

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect' || jestFnCall.args.length === 0) {
return;

@@ -101,19 +87,26 @@ }

const {
expect: {
arguments: [comparison],
range: [, expectCallEnd]
},
matcher,
modifier
} = (0, _utils2.parseExpectCall)(node);
parent: expect
} = jestFnCall.head.node;
if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _utils.AST_NODE_TYPES.BinaryExpression || isComparingToString(comparison) || !isBooleanEqualityMatcher(matcher)) {
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
const negation = modifier !== null && modifier !== void 0 && modifier.negation ? {
node: modifier.negation
} : (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.not ? modifier : null;
const preferredMatcher = determineMatcher(comparison.operator, (0, _utils2.followTypeAssertionChain)(matcher.arguments[0]).value === !!negation);
const {
arguments: [comparison],
range: [, expectCallEnd]
} = expect;
const {
matcher
} = jestFnCall;
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
if ((comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _utils.AST_NODE_TYPES.BinaryExpression || isComparingToString(comparison) || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg)) {
return;
}
const [modifier] = jestFnCall.modifiers;
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
const preferredMatcher = determineMatcher(comparison.operator, matcherArg.value === hasNot);
if (!preferredMatcher) {

@@ -127,7 +120,7 @@ return;

const modifierText = modifier && (modifier === null || modifier === void 0 ? void 0 : modifier.node) !== (negation === null || negation === void 0 ? void 0 : negation.node) ? `.${modifier.name}` : '';
const modifierText = modifier && (0, _utils2.getAccessorValue)(modifier) !== 'not' ? `.${(0, _utils2.getAccessorValue)(modifier)}` : '';
return [// replace the comparison argument with the left-hand side of the comparison
fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], `${modifierText}.${preferredMatcher}`), // replace the matcher argument with the right-hand side of the comparison
fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], `${modifierText}.${preferredMatcher}`), // replace the matcher argument with the right-hand side of the comparison
fixer.replaceText(matcherArg, sourceCode.getText(comparison.right))];
},

@@ -139,3 +132,3 @@

},
node: matcher.node.property
node: matcher
});

@@ -142,0 +135,0 @@ }

@@ -12,18 +12,2 @@ "use strict";

const isBooleanLiteral = node => node.type === _utils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a boolean literal as the sole argument.
*
* @example javascript
* toBe(true);
* toEqual(false);
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedBooleanEqualityMatcher}
*/
const isBooleanEqualityMatcher = matcher => (0, _utils2.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils2.followTypeAssertionChain)(matcher.arguments[0]));
var _default = (0, _utils2.createRule)({

@@ -51,3 +35,5 @@ name: __filename,

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect' || jestFnCall.args.length === 0) {
return;

@@ -57,21 +43,28 @@ }

const {
expect: {
arguments: [comparison],
range: [, expectCallEnd]
},
matcher,
modifier
} = (0, _utils2.parseExpectCall)(node);
parent: expect
} = jestFnCall.head.node;
if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _utils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== '===' && comparison.operator !== '!==' || !isBooleanEqualityMatcher(matcher)) {
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
const matcherValue = (0, _utils2.followTypeAssertionChain)(matcher.arguments[0]).value;
const negation = modifier !== null && modifier !== void 0 && modifier.negation ? {
node: modifier.negation
} : (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.not ? modifier : null; // we need to negate the expectation if the current expected
const {
arguments: [comparison],
range: [, expectCallEnd]
} = expect;
const {
matcher
} = jestFnCall;
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
if ((comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _utils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== '===' && comparison.operator !== '!==' || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg)) {
return;
}
const matcherValue = matcherArg.value;
const [modifier] = jestFnCall.modifiers;
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not'); // we need to negate the expectation if the current expected
// value is itself negated by the "not" modifier
const addNotModifier = (comparison.operator === '!==' ? !matcherValue : matcherValue) === !!negation;
const addNotModifier = (comparison.operator === '!==' ? !matcherValue : matcherValue) === hasNot;

@@ -81,3 +74,3 @@ const buildFixer = equalityMatcher => fixer => {

let modifierText = modifier && (modifier === null || modifier === void 0 ? void 0 : modifier.node) !== (negation === null || negation === void 0 ? void 0 : negation.node) ? `.${modifier.name}` : '';
let modifierText = modifier && (0, _utils2.getAccessorValue)(modifier) !== 'not' ? `.${(0, _utils2.getAccessorValue)(modifier)}` : '';

@@ -90,4 +83,4 @@ if (addNotModifier) {

fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], `${modifierText}.${equalityMatcher}`), // replace the matcher argument with the right-hand side of the comparison
fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], `${modifierText}.${equalityMatcher}`), // replace the matcher argument with the right-hand side of the comparison
fixer.replaceText(matcherArg, sourceCode.getText(comparison.right))];
};

@@ -104,3 +97,3 @@

})),
node: matcher.node.property
node: matcher
});

@@ -107,0 +100,0 @@ }

@@ -119,3 +119,5 @@ "use strict";

CallExpression(node) {
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'test') {
inTestCaseCall = true;

@@ -125,3 +127,3 @@ return;

if ((0, _utils2.isExpectCall)(node) && inTestCaseCall) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect' && inTestCaseCall) {
if (inForLoop) {

@@ -128,0 +130,0 @@ hasExpectInLoop = true;

@@ -30,11 +30,25 @@ "use strict";

CallExpression(node) {
const [awaitNode] = node.arguments;
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((0, _utils2.isExpectCall)(node) && (awaitNode === null || awaitNode === void 0 ? void 0 : awaitNode.type) === _utils.AST_NODE_TYPES.AwaitExpression) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
const {
parent
} = jestFnCall.head.node;
if ((parent === null || parent === void 0 ? void 0 : parent.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
const [awaitNode] = parent.arguments;
if ((awaitNode === null || awaitNode === void 0 ? void 0 : awaitNode.type) === _utils.AST_NODE_TYPES.AwaitExpression) {
context.report({
node: node.arguments[0],
node: awaitNode,
messageId: 'expectResolves',
fix(fixer) {
return [fixer.insertTextBefore(node, 'await '), fixer.removeRange([awaitNode.range[0], awaitNode.argument.range[0]]), fixer.insertTextAfter(node, '.resolves')];
return [fixer.insertTextBefore(parent, 'await '), fixer.removeRange([awaitNode.range[0], awaitNode.argument.range[0]]), fixer.insertTextAfter(parent, '.resolves')];
}

@@ -41,0 +55,0 @@

@@ -11,9 +11,6 @@ "use strict";

const snapshotMatchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'];
const snapshotMatcherNames = snapshotMatchers;
const isSnapshotMatcher = matcher => {
return snapshotMatchers.includes(matcher.name);
};
const isSnapshotMatcherWithoutHint = matcher => {
if (!matcher.arguments || matcher.arguments.length === 0) {
const isSnapshotMatcherWithoutHint = expectFnCall => {
if (expectFnCall.args.length === 0) {
return true;

@@ -23,4 +20,4 @@ } // this matcher only supports one argument which is the hint

if (matcher.name !== 'toMatchSnapshot') {
return matcher.arguments.length !== 1;
if (!(0, _utils.isSupportedAccessor)(expectFnCall.matcher, 'toMatchSnapshot')) {
return expectFnCall.args.length !== 1;
} // if we're being passed two arguments,

@@ -30,7 +27,7 @@ // the second one should be the hint

if (matcher.arguments.length === 2) {
if (expectFnCall.args.length === 2) {
return false;
}
const [arg] = matcher.arguments; // the first argument to `toMatchSnapshot` can be _either_ a snapshot hint or
const [arg] = expectFnCall.args; // the first argument to `toMatchSnapshot` can be _either_ a snapshot hint or
// an object with asymmetric matchers, so we can't just assume that the first

@@ -73,3 +70,3 @@ // argument is a hint when it's by itself.

messageId: 'missingHint',
node: snapshotMatcher.node.property
node: snapshotMatcher.matcher
});

@@ -122,20 +119,20 @@ }

CallExpression(node) {
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe', 'test'])) {
depths.push(expressionDepth);
expressionDepth = 0;
}
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if (!(0, _utils.isExpectCall)(node)) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'describe' || (jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'test') {
depths.push(expressionDepth);
expressionDepth = 0;
}
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
const matcherName = (0, _utils.getAccessorValue)(jestFnCall.matcher);
if (!matcher || !isSnapshotMatcher(matcher)) {
if (!snapshotMatcherNames.includes(matcherName)) {
return;
}
snapshotMatchers.push(matcher);
snapshotMatchers.push(jestFnCall);
}

@@ -142,0 +139,0 @@

@@ -32,3 +32,5 @@ "use strict";

CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;

@@ -39,11 +41,11 @@ }

matcher
} = (0, _utils.parseExpectCall)(node);
} = jestFnCall;
if (matcher && (0, _utils.isParsedEqualityMatcherCall)(matcher, _utils.EqualityMatcher.toEqual)) {
if ((0, _utils.isSupportedAccessor)(matcher, 'toEqual')) {
context.report({
messageId: 'useToStrictEqual',
node: matcher.node.property,
node: matcher,
suggest: [{
messageId: 'suggestReplaceWithStrictEqual',
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher.node.property, _utils.EqualityMatcher.toStrictEqual)]
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher, _utils.EqualityMatcher.toStrictEqual)]
}]

@@ -50,0 +52,0 @@ });

@@ -19,8 +19,8 @@ "use strict";

const isNullEqualityMatcher = matcher => isNullLiteral(getFirstArgument(matcher));
const isNullEqualityMatcher = expectFnCall => isNullLiteral((0, _utils2.getFirstMatcherArg)(expectFnCall));
const isFirstArgumentIdentifier = (matcher, name) => (0, _utils2.isIdentifier)(getFirstArgument(matcher), name);
const isFirstArgumentIdentifier = (expectFnCall, name) => (0, _utils2.isIdentifier)((0, _utils2.getFirstMatcherArg)(expectFnCall), name);
const shouldUseToBe = matcher => {
const firstArg = getFirstArgument(matcher);
const shouldUseToBe = expectFnCall => {
const firstArg = (0, _utils2.getFirstMatcherArg)(expectFnCall);

@@ -36,8 +36,3 @@ if (firstArg.type === _utils.AST_NODE_TYPES.Literal) {

const getFirstArgument = matcher => {
return (0, _utils2.followTypeAssertionChain)(matcher.arguments[0]);
};
const reportPreferToBe = (context, whatToBe, matcher, modifier) => {
const modifierNode = (modifier === null || modifier === void 0 ? void 0 : modifier.negation) || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.not && (modifier === null || modifier === void 0 ? void 0 : modifier.node);
const reportPreferToBe = (context, whatToBe, expectFnCall, modifierNode) => {
context.report({

@@ -47,12 +42,12 @@ messageId: `useToBe${whatToBe}`,

fix(fixer) {
var _matcher$arguments;
var _expectFnCall$args;
const fixes = [(0, _utils2.replaceAccessorFixer)(fixer, matcher.node.property, `toBe${whatToBe}`)];
const fixes = [(0, _utils2.replaceAccessorFixer)(fixer, expectFnCall.matcher, `toBe${whatToBe}`)];
if ((_matcher$arguments = matcher.arguments) !== null && _matcher$arguments !== void 0 && _matcher$arguments.length && whatToBe !== '') {
fixes.push(fixer.remove(matcher.arguments[0]));
if ((_expectFnCall$args = expectFnCall.args) !== null && _expectFnCall$args !== void 0 && _expectFnCall$args.length && whatToBe !== '') {
fixes.push(fixer.remove(expectFnCall.args[0]));
}
if (modifierNode) {
fixes.push(fixer.removeRange([modifierNode.property.range[0] - 1, modifierNode.property.range[1]]));
fixes.push(fixer.removeRange([modifierNode.range[0] - 1, modifierNode.range[1]]));
}

@@ -63,3 +58,3 @@

node: matcher.node.property
node: expectFnCall.matcher
});

@@ -92,43 +87,39 @@ };

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
const {
matcher,
modifier
} = (0, _utils2.parseExpectCall)(node);
const matcherName = (0, _utils2.getAccessorValue)(jestFnCall.matcher);
const notModifier = jestFnCall.modifiers.find(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
if (!matcher) {
if (notModifier && ['toBeUndefined', 'toBeDefined'].includes(matcherName)) {
reportPreferToBe(context, matcherName === 'toBeDefined' ? 'Undefined' : 'Defined', jestFnCall, notModifier);
return;
}
if (((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.not || modifier !== null && modifier !== void 0 && modifier.negation) && ['toBeUndefined', 'toBeDefined'].includes(matcher.name)) {
reportPreferToBe(context, matcher.name === 'toBeDefined' ? 'Undefined' : 'Defined', matcher, modifier);
if (!_utils2.EqualityMatcher.hasOwnProperty(matcherName) || jestFnCall.args.length === 0) {
return;
}
if (!(0, _utils2.isParsedEqualityMatcherCall)(matcher)) {
if (isNullEqualityMatcher(jestFnCall)) {
reportPreferToBe(context, 'Null', jestFnCall);
return;
}
if (isNullEqualityMatcher(matcher)) {
reportPreferToBe(context, 'Null', matcher);
if (isFirstArgumentIdentifier(jestFnCall, 'undefined')) {
const name = notModifier ? 'Defined' : 'Undefined';
reportPreferToBe(context, name, jestFnCall, notModifier);
return;
}
if (isFirstArgumentIdentifier(matcher, 'undefined')) {
const name = (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.not || modifier !== null && modifier !== void 0 && modifier.negation ? 'Defined' : 'Undefined';
reportPreferToBe(context, name, matcher, modifier);
if (isFirstArgumentIdentifier(jestFnCall, 'NaN')) {
reportPreferToBe(context, 'NaN', jestFnCall);
return;
}
if (isFirstArgumentIdentifier(matcher, 'NaN')) {
reportPreferToBe(context, 'NaN', matcher);
return;
if (shouldUseToBe(jestFnCall) && matcherName !== _utils2.EqualityMatcher.toBe) {
reportPreferToBe(context, '', jestFnCall);
}
if (shouldUseToBe(matcher) && matcher.name !== _utils2.EqualityMatcher.toBe) {
reportPreferToBe(context, '', matcher);
}
}

@@ -135,0 +126,0 @@

@@ -12,19 +12,3 @@ "use strict";

const isBooleanLiteral = node => node.type === _utils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a boolean literal as the sole argument.
*
* @example javascript
* toBe(true);
* toEqual(false);
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedBooleanEqualityMatcher}
*/
const isBooleanEqualityMatcher = matcher => (0, _utils2.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils2.followTypeAssertionChain)(matcher.arguments[0]));
/**
* Checks if the given `node` is a `CallExpression` representing the calling

@@ -37,3 +21,3 @@ * of an `includes`-like method that can be 'fixed' (using `toContain`).

*/
const isFixableIncludesCallExpression = node => node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils2.hasOnlyOneArgument)(node); // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
const isFixableIncludesCallExpression = node => node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils2.hasOnlyOneArgument)(node) && node.arguments[0].type !== _utils.AST_NODE_TYPES.SpreadElement; // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)

@@ -61,3 +45,5 @@

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect' || jestFnCall.args.length === 0) {
return;

@@ -67,14 +53,23 @@ }

const {
expect: {
arguments: [includesCall],
range: [, expectCallEnd]
},
matcher,
modifier
} = (0, _utils2.parseExpectCall)(node);
parent: expect
} = jestFnCall.head.node;
if (!matcher || !includesCall || modifier && modifier.name !== _utils2.ModifierName.not || !isBooleanEqualityMatcher(matcher) || !isFixableIncludesCallExpression(includesCall)) {
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
const {
arguments: [includesCall],
range: [, expectCallEnd]
} = expect;
const {
matcher
} = jestFnCall;
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
if (!includesCall || matcherArg.type === _utils.AST_NODE_TYPES.SpreadElement || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
return;
}
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
context.report({

@@ -85,11 +80,11 @@ fix(fixer) {

const addNotModifier = (0, _utils2.followTypeAssertionChain)(matcher.arguments[0]).value === !!modifier;
const addNotModifier = matcherArg.value === hasNot;
return [// remove the "includes" call entirely
fixer.removeRange([includesCall.callee.property.range[0] - 1, includesCall.range[1]]), // replace the current matcher with "toContain", adding "not" if needed
fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], addNotModifier ? `.${_utils2.ModifierName.not}.toContain` : '.toContain'), // replace the matcher argument with the value from the "includes"
fixer.replaceText(matcher.arguments[0], sourceCode.getText(includesCall.arguments[0]))];
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], addNotModifier ? `.${_utils2.ModifierName.not}.toContain` : '.toContain'), // replace the matcher argument with the value from the "includes"
fixer.replaceText(jestFnCall.args[0], sourceCode.getText(includesCall.arguments[0]))];
},
messageId: 'useToContain',
node: matcher.node.property
node: matcher
});

@@ -96,0 +91,0 @@ }

@@ -32,3 +32,5 @@ "use strict";

CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;

@@ -38,9 +40,15 @@ }

const {
expect: {
arguments: [argument]
},
parent: expect
} = jestFnCall.head.node;
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
const [argument] = expect.arguments;
const {
matcher
} = (0, _utils2.parseExpectCall)(node);
} = jestFnCall;
if (!matcher || !(0, _utils2.isParsedEqualityMatcherCall)(matcher) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'length')) {
if (!_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'length')) {
return;

@@ -53,7 +61,7 @@ }

fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]), // replace the current matcher with "toHaveLength"
fixer.replaceTextRange([matcher.node.object.range[1], matcher.node.range[1]], '.toHaveLength')];
fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], '.toHaveLength')];
},
messageId: 'useToHaveLength',
node: matcher.node.property
node: matcher
});

@@ -60,0 +68,0 @@ }

@@ -29,5 +29,5 @@ "use strict";

CallExpression(node) {
var _matcher$arguments;
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
if (!(0, _utils.isExpectCall)(node)) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;

@@ -37,7 +37,7 @@ }

const {
matcher,
modifier
} = (0, _utils.parseExpectCall)(node);
matcher
} = jestFnCall;
const matcherName = (0, _utils.getAccessorValue)(matcher);
if ((matcher === null || matcher === void 0 ? void 0 : (_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.length) === 0 && ['toThrow', 'toThrowError'].includes(matcher.name) && (!modifier || !(modifier.name === _utils.ModifierName.not || modifier.negation))) {
if (jestFnCall.args.length === 0 && ['toThrow', 'toThrowError'].includes(matcherName) && !jestFnCall.modifiers.some(nod => (0, _utils.getAccessorValue)(nod) === 'not')) {
// Look for `toThrow` calls with no arguments.

@@ -47,5 +47,5 @@ context.report({

data: {
matcherName: matcher.name
matcherName
},
node: matcher.node.property
node: matcher
});

@@ -52,0 +52,0 @@ }

@@ -8,22 +8,8 @@ "use strict";

var _utils = require("./utils");
var _utils = require("@typescript-eslint/utils");
var _utils2 = require("./utils");
const toThrowMatchers = ['toThrow', 'toThrowError', 'toThrowErrorMatchingSnapshot', 'toThrowErrorMatchingInlineSnapshot'];
const isJestExpectToThrowCall = node => {
if (!(0, _utils.isExpectCall)(node)) {
return false;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher) {
return false;
}
return !toThrowMatchers.includes(matcher.name);
};
const baseRule = (() => {

@@ -56,3 +42,3 @@ try {

var _default = (0, _utils.createRule)({
var _default = (0, _utils2.createRule)({
defaultOptions: [{

@@ -89,20 +75,19 @@ ignoreStatic: false

let inExpectToThrowCall = false;
return { ...baseSelectors,
CallExpression(node) {
inExpectToThrowCall = isJestExpectToThrowCall(node);
},
MemberExpression(node) {
var _node$parent, _baseSelectors$Member;
'CallExpression:exit'(node) {
if (inExpectToThrowCall && isJestExpectToThrowCall(node)) {
inExpectToThrowCall = false;
}
},
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) === _utils.AST_NODE_TYPES.CallExpression) {
const jestFnCall = (0, _utils2.parseJestFnCall)((0, _utils2.findTopMostCallExpression)(node.parent), context);
MemberExpression(node) {
var _baseSelectors$Member;
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect') {
const {
matcher
} = jestFnCall;
if (inExpectToThrowCall) {
return;
if (!toThrowMatchers.includes((0, _utils2.getAccessorValue)(matcher))) {
return;
}
}
}

@@ -109,0 +94,0 @@

@@ -70,15 +70,2 @@ "use strict";

});
});
var _parseExpectCall = require("./parseExpectCall");
Object.keys(_parseExpectCall).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _parseExpectCall[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _parseExpectCall[key];
}
});
});

@@ -6,5 +6,5 @@ "use strict";

});
exports.createRule = exports.TestCaseName = exports.HookName = exports.DescribeAlias = void 0;
exports.getFirstMatcherArg = exports.findTopMostCallExpression = exports.createRule = exports.TestCaseName = exports.ModifierName = exports.HookName = exports.EqualityMatcher = exports.DescribeAlias = void 0;
exports.getNodeName = getNodeName;
exports.replaceAccessorFixer = exports.isFunction = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = void 0;
exports.replaceAccessorFixer = exports.isFunction = exports.isBooleanLiteral = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = void 0;

@@ -19,2 +19,4 @@ var _path = require("path");

var _followTypeAssertionChain = require("./followTypeAssertionChain");
var _parseJestFnCall = require("./parseJestFnCall");

@@ -75,2 +77,20 @@

let ModifierName;
exports.ModifierName = ModifierName;
(function (ModifierName) {
ModifierName["not"] = "not";
ModifierName["rejects"] = "rejects";
ModifierName["resolves"] = "resolves";
})(ModifierName || (exports.ModifierName = ModifierName = {}));
let EqualityMatcher;
exports.EqualityMatcher = EqualityMatcher;
(function (EqualityMatcher) {
EqualityMatcher["toBe"] = "toBe";
EqualityMatcher["toEqual"] = "toEqual";
EqualityMatcher["toStrictEqual"] = "toStrictEqual";
})(EqualityMatcher || (exports.EqualityMatcher = EqualityMatcher = {}));
const joinNames = (a, b) => a && b ? `${a}.${b}` : null;

@@ -123,2 +143,43 @@

exports.replaceAccessorFixer = replaceAccessorFixer;
exports.replaceAccessorFixer = replaceAccessorFixer;
const findTopMostCallExpression = node => {
let topMostCallExpression = node;
let {
parent
} = node;
while (parent) {
if (parent.type === _utils.AST_NODE_TYPES.CallExpression) {
topMostCallExpression = parent;
parent = parent.parent;
continue;
}
if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) {
break;
}
parent = parent.parent;
}
return topMostCallExpression;
};
exports.findTopMostCallExpression = findTopMostCallExpression;
const isBooleanLiteral = node => node.type === _utils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
exports.isBooleanLiteral = isBooleanLiteral;
const getFirstMatcherArg = expectFnCall => {
const [firstArg] = expectFnCall.args;
if (firstArg.type === _utils.AST_NODE_TYPES.SpreadElement) {
return firstArg;
}
return (0, _followTypeAssertionChain.followTypeAssertionChain)(firstArg);
};
exports.getFirstMatcherArg = getFirstMatcherArg;

@@ -7,3 +7,3 @@ "use strict";

exports.getNodeChain = getNodeChain;
exports.scopeHasLocalReference = exports.parseJestFnCall = exports.isTypeOfJestFnCall = void 0;
exports.scopeHasLocalReference = exports.parseJestFnCallWithReason = exports.parseJestFnCall = exports.isTypeOfJestFnCall = void 0;

@@ -43,5 +43,6 @@ var _utils = require("@typescript-eslint/utils");

const determineJestFnType = name => {
// if (name === 'expect') {
// return 'expect';
// }
if (name === 'expect') {
return 'expect';
}
if (name === 'jest') {

@@ -86,23 +87,19 @@ return 'jest';

const parseJestFnCall = (node, context) => {
var _node$parent, _node$parent2, _resolved$original;
const jestFnCall = parseJestFnCallWithReason(node, context);
// ensure that we're at the "top" of the function call chain otherwise when
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
// the full chain is not a valid jest function call chain
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) === _utils.AST_NODE_TYPES.CallExpression || ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === _utils.AST_NODE_TYPES.MemberExpression) {
if (typeof jestFnCall === 'string') {
return null;
}
const chain = getNodeChain(node);
return jestFnCall;
};
if (!(chain !== null && chain !== void 0 && chain.length)) {
return null;
} // check that every link in the chain except the last is a member expression
exports.parseJestFnCall = parseJestFnCall;
const parseJestFnCallWithReason = (node, context) => {
var _resolved$original, _node$parent2, _node$parent3;
if (chain.slice(0, chain.length - 1).some(nod => {
var _nod$parent;
const chain = getNodeChain(node);
return ((_nod$parent = nod.parent) === null || _nod$parent === void 0 ? void 0 : _nod$parent.type) !== _utils.AST_NODE_TYPES.MemberExpression;
})) {
if (!(chain !== null && chain !== void 0 && chain.length)) {
return null;

@@ -133,18 +130,118 @@ }

if (name !== 'jest' && !ValidJestFnCallChains.includes(links.join('.'))) {
if (name !== 'jest' && name !== 'expect' && !ValidJestFnCallChains.includes(links.join('.'))) {
return null;
}
return {
const parsedJestFnCall = {
name,
type: determineJestFnType(name),
head: { ...resolved,
node: first
},
// every member node must have a member expression as their parent
// in order to be part of the call chain we're parsing
members: rest
};
const type = determineJestFnType(name);
if (type === 'expect') {
const result = parseJestExpectCall(parsedJestFnCall); // if the `expect` call chain is not valid, only report on the topmost node
// since all members in the chain are likely to get flagged for some reason
if (typeof result === 'string' && (0, _utils2.findTopMostCallExpression)(node) !== node) {
return null;
}
if (result === 'matcher-not-found') {
var _node$parent;
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) === _utils.AST_NODE_TYPES.MemberExpression) {
return 'matcher-not-called';
}
}
return result;
} // check that every link in the chain except the last is a member expression
if (chain.slice(0, chain.length - 1).some(nod => {
var _nod$parent;
return ((_nod$parent = nod.parent) === null || _nod$parent === void 0 ? void 0 : _nod$parent.type) !== _utils.AST_NODE_TYPES.MemberExpression;
})) {
return null;
} // ensure that we're at the "top" of the function call chain otherwise when
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
// the full chain is not a valid jest function call chain
if (((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === _utils.AST_NODE_TYPES.CallExpression || ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.type) === _utils.AST_NODE_TYPES.MemberExpression) {
return null;
}
return { ...parsedJestFnCall,
type
};
};
exports.parseJestFnCall = parseJestFnCall;
exports.parseJestFnCallWithReason = parseJestFnCallWithReason;
const findModifiersAndMatcher = members => {
const modifiers = [];
for (const member of members) {
var _member$parent, _member$parent$parent;
// check if the member is being called, which means it is the matcher
// (and also the end of the entire "expect" call chain)
if (((_member$parent = member.parent) === null || _member$parent === void 0 ? void 0 : _member$parent.type) === _utils.AST_NODE_TYPES.MemberExpression && ((_member$parent$parent = member.parent.parent) === null || _member$parent$parent === void 0 ? void 0 : _member$parent$parent.type) === _utils.AST_NODE_TYPES.CallExpression) {
return {
matcher: member,
args: member.parent.parent.arguments,
modifiers
};
} // otherwise, it should be a modifier
const name = (0, _utils2.getAccessorValue)(member);
if (modifiers.length === 0) {
// the first modifier can be any of the three modifiers
if (!_utils2.ModifierName.hasOwnProperty(name)) {
return 'modifier-unknown';
}
} else if (modifiers.length === 1) {
// the second modifier can only be "not"
if (name !== _utils2.ModifierName.not) {
return 'modifier-unknown';
}
const firstModifier = (0, _utils2.getAccessorValue)(modifiers[0]); // and the first modifier has to be either "resolves" or "rejects"
if (firstModifier !== _utils2.ModifierName.resolves && firstModifier !== _utils2.ModifierName.rejects) {
return 'modifier-unknown';
}
} else {
return 'modifier-unknown';
}
modifiers.push(member);
} // this will only really happen if there are no members
return 'matcher-not-found';
};
const parseJestExpectCall = typelessParsedJestFnCall => {
const modifiersAndMatcher = findModifiersAndMatcher(typelessParsedJestFnCall.members);
if (typeof modifiersAndMatcher === 'string') {
return modifiersAndMatcher;
}
return { ...typelessParsedJestFnCall,
type: 'expect',
...modifiersAndMatcher
};
};
const describeImportDefAsImport = def => {

@@ -151,0 +248,0 @@ if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {

@@ -32,25 +32,2 @@ "use strict";

const findTopMostCallExpression = node => {
let topMostCallExpression = node;
let {
parent
} = node;
while (parent) {
if (parent.type === _utils.AST_NODE_TYPES.CallExpression) {
topMostCallExpression = parent;
parent = parent.parent;
continue;
}
if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) {
break;
}
parent = parent.parent;
}
return topMostCallExpression;
};
const isTestCaseCallWithCallbackArg = (node, context) => {

@@ -177,3 +154,3 @@ const jestCallFn = (0, _utils2.parseJestFnCall)(node, context);

const isValueAwaitedOrReturned = (identifier, body) => {
const isValueAwaitedOrReturned = (identifier, body, context) => {
const {

@@ -202,9 +179,9 @@ name

const leftMostCall = getLeftMostCallExpression(node.expression);
const jestFnCall = (0, _utils2.parseJestFnCall)(node.expression, context);
if ((0, _utils2.isExpectCall)(leftMostCall) && leftMostCall.arguments.length > 0 && (0, _utils2.isIdentifier)(leftMostCall.arguments[0], name)) {
const {
modifier
} = (0, _utils2.parseExpectCall)(leftMostCall);
if ((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.resolves || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.rejects) {
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect' && leftMostCall.arguments.length > 0 && (0, _utils2.isIdentifier)(leftMostCall.arguments[0], name)) {
if (jestFnCall.members.some(m => {
const v = (0, _utils2.getAccessorValue)(m);
return v === _utils2.ModifierName.resolves || v === _utils2.ModifierName.rejects;
})) {
return true;

@@ -234,3 +211,3 @@ }

if (node.type === _utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body)) {
if (node.type === _utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body, context)) {
return true;

@@ -276,3 +253,3 @@ }

const isVariableAwaitedOrReturned = variable => {
const isVariableAwaitedOrReturned = (variable, context) => {
const body = findFirstBlockBodyUp(variable); // it's pretty much impossible for us to track destructuring assignments,

@@ -285,3 +262,3 @@ // so we return true to bailout gracefully

return isValueAwaitedOrReturned(variable.id, body);
return isValueAwaitedOrReturned(variable.id, body, context);
};

@@ -333,3 +310,3 @@

if (chains.length > 0 && (0, _utils2.isExpectCall)(node)) {
if (chains.length > 0 && (0, _utils2.isTypeOfJestFnCall)(node, context, ['expect'])) {
chains[0] = true;

@@ -366,3 +343,3 @@ }

parent
} = findTopMostCallExpression(node); // if we don't have a parent (which is technically impossible at runtime)
} = (0, _utils2.findTopMostCallExpression)(node); // if we don't have a parent (which is technically impossible at runtime)
// or our parent is not directly within the test case, we stop checking

@@ -379,3 +356,3 @@ // because we're most likely in the body of a function being defined

{
if (isVariableAwaitedOrReturned(parent)) {
if (isVariableAwaitedOrReturned(parent, context)) {
return;

@@ -389,3 +366,3 @@ }

{
if (parent.left.type === _utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent))) {
if (parent.left.type === _utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent), context)) {
return;

@@ -392,0 +369,0 @@ }

@@ -47,3 +47,3 @@ "use strict";

if (grandParentNode && grandParentNode.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils2.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) {
if (grandParentNode && grandParentNode.type === _utils.AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(grandParentNode.callee.property) && ['then', 'catch'].includes((0, _utils2.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) {
// Just in case `then`s are chained look one above.

@@ -68,4 +68,2 @@ return getParentIfThenified(grandParentNode);

const isNoAssertionsParentNode = node => node.type === _utils.AST_NODE_TYPES.ExpressionStatement || node.type === _utils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _utils.AST_NODE_TYPES.ExpressionStatement;
const promiseArrayExceptionKey = ({

@@ -89,3 +87,3 @@ start,

notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
modifierUnknown: 'Expect has an unknown modifier.',
matcherNotFound: 'Expect must have a corresponding matcher call.',

@@ -150,24 +148,75 @@ matcherNotCalled: 'Matchers must be called to assert.',

const findTopMostMemberExpression = node => {
let topMostMemberExpression = node;
let {
parent
} = node;
while (parent) {
if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) {
break;
}
topMostMemberExpression = parent;
parent = parent.parent;
}
return topMostMemberExpression;
};
return {
CallExpression(node) {
if (!(0, _utils2.isExpectCall)(node)) {
const jestFnCall = (0, _utils2.parseJestFnCallWithReason)(node, context);
if (typeof jestFnCall === 'string') {
var _node$parent3;
const reportingNode = ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.type) === _utils.AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node;
if (jestFnCall === 'matcher-not-found') {
context.report({
messageId: 'matcherNotFound',
node: reportingNode
});
return;
}
if (jestFnCall === 'matcher-not-called') {
context.report({
messageId: (0, _utils2.isSupportedAccessor)(reportingNode) && _utils2.ModifierName.hasOwnProperty((0, _utils2.getAccessorValue)(reportingNode)) ? 'matcherNotFound' : 'matcherNotCalled',
node: reportingNode
});
}
if (jestFnCall === 'modifier-unknown') {
context.report({
messageId: 'modifierUnknown',
node: reportingNode
});
return;
}
return;
} else if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') {
return;
}
const {
expect,
modifier,
matcher
} = (0, _utils2.parseExpectCall)(node);
parent: expect
} = jestFnCall.head.node;
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) {
return;
}
if (expect.arguments.length < minArgs) {
const expectLength = (0, _utils2.getAccessorValue)(expect.callee).length;
const expectLength = (0, _utils2.getAccessorValue)(jestFnCall.head.node).length;
const loc = {
start: {
column: node.loc.start.column + expectLength,
line: node.loc.start.line
column: expect.loc.start.column + expectLength,
line: expect.loc.start.line
},
end: {
column: node.loc.start.column + expectLength + 1,
line: node.loc.start.line
column: expect.loc.start.column + expectLength + 1,
line: expect.loc.start.line
}

@@ -181,3 +230,3 @@ };

},
node,
node: expect,
loc

@@ -193,3 +242,3 @@ });

end
} = expect.arguments[node.arguments.length - 1].loc;
} = expect.arguments[expect.arguments.length - 1].loc;
const loc = {

@@ -208,41 +257,14 @@ start,

},
node,
node: expect,
loc
});
} // something was called on `expect()`
if (!matcher) {
if (modifier) {
context.report({
messageId: 'matcherNotFound',
node: modifier.node.property
});
}
return;
}
if ((0, _utils2.isExpectMember)(matcher.node.parent)) {
context.report({
messageId: 'modifierUnknown',
data: {
modifierName: matcher.name
},
node: matcher.node.property
});
return;
}
const {
matcher
} = jestFnCall;
const parentNode = matcher.parent.parent;
const shouldBeAwaited = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) !== 'not') || asyncMatchers.includes((0, _utils2.getAccessorValue)(matcher));
if (!matcher.arguments) {
context.report({
messageId: 'matcherNotCalled',
node: matcher.node.property
});
}
const parentNode = matcher.node.parent;
const shouldBeAwaited = modifier && modifier.name !== _utils2.ModifierName.not || asyncMatchers.includes(matcher.name);
if (!parentNode.parent || !shouldBeAwaited) {
if (!(parentNode !== null && parentNode !== void 0 && parentNode.parent) || !shouldBeAwaited) {
return;

@@ -283,12 +305,2 @@ }

}
},
// nothing called on "expect()"
'CallExpression:exit'(node) {
if ((0, _utils2.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) {
context.report({
messageId: 'matcherNotFound',
node
});
}
}

@@ -295,0 +307,0 @@

{
"name": "eslint-plugin-jest",
"version": "26.7.0",
"version": "26.8.0",
"description": "ESLint rules for Jest",

@@ -24,12 +24,12 @@ "keywords": [

"build": "babel --extensions .js,.ts src --out-dir lib --copy-files && rimraf lib/__tests__ 'lib/**/__tests__'",
"_postinstall": "is-ci || husky install",
"lint": "eslint . --ignore-pattern '!.eslintrc.js' --ext js,ts",
"prepack": "rimraf lib && yarn build",
"prepublishOnly": "pinst --disable",
"prettier:check": "prettier --check 'docs/**/*.md' README.md '.github/**' package.json tsconfig.json src/globals.json .yarnrc.yml",
"prettier:write": "prettier --write 'docs/**/*.md' README.md '.github/**' package.json tsconfig.json src/globals.json .yarnrc.yml",
"postpublish": "pinst --enable",
"test": "jest",
"tools:regenerate-docs": "ts-node -T tools/regenerate-docs",
"typecheck": "tsc -p .",
"_postinstall": "is-ci || husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable"
"typecheck": "tsc -p ."
},

@@ -52,2 +52,19 @@ "commitlint": {

},
"release": {
"branches": [
"main",
{
"name": "next",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/git",
"@semantic-release/github"
]
},
"jest": {

@@ -108,3 +125,2 @@ "coverageThreshold": {

"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-eslint-config": "^2.0.0",
"eslint-plugin-eslint-plugin": "^5.0.0",

@@ -141,26 +157,6 @@ "eslint-plugin-import": "^2.25.1",

},
"packageManager": "yarn@3.2.2",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"release": {
"branches": [
"main",
{
"name": "next",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/git",
"@semantic-release/github"
]
},
"resolutions": {
"@typescript-eslint/experimental-utils": "^5.0.0"
},
"packageManager": "yarn@3.2.2"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc