From 7d4d4b6a427b0712e35288b2b42757fd5085fe4d Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 30 Jul 2022 08:41:56 +1200 Subject: [PATCH] refactor: include matcher and modifiers in parsed expect fn call --- src/rules/no-alias-methods.ts | 11 ++--------- src/rules/no-interpolation-in-snapshots.ts | 4 +--- src/rules/no-large-snapshots.ts | 4 +--- src/rules/prefer-called-with.ts | 8 ++------ src/rules/prefer-comparison-matcher.ts | 9 ++++----- src/rules/prefer-equality-matcher.ts | 9 ++++----- src/rules/prefer-snapshot-hint.ts | 18 +++++------------- src/rules/prefer-strict-equal.ts | 4 ++-- src/rules/prefer-to-be.ts | 14 ++++++-------- src/rules/prefer-to-contain.ts | 16 ++++++---------- src/rules/prefer-to-have-length.ts | 3 +-- src/rules/require-to-throw-message.ts | 10 +++++----- src/rules/unbound-method.ts | 4 ++-- src/rules/utils/parseJestFnCall.ts | 12 +++++++----- src/rules/valid-expect.ts | 12 ++++-------- 15 files changed, 52 insertions(+), 86 deletions(-) diff --git a/src/rules/no-alias-methods.ts b/src/rules/no-alias-methods.ts index 5a47eeba6..814b73f5c 100644 --- a/src/rules/no-alias-methods.ts +++ b/src/rules/no-alias-methods.ts @@ -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); @@ -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)], }); diff --git a/src/rules/no-interpolation-in-snapshots.ts b/src/rules/no-interpolation-in-snapshots.ts index 62210208a..4b7d87daf 100644 --- a/src/rules/no-interpolation-in-snapshots.ts +++ b/src/rules/no-interpolation-in-snapshots.ts @@ -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 => { diff --git a/src/rules/no-large-snapshots.ts b/src/rules/no-large-snapshots.ts index 75ab0005c..d3d88131c 100644 --- a/src/rules/no-large-snapshots.ts +++ b/src/rules/no-large-snapshots.ts @@ -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], { diff --git a/src/rules/prefer-called-with.ts b/src/rules/prefer-called-with.ts index cf863b263..b4b6709a2 100644 --- a/src/rules/prefer-called-with.ts +++ b/src/rules/prefer-called-with.ts @@ -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)) { diff --git a/src/rules/prefer-comparison-matcher.ts b/src/rules/prefer-comparison-matcher.ts index d46362f80..9de4f237b 100644 --- a/src/rules/prefer-comparison-matcher.ts +++ b/src/rules/prefer-comparison-matcher.ts @@ -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)) || @@ -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', ); @@ -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)}` : ''; diff --git a/src/rules/prefer-equality-matcher.ts b/src/rules/prefer-equality-matcher.ts index 4b1fa8e3c..71b7a0bb4 100644 --- a/src/rules/prefer-equality-matcher.ts +++ b/src/rules/prefer-equality-matcher.ts @@ -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)) || @@ -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', ); @@ -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)}` : ''; diff --git a/src/rules/prefer-snapshot-hint.ts b/src/rules/prefer-snapshot-hint.ts index d045e1f74..5cb38deef 100644 --- a/src/rules/prefer-snapshot-hint.ts +++ b/src/rules/prefer-snapshot-hint.ts @@ -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; } @@ -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, }); } } @@ -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; } diff --git a/src/rules/prefer-strict-equal.ts b/src/rules/prefer-strict-equal.ts index f303b162d..bd688056c 100644 --- a/src/rules/prefer-strict-equal.ts +++ b/src/rules/prefer-strict-equal.ts @@ -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, diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index b4991d687..acf26dbd0 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -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])); @@ -72,7 +72,7 @@ const reportPreferToBe = ( return fixes; }, - node: matcher, + node: expectFnCall.matcher, }); }; @@ -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', ); diff --git a/src/rules/prefer-to-contain.ts b/src/rules/prefer-to-contain.ts index f8af5e1da..b9e51dbd0 100644 --- a/src/rules/prefer-to-contain.ts +++ b/src/rules/prefer-to-contain.ts @@ -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', ); @@ -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 diff --git a/src/rules/prefer-to-have-length.ts b/src/rules/prefer-to-have-length.ts index 1651df19f..a39fa47ce 100644 --- a/src/rules/prefer-to-have-length.ts +++ b/src/rules/prefer-to-have-length.ts @@ -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') diff --git a/src/rules/require-to-throw-message.ts b/src/rules/require-to-throw-message.ts index 70c78c45a..9fdd6707c 100644 --- a/src/rules/require-to-throw-message.ts +++ b/src/rules/require-to-throw-message.ts @@ -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, }); } diff --git a/src/rules/unbound-method.ts b/src/rules/unbound-method.ts index 47278c2c3..5298ce014 100644 --- a/src/rules/unbound-method.ts +++ b/src/rules/unbound-method.ts @@ -92,8 +92,8 @@ export default createRule({ 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; diff --git a/src/rules/utils/parseJestFnCall.ts b/src/rules/utils/parseJestFnCall.ts index 1c5158b56..c24949df9 100644 --- a/src/rules/utils/parseJestFnCall.ts +++ b/src/rules/utils/parseJestFnCall.ts @@ -93,9 +93,10 @@ interface ParsedGeneralJestFnCall extends BaseParsedJestFnCall { type: Exclude; } -export interface ParsedExpectFnCall extends BaseParsedJestFnCall { +export interface ParsedExpectFnCall + extends BaseParsedJestFnCall, + ModifiersAndMatcher { type: 'expect'; - args: TSESTree.CallExpression['arguments']; } export type ParsedJestFnCall = ParsedGeneralJestFnCall | ParsedExpectFnCall; @@ -302,7 +303,8 @@ type KnownMemberExpressionProperty = 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 = ( @@ -319,7 +321,7 @@ const findModifiersAndMatcher = ( ) { return { matcher: member, - matcherArgs: member.parent.parent.arguments, + args: member.parent.parent.arguments, modifiers, }; } @@ -372,7 +374,7 @@ const parseJestExpectCall = ( return { ...typelessParsedJestFnCall, type: 'expect', - args: modifiersAndMatcher.matcherArgs, + ...modifiersAndMatcher, }; }; diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index ea23fe3d2..743205ea3 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -300,13 +300,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) { @@ -337,9 +335,7 @@ export default createRule<[Options], MessageIds>({ ) { context.report({ loc: finalNode.loc, - data: { - orReturned, - }, + data: { orReturned }, messageId: finalNode === targetNode ? 'asyncMustBeAwaited'