Skip to content

Commit

Permalink
refactor: include matcher and modifiers in parsed expect fn call
Browse files Browse the repository at this point in the history
  • Loading branch information
G-Rath committed Jul 29, 2022
1 parent 28439b5 commit 407bc9c
Show file tree
Hide file tree
Showing 15 changed files with 52 additions and 86 deletions.
11 changes: 2 additions & 9 deletions src/rules/no-alias-methods.ts
Expand Up @@ -45,11 +45,7 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];

// if (!matcher) {
// return;
// }
const { matcher } = jestFnCall;

const alias = getAccessorValue(matcher);

Expand All @@ -58,10 +54,7 @@ export default createRule({

context.report({
messageId: 'replaceAlias',
data: {
alias,
canonical,
},
data: { alias, canonical },
node: matcher,
fix: fixer => [replaceAccessorFixer(fixer, matcher, canonical)],
});
Expand Down
4 changes: 1 addition & 3 deletions src/rules/no-interpolation-in-snapshots.ts
Expand Up @@ -25,13 +25,11 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];

if (
[
'toMatchInlineSnapshot',
'toThrowErrorMatchingInlineSnapshot',
].includes(getAccessorValue(matcher))
].includes(getAccessorValue(jestFnCall.matcher))
) {
// Check all since the optional 'propertyMatchers' argument might be present
jestFnCall.args.forEach(argument => {
Expand Down
4 changes: 1 addition & 3 deletions src/rules/no-large-snapshots.ts
Expand Up @@ -118,13 +118,11 @@ export default createRule<[RuleOptions], MessageId>({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];

if (
[
'toMatchInlineSnapshot',
'toThrowErrorMatchingInlineSnapshot',
].includes(getAccessorValue(matcher)) &&
].includes(getAccessorValue(jestFnCall.matcher)) &&
jestFnCall.args.length
) {
reportOnViolation(context, jestFnCall.args[0], {
Expand Down
8 changes: 2 additions & 6 deletions src/rules/prefer-called-with.ts
Expand Up @@ -25,15 +25,11 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];

if (
!matcher ||
jestFnCall.members.some(nod => getAccessorValue(nod) === 'not')
) {
if (jestFnCall.modifiers.some(nod => getAccessorValue(nod) === 'not')) {
return;
}

const { matcher } = jestFnCall;
const matcherName = getAccessorValue(matcher);

if (['toBeCalled', 'toHaveBeenCalled'].includes(matcherName)) {
Expand Down
9 changes: 4 additions & 5 deletions src/rules/prefer-comparison-matcher.ts
Expand Up @@ -88,11 +88,10 @@ export default createRule({
range: [, expectCallEnd],
} = expect;

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;
const matcherArg = getFirstMatcherArg(jestFnCall);

if (
!matcher ||
comparison?.type !== AST_NODE_TYPES.BinaryExpression ||
isComparingToString(comparison) ||
!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) ||
Expand All @@ -101,8 +100,8 @@ export default createRule({
return;
}

const [modifier] = jestFnCall.members;
const hasNot = jestFnCall.members.some(
const [modifier] = jestFnCall.modifiers;
const hasNot = jestFnCall.modifiers.some(
nod => getAccessorValue(nod) === 'not',
);

Expand All @@ -121,7 +120,7 @@ export default createRule({

// preserve the existing modifier if it's not a negation
const modifierText =
modifier !== matcher && getAccessorValue(modifier) !== 'not'
modifier && getAccessorValue(modifier) !== 'not'
? `.${getAccessorValue(modifier)}`
: '';

Expand Down
9 changes: 4 additions & 5 deletions src/rules/prefer-equality-matcher.ts
Expand Up @@ -47,11 +47,10 @@ export default createRule({
range: [, expectCallEnd],
} = expect;

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;
const matcherArg = getFirstMatcherArg(jestFnCall);

if (
!matcher ||
comparison?.type !== AST_NODE_TYPES.BinaryExpression ||
(comparison.operator !== '===' && comparison.operator !== '!==') ||
!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) ||
Expand All @@ -62,8 +61,8 @@ export default createRule({

const matcherValue = matcherArg.value;

const [modifier] = jestFnCall.members;
const hasNot = jestFnCall.members.some(
const [modifier] = jestFnCall.modifiers;
const hasNot = jestFnCall.modifiers.some(
nod => getAccessorValue(nod) === 'not',
);

Expand All @@ -80,7 +79,7 @@ export default createRule({

// preserve the existing modifier if it's not a negation
let modifierText =
modifier !== matcher && getAccessorValue(modifier) !== 'not'
modifier && getAccessorValue(modifier) !== 'not'
? `.${getAccessorValue(modifier)}`
: '';

Expand Down
18 changes: 5 additions & 13 deletions src/rules/prefer-snapshot-hint.ts
Expand Up @@ -12,14 +12,12 @@ const snapshotMatchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'];
const snapshotMatcherNames = snapshotMatchers;

const isSnapshotMatcherWithoutHint = (expectFnCall: ParsedExpectFnCall) => {
if (!expectFnCall.args || expectFnCall.args.length === 0) {
if (expectFnCall.args.length === 0) {
return true;
}

const matcher = expectFnCall.members[expectFnCall.members.length - 1];

// this matcher only supports one argument which is the hint
if (!isSupportedAccessor(matcher, 'toMatchSnapshot')) {
if (!isSupportedAccessor(expectFnCall.matcher, 'toMatchSnapshot')) {
return expectFnCall.args.length !== 1;
}

Expand Down Expand Up @@ -67,12 +65,9 @@ export default createRule<[('always' | 'multi')?], keyof typeof messages>({
const reportSnapshotMatchersWithoutHints = () => {
for (const snapshotMatcher of snapshotMatchers) {
if (isSnapshotMatcherWithoutHint(snapshotMatcher)) {
const matcher =
snapshotMatcher.members[snapshotMatcher.members.length - 1];

context.report({
messageId: 'missingHint',
node: matcher,
node: snapshotMatcher.matcher,
});
}
}
Expand Down Expand Up @@ -126,12 +121,9 @@ export default createRule<[('always' | 'multi')?], keyof typeof messages>({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const matcherName = getAccessorValue(jestFnCall.matcher);

if (
!matcher ||
!snapshotMatcherNames.includes(getAccessorValue(matcher))
) {
if (!snapshotMatcherNames.includes(matcherName)) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions src/rules/prefer-strict-equal.ts
Expand Up @@ -33,9 +33,9 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;

if (matcher && isSupportedAccessor(matcher, 'toEqual')) {
if (isSupportedAccessor(matcher, 'toEqual')) {
context.report({
messageId: 'useToStrictEqual',
node: matcher,
Expand Down
14 changes: 6 additions & 8 deletions src/rules/prefer-to-be.ts
Expand Up @@ -53,12 +53,12 @@ const reportPreferToBe = (
expectFnCall: ParsedExpectFnCall,
modifierNode?: AccessorNode,
) => {
const matcher = expectFnCall.members[expectFnCall.members.length - 1];

context.report({
messageId: `useToBe${whatToBe}`,
fix(fixer) {
const fixes = [replaceAccessorFixer(fixer, matcher, `toBe${whatToBe}`)];
const fixes = [
replaceAccessorFixer(fixer, expectFnCall.matcher, `toBe${whatToBe}`),
];

if (expectFnCall.args?.length && whatToBe !== '') {
fixes.push(fixer.remove(expectFnCall.args[0]));
Expand All @@ -72,7 +72,7 @@ const reportPreferToBe = (

return fixes;
},
node: matcher,
node: expectFnCall.matcher,
});
};

Expand Down Expand Up @@ -105,10 +105,8 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];

const matcherName = getAccessorValue(matcher);
const notModifier = jestFnCall.members.find(
const matcherName = getAccessorValue(jestFnCall.matcher);
const notModifier = jestFnCall.modifiers.find(
nod => getAccessorValue(nod) === 'not',
);

Expand Down
16 changes: 6 additions & 10 deletions src/rules/prefer-to-contain.ts
Expand Up @@ -70,24 +70,20 @@ export default createRule({
range: [, expectCallEnd],
} = expect;

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const arg = getFirstMatcherArg(jestFnCall);
const { matcher } = jestFnCall;
const matcherArg = getFirstMatcherArg(jestFnCall);

if (
!matcher ||
!includesCall ||
arg.type === AST_NODE_TYPES.SpreadElement ||
jestFnCall.members
.slice(0, -1)
.some(nod => getAccessorValue(nod) !== 'not') ||
matcherArg.type === AST_NODE_TYPES.SpreadElement ||
!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) ||
!isBooleanLiteral(arg) ||
!isBooleanLiteral(matcherArg) ||
!isFixableIncludesCallExpression(includesCall)
) {
return;
}

const hasNot = jestFnCall.members.some(
const hasNot = jestFnCall.modifiers.some(
nod => getAccessorValue(nod) === 'not',
);

Expand All @@ -97,7 +93,7 @@ export default createRule({

// we need to negate the expectation if the current expected
// value is itself negated by the "not" modifier
const addNotModifier = arg.value === hasNot;
const addNotModifier = matcherArg.value === hasNot;

return [
// remove the "includes" call entirely
Expand Down
3 changes: 1 addition & 2 deletions src/rules/prefer-to-have-length.ts
Expand Up @@ -39,10 +39,9 @@ export default createRule({
}

const [argument] = expect.arguments;
const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;

if (
!matcher ||
!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) ||
argument?.type !== AST_NODE_TYPES.MemberExpression ||
!isSupportedAccessor(argument.property, 'length')
Expand Down
10 changes: 5 additions & 5 deletions src/rules/require-to-throw-message.ts
Expand Up @@ -24,18 +24,18 @@ export default createRule({
return;
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;
const matcherName = getAccessorValue(matcher);

if (
matcher &&
jestFnCall.args.length === 0 &&
['toThrow', 'toThrowError'].includes(getAccessorValue(matcher)) &&
!jestFnCall.members.some(nod => getAccessorValue(nod) === 'not')
['toThrow', 'toThrowError'].includes(matcherName) &&
!jestFnCall.modifiers.some(nod => getAccessorValue(nod) === 'not')
) {
// Look for `toThrow` calls with no arguments.
context.report({
messageId: 'addErrorMessage',
data: { matcherName: getAccessorValue(matcher) },
data: { matcherName },
node: matcher,
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/rules/unbound-method.ts
Expand Up @@ -92,8 +92,8 @@ export default createRule<Options, MessageIds>({
context,
);

if (jestFnCall?.type === 'expect' && jestFnCall.members.length > 0) {
const matcher = jestFnCall.members[jestFnCall.members.length - 1];
if (jestFnCall?.type === 'expect') {
const { matcher } = jestFnCall;

if (!toThrowMatchers.includes(getAccessorValue(matcher))) {
return;
Expand Down
12 changes: 7 additions & 5 deletions src/rules/utils/parseJestFnCall.ts
Expand Up @@ -93,9 +93,10 @@ interface ParsedGeneralJestFnCall extends BaseParsedJestFnCall {
type: Exclude<JestFnType, 'expect'>;
}

export interface ParsedExpectFnCall extends BaseParsedJestFnCall {
export interface ParsedExpectFnCall
extends BaseParsedJestFnCall,
ModifiersAndMatcher {
type: 'expect';
args: TSESTree.CallExpression['arguments'];
}

export type ParsedJestFnCall = ParsedGeneralJestFnCall | ParsedExpectFnCall;
Expand Down Expand Up @@ -302,7 +303,8 @@ type KnownMemberExpressionProperty<Specifics extends string = string> =
interface ModifiersAndMatcher {
modifiers: KnownMemberExpressionProperty[];
matcher: KnownMemberExpressionProperty;
matcherArgs: TSESTree.CallExpression['arguments'];
/** The arguments that are being passed to the `matcher` */
args: TSESTree.CallExpression['arguments'];
}

const findModifiersAndMatcher = (
Expand All @@ -319,7 +321,7 @@ const findModifiersAndMatcher = (
) {
return {
matcher: member,
matcherArgs: member.parent.parent.arguments,
args: member.parent.parent.arguments,
modifiers,
};
}
Expand Down Expand Up @@ -372,7 +374,7 @@ const parseJestExpectCall = (
return {
...typelessParsedJestFnCall,
type: 'expect',
args: modifiersAndMatcher.matcherArgs,
...modifiersAndMatcher,
};
};

Expand Down
12 changes: 4 additions & 8 deletions src/rules/valid-expect.ts
Expand Up @@ -301,13 +301,11 @@ export default createRule<[Options], MessageIds>({
});
}

const matcher = jestFnCall.members[jestFnCall.members.length - 1];
const { matcher } = jestFnCall;

const parentNode = matcher.parent?.parent;
const parentNode = matcher.parent.parent;
const shouldBeAwaited =
jestFnCall.members
.slice(0, -1)
.some(nod => getAccessorValue(nod) !== 'not') ||
jestFnCall.modifiers.some(nod => getAccessorValue(nod) !== 'not') ||
asyncMatchers.includes(getAccessorValue(matcher));

if (!parentNode?.parent || !shouldBeAwaited) {
Expand Down Expand Up @@ -338,9 +336,7 @@ export default createRule<[Options], MessageIds>({
) {
context.report({
loc: finalNode.loc,
data: {
orReturned,
},
data: { orReturned },
messageId:
finalNode === targetNode
? 'asyncMustBeAwaited'
Expand Down

0 comments on commit 407bc9c

Please sign in to comment.