diff --git a/src/rules/__tests__/no-done-callback.test.ts b/src/rules/__tests__/no-done-callback.test.ts index 7d99213b4..dcb258bc0 100644 --- a/src/rules/__tests__/no-done-callback.test.ts +++ b/src/rules/__tests__/no-done-callback.test.ts @@ -59,6 +59,24 @@ ruleTester.run('no-done-callback', rule, { }, ], }, + { + code: 'test("something", (done,) => {done();})', + errors: [ + { + messageId: 'noDoneCallback', + line: 1, + column: 20, + suggestions: [ + { + messageId: 'suggestWrappingInPromise', + data: { callback: 'done' }, + output: + 'test("something", () => {return new Promise(done => {done();})})', + }, + ], + }, + ], + }, { code: 'test("something", finished => {finished();})', errors: [ @@ -89,7 +107,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'test("something", () => {return new Promise((done) => {done();})})', + 'test("something", () => {return new Promise(done => {done();})})', }, ], }, @@ -123,7 +141,7 @@ ruleTester.run('no-done-callback', rule, { { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, - output: 'test("something", () => new Promise((done) => done()))', + output: 'test("something", () => new Promise(done => done()))', }, ], }, @@ -141,7 +159,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'test("something", function() {return new Promise((done) => {done();})})', + 'test("something", function() {return new Promise(done => {done();})})', }, ], }, @@ -159,7 +177,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'test("something", function () {return new Promise((done) => {done();})})', + 'test("something", function () {return new Promise(done => {done();})})', }, ], }, @@ -203,7 +221,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: dedent` - test('something', () => {return new Promise((done) => { + test('something', () => {return new Promise(done => { done(); })}); `, @@ -270,7 +288,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'beforeEach(() => {return new Promise((done) => {done();})})', + 'beforeEach(() => {return new Promise(done => {done();})})', }, ], }, @@ -304,7 +322,7 @@ ruleTester.run('no-done-callback', rule, { { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, - output: 'afterEach(() => new Promise((done) => done()))', + output: 'afterEach(() => new Promise(done => done()))', }, ], }, @@ -322,7 +340,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'beforeAll(function() {return new Promise((done) => {done();})})', + 'beforeAll(function() {return new Promise(done => {done();})})', }, ], }, @@ -340,7 +358,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: - 'afterEach(function () {return new Promise((done) => {done();})})', + 'afterEach(function () {return new Promise(done => {done();})})', }, ], }, @@ -383,7 +401,7 @@ ruleTester.run('no-done-callback', rule, { messageId: 'suggestWrappingInPromise', data: { callback: 'done' }, output: dedent` - beforeEach(() => {return new Promise((done) => { + beforeEach(() => {return new Promise(done => { done(); })}); `, @@ -413,7 +431,7 @@ ruleTester.run('no-done-callback', rule, { output: dedent` import { beforeEach } from '@jest/globals'; - beforeEach(() => {return new Promise((done) => { + beforeEach(() => {return new Promise(done => { done(); })}); `, @@ -443,7 +461,7 @@ ruleTester.run('no-done-callback', rule, { output: dedent` import { beforeEach as atTheStartOfEachTest } from '@jest/globals'; - atTheStartOfEachTest(() => {return new Promise((done) => { + atTheStartOfEachTest(() => {return new Promise(done => { done(); })}); `, diff --git a/src/rules/__tests__/prefer-comparison-matcher.test.ts b/src/rules/__tests__/prefer-comparison-matcher.test.ts index 2daf914e3..8df6065e7 100644 --- a/src/rules/__tests__/prefer-comparison-matcher.test.ts +++ b/src/rules/__tests__/prefer-comparison-matcher.test.ts @@ -28,6 +28,19 @@ const generateInvalidCases = ( }, ], }, + { + code: `expect(value ${operator} 1,).${equalityMatcher}(true,);`, + output: `expect(value,).${preferredMatcher}(1,);`, + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + messageId: 'useToBeComparison', + data: { preferredMatcher }, + column: 19 + operator.length, + line: 1, + }, + ], + }, { code: `expect(value ${operator} 1)['${equalityMatcher}'](true);`, output: `expect(value).${preferredMatcher}(1);`, diff --git a/src/rules/__tests__/prefer-equality-matcher.test.ts b/src/rules/__tests__/prefer-equality-matcher.test.ts index 7bd2fd104..0f18bacb2 100644 --- a/src/rules/__tests__/prefer-equality-matcher.test.ts +++ b/src/rules/__tests__/prefer-equality-matcher.test.ts @@ -54,6 +54,20 @@ ruleTester.run('prefer-equality-matcher: ===', rule, { }, ], }, + { + code: 'expect(a === b,).toBe(true,);', + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + messageId: 'useEqualityMatcher', + suggestions: expectSuggestions( + equalityMatcher => `expect(a,).${equalityMatcher}(b,);`, + ), + column: 18, + line: 1, + }, + ], + }, { code: 'expect(a === b).toBe(false);', errors: [ diff --git a/src/rules/__tests__/prefer-expect-assertions.test.ts b/src/rules/__tests__/prefer-expect-assertions.test.ts index 40379e11d..f7d208abe 100644 --- a/src/rules/__tests__/prefer-expect-assertions.test.ts +++ b/src/rules/__tests__/prefer-expect-assertions.test.ts @@ -254,7 +254,23 @@ ruleTester.run('prefer-expect-assertions', rule, { suggestions: [ { messageId: 'suggestRemovingExtraArguments', - output: 'it("it1", function() {expect.assertions(1);})', + output: 'it("it1", function() {expect.assertions(1,);})', + }, + ], + }, + ], + }, + { + code: 'it("it1", function() {expect.assertions(1,2,);})', + errors: [ + { + messageId: 'assertionsRequiresOneArgument', + column: 43, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'it("it1", function() {expect.assertions(1,);})', }, ], }, @@ -287,6 +303,22 @@ ruleTester.run('prefer-expect-assertions', rule, { }, ], }, + { + code: 'it("it1", function() {expect.hasAssertions("1",);})', + errors: [ + { + messageId: 'hasAssertionsTakesNoArguments', + column: 30, + line: 1, + suggestions: [ + { + messageId: 'suggestRemovingExtraArguments', + output: 'it("it1", function() {expect.hasAssertions();})', + }, + ], + }, + ], + }, { code: 'it("it1", function() {expect.hasAssertions("1", "2");})', errors: [ diff --git a/src/rules/__tests__/prefer-expect-resolves.test.ts b/src/rules/__tests__/prefer-expect-resolves.test.ts index c91258ed7..8f7b6589b 100644 --- a/src/rules/__tests__/prefer-expect-resolves.test.ts +++ b/src/rules/__tests__/prefer-expect-resolves.test.ts @@ -37,12 +37,12 @@ ruleTester.run('prefer-expect-resolves', rule, { { code: dedent` it('passes', async () => { - expect(await someValue()).toBe(true); + expect(await someValue(),).toBe(true); }); `, output: dedent` it('passes', async () => { - await expect(someValue()).resolves.toBe(true); + await expect(someValue(),).resolves.toBe(true); }); `, errors: [{ endColumn: 27, column: 10, messageId: 'expectResolves' }], diff --git a/src/rules/__tests__/prefer-mock-promise-shorthand.test.ts b/src/rules/__tests__/prefer-mock-promise-shorthand.test.ts index f74c47011..c5d94af1f 100644 --- a/src/rules/__tests__/prefer-mock-promise-shorthand.test.ts +++ b/src/rules/__tests__/prefer-mock-promise-shorthand.test.ts @@ -127,6 +127,19 @@ ruleTester.run('prefer-mock-shorthand', rule, { }, ], }, + { + code: 'aVariable.mockImplementation(() => Promise.reject(42),)', + output: 'aVariable.mockRejectedValue(42,)', + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + messageId: 'useMockShorthand', + data: { replacement: 'mockRejectedValue' }, + column: 11, + line: 1, + }, + ], + }, { code: 'aVariable.mockImplementationOnce(() => Promise.resolve(42))', output: 'aVariable.mockResolvedValueOnce(42)', diff --git a/src/rules/__tests__/prefer-spy-on.test.ts b/src/rules/__tests__/prefer-spy-on.test.ts index 93e6562bc..d418485c2 100644 --- a/src/rules/__tests__/prefer-spy-on.test.ts +++ b/src/rules/__tests__/prefer-spy-on.test.ts @@ -74,8 +74,9 @@ ruleTester.run('prefer-spy-on', rule, { ], }, { - code: 'obj.a = jest.fn(() => 10)', + code: 'obj.a = jest.fn(() => 10,)', output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)", + parserOptions: { ecmaVersion: 2017 }, errors: [ { messageId: 'useJestSpyOn', diff --git a/src/rules/__tests__/prefer-strict-equal.test.ts b/src/rules/__tests__/prefer-strict-equal.test.ts index fa1e16908..670a5c12b 100644 --- a/src/rules/__tests__/prefer-strict-equal.test.ts +++ b/src/rules/__tests__/prefer-strict-equal.test.ts @@ -26,6 +26,23 @@ ruleTester.run('prefer-strict-equal', rule, { }, ], }, + { + code: 'expect(something).toEqual(somethingElse,);', + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + messageId: 'useToStrictEqual', + column: 19, + line: 1, + suggestions: [ + { + messageId: 'suggestReplaceWithStrictEqual', + output: 'expect(something).toStrictEqual(somethingElse,);', + }, + ], + }, + ], + }, { code: 'expect(something)["toEqual"](somethingElse);', errors: [ diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index 545b9c857..86ac70558 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -42,6 +42,12 @@ ruleTester.run('prefer-to-be', rule, { output: 'expect(value).toBe(1);', errors: [{ messageId: 'useToBe', column: 15, line: 1 }], }, + { + code: 'expect(value).toStrictEqual(1,);', + output: 'expect(value).toBe(1,);', + parserOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: 'useToBe', column: 15, line: 1 }], + }, { code: 'expect(value).toStrictEqual(-1);', output: 'expect(value).toBe(-1);', @@ -115,6 +121,12 @@ ruleTester.run('prefer-to-be: null', rule, { output: 'expect(null).toBeNull();', errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], }, + { + code: 'expect(null).toEqual(null,);', + output: 'expect(null).toBeNull();', + parserOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + }, { code: 'expect(null).toStrictEqual(null);', output: 'expect(null).toBeNull();', diff --git a/src/rules/__tests__/prefer-to-contain.test.ts b/src/rules/__tests__/prefer-to-contain.test.ts index 8fd8f0bf1..458e5d62a 100644 --- a/src/rules/__tests__/prefer-to-contain.test.ts +++ b/src/rules/__tests__/prefer-to-contain.test.ts @@ -44,6 +44,12 @@ ruleTester.run('prefer-to-contain', rule, { output: 'expect(a).toContain(b);', errors: [{ messageId: 'useToContain', column: 23, line: 1 }], }, + { + code: 'expect(a.includes(b,),).toEqual(true,);', + output: 'expect(a,).toContain(b,);', + parserOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: 'useToContain', column: 25, line: 1 }], + }, { code: "expect(a['includes'](b)).toEqual(true);", output: 'expect(a).toContain(b);', diff --git a/src/rules/__tests__/prefer-to-have-length.test.ts b/src/rules/__tests__/prefer-to-have-length.test.ts index e4a948455..171b3392c 100644 --- a/src/rules/__tests__/prefer-to-have-length.test.ts +++ b/src/rules/__tests__/prefer-to-have-length.test.ts @@ -28,6 +28,12 @@ ruleTester.run('prefer-to-have-length', rule, { output: 'expect(files).toHaveLength(1);', errors: [{ messageId: 'useToHaveLength', column: 25, line: 1 }], }, + { + code: 'expect(files["length"]).toBe(1,);', + output: 'expect(files).toHaveLength(1,);', + parserOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: 'useToHaveLength', column: 25, line: 1 }], + }, { code: 'expect(files["length"])["not"].toBe(1);', output: 'expect(files)["not"].toHaveLength(1);', diff --git a/src/rules/__tests__/prefer-todo.test.ts b/src/rules/__tests__/prefer-todo.test.ts index 443298692..e36b3a5b1 100644 --- a/src/rules/__tests__/prefer-todo.test.ts +++ b/src/rules/__tests__/prefer-todo.test.ts @@ -33,6 +33,12 @@ ruleTester.run('prefer-todo', rule, { output: 'test.todo("i need to write this test");', errors: [{ messageId: 'unimplementedTest' }], }, + { + code: `test("i need to write this test",);`, + output: 'test.todo("i need to write this test",);', + parserOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: 'unimplementedTest' }], + }, { code: 'test(`i need to write this test`);', output: 'test.todo(`i need to write this test`);', diff --git a/src/rules/no-done-callback.ts b/src/rules/no-done-callback.ts index a356f2664..b894b8a6e 100644 --- a/src/rules/no-done-callback.ts +++ b/src/rules/no-done-callback.ts @@ -98,41 +98,49 @@ export default createRule({ messageId: 'suggestWrappingInPromise', data: { callback: argument.name }, fix(fixer) { - const { body } = callback; + const { body, params } = callback; const sourceCode = context.getSourceCode(); const firstBodyToken = sourceCode.getFirstToken(body); const lastBodyToken = sourceCode.getLastToken(body); - const tokenBeforeArgument = sourceCode.getTokenBefore(argument); - const tokenAfterArgument = sourceCode.getTokenAfter(argument); + + const [firstParam] = params; + const lastParam = params[params.length - 1]; + + const tokenBeforeFirstParam = + sourceCode.getTokenBefore(firstParam); + let tokenAfterLastParam = sourceCode.getTokenAfter(lastParam); + + if (tokenAfterLastParam?.value === ',') { + tokenAfterLastParam = + sourceCode.getTokenAfter(tokenAfterLastParam); + } /* istanbul ignore if */ if ( !firstBodyToken || !lastBodyToken || - !tokenBeforeArgument || - !tokenAfterArgument + !tokenBeforeFirstParam || + !tokenAfterLastParam ) { throw new Error( `Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`, ); } - const argumentInParens = - tokenBeforeArgument.value === '(' && - tokenAfterArgument.value === ')'; - - let argumentFix = fixer.replaceText(argument, '()'); + let argumentFix = fixer.replaceText(firstParam, '()'); - if (argumentInParens) { - argumentFix = fixer.remove(argument); + if ( + tokenBeforeFirstParam.value === '(' && + tokenAfterLastParam.value === ')' + ) { + argumentFix = fixer.removeRange([ + tokenBeforeFirstParam.range[1], + tokenAfterLastParam.range[0], + ]); } - let newCallback = argument.name; - - if (argumentInParens) { - newCallback = `(${newCallback})`; - } + const newCallback = argument.name; let beforeReplacement = `new Promise(${newCallback} => `; let afterReplacement = ')'; diff --git a/src/rules/prefer-expect-assertions.ts b/src/rules/prefer-expect-assertions.ts index 4c1193b8f..a34ed4bed 100644 --- a/src/rules/prefer-expect-assertions.ts +++ b/src/rules/prefer-expect-assertions.ts @@ -6,6 +6,7 @@ import { isFunction, isTypeOfJestFnCall, parseJestFnCall, + removeExtraArgumentsFixer, } from './utils'; const isFirstStatement = (node: TSESTree.CallExpression): boolean => { @@ -32,15 +33,12 @@ const isFirstStatement = (node: TSESTree.CallExpression): boolean => { }; const suggestRemovingExtraArguments = ( - args: TSESTree.CallExpression['arguments'], - extraArgsStartAt: number, + context: TSESLint.RuleContext, + func: TSESTree.CallExpression, + from: number, ): TSESLint.ReportSuggestionArray[0] => ({ messageId: 'suggestRemovingExtraArguments', - fix: fixer => - fixer.removeRange([ - args[extraArgsStartAt].range[0] - Math.sign(extraArgsStartAt), - args[args.length - 1].range[1], - ]), + fix: fixer => removeExtraArgumentsFixer(fixer, context, func, from), }); interface RuleOptions { @@ -139,13 +137,16 @@ export default createRule<[RuleOptions], MessageIds>({ return false; }; - const checkExpectHasAssertions = (expectFnCall: ParsedExpectFnCall) => { + const checkExpectHasAssertions = ( + expectFnCall: ParsedExpectFnCall, + func: TSESTree.CallExpression, + ) => { if (getAccessorValue(expectFnCall.members[0]) === 'hasAssertions') { if (expectFnCall.args.length) { context.report({ messageId: 'hasAssertionsTakesNoArguments', node: expectFnCall.matcher, - suggest: [suggestRemovingExtraArguments(expectFnCall.args, 0)], + suggest: [suggestRemovingExtraArguments(context, func, 0)], }); } @@ -158,7 +159,7 @@ export default createRule<[RuleOptions], MessageIds>({ if (expectFnCall.args.length) { loc = expectFnCall.args[1].loc; - suggest.push(suggestRemovingExtraArguments(expectFnCall.args, 1)); + suggest.push(suggestRemovingExtraArguments(context, func, 1)); } context.report({ @@ -222,7 +223,7 @@ export default createRule<[RuleOptions], MessageIds>({ getAccessorValue(jestFnCall.members[0]), ) ) { - checkExpectHasAssertions(jestFnCall); + checkExpectHasAssertions(jestFnCall, node); hasExpectAssertionsAsFirstStatement = true; } diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index 339b20914..dc1839e83 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -8,6 +8,7 @@ import { getFirstMatcherArg, isIdentifier, parseJestFnCall, + removeExtraArgumentsFixer, replaceAccessorFixer, } from './utils'; @@ -58,6 +59,7 @@ const reportPreferToBe = ( context: TSESLint.RuleContext, whatToBe: ToBeWhat, expectFnCall: ParsedExpectFnCall, + func: TSESTree.CallExpression, modifierNode?: AccessorNode, ) => { context.report({ @@ -68,7 +70,7 @@ const reportPreferToBe = ( ]; if (expectFnCall.args?.length && whatToBe !== '') { - fixes.push(fixer.remove(expectFnCall.args[0])); + fixes.push(removeExtraArgumentsFixer(fixer, context, func, 0)); } if (modifierNode) { @@ -125,6 +127,7 @@ export default createRule({ context, matcherName === 'toBeDefined' ? 'Undefined' : 'Defined', jestFnCall, + node, notModifier, ); @@ -139,7 +142,7 @@ export default createRule({ } if (isNullEqualityMatcher(jestFnCall)) { - reportPreferToBe(context, 'Null', jestFnCall); + reportPreferToBe(context, 'Null', jestFnCall, node); return; } @@ -147,19 +150,19 @@ export default createRule({ if (isFirstArgumentIdentifier(jestFnCall, 'undefined')) { const name = notModifier ? 'Defined' : 'Undefined'; - reportPreferToBe(context, name, jestFnCall, notModifier); + reportPreferToBe(context, name, jestFnCall, node, notModifier); return; } if (isFirstArgumentIdentifier(jestFnCall, 'NaN')) { - reportPreferToBe(context, 'NaN', jestFnCall); + reportPreferToBe(context, 'NaN', jestFnCall, node); return; } if (shouldUseToBe(jestFnCall) && matcherName !== EqualityMatcher.toBe) { - reportPreferToBe(context, '', jestFnCall); + reportPreferToBe(context, '', jestFnCall, node); } }, }; diff --git a/src/rules/utils/misc.ts b/src/rules/utils/misc.ts index 109a51781..256bccf43 100644 --- a/src/rules/utils/misc.ts +++ b/src/rules/utils/misc.ts @@ -168,6 +168,25 @@ export const replaceAccessorFixer = ( ); }; +export const removeExtraArgumentsFixer = ( + fixer: TSESLint.RuleFixer, + context: TSESLint.RuleContext, + func: TSESTree.CallExpression, + from: number, +): TSESLint.RuleFix => { + const firstArg = func.arguments[from]; + const lastArg = func.arguments[func.arguments.length - 1]; + + const sourceCode = context.getSourceCode(); + let tokenAfterLastParam = sourceCode.getTokenAfter(lastArg)!; + + if (tokenAfterLastParam.value === ',') { + tokenAfterLastParam = sourceCode.getTokenAfter(tokenAfterLastParam)!; + } + + return fixer.removeRange([firstArg.range[0], tokenAfterLastParam.range[0]]); +}; + export const findTopMostCallExpression = ( node: TSESTree.CallExpression, ): TSESTree.CallExpression => {