From 19f3748af08d3ff51f17769600e9b19f21e93c4d Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Fri, 3 May 2024 15:10:52 +0900 Subject: [PATCH 01/11] feat: add async --- src/rules/valid-expect.ts | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index bee809d73..3fb208e64 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -5,6 +5,7 @@ import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; import { + type FunctionExpression, ModifierName, createRule, getAccessorValue, @@ -50,16 +51,27 @@ const findPromiseCallExpressionNode = (node: TSESTree.Node) => ? getPromiseCallExpressionNode(node.parent) : null; -const findFirstAsyncFunction = ({ +const findFirstFunctionExpression = ({ parent, -}: TSESTree.Node): TSESTree.Node | null => { +}: TSESTree.Node): FunctionExpression | null => { if (!parent) { return null; } - return isFunction(parent) && parent.async - ? parent - : findFirstAsyncFunction(parent); + return isFunction(parent) ? parent : findFirstFunctionExpression(parent); +}; + +const getNormalizeFunctionExpression = ( + functionExpression: FunctionExpression, +): TSESTree.Node => { + if ( + functionExpression.parent.type === AST_NODE_TYPES.Property && + functionExpression.type === AST_NODE_TYPES.FunctionExpression + ) { + return functionExpression.parent; + } + + return functionExpression; }; const getParentIfThenified = (node: TSESTree.Node): TSESTree.Node => { @@ -355,9 +367,18 @@ export default createRule<[Options], MessageIds>({ : 'promisesWithAsyncAssertionsMustBeAwaited', node, fix(fixer) { - if (!findFirstAsyncFunction(finalNode)) { + const functionExpression = findFirstFunctionExpression(finalNode); + + if (!functionExpression) { return []; } + + if (!functionExpression.async) { + const targetFunction = + getNormalizeFunctionExpression(functionExpression); + + return fixer.insertTextBefore(targetFunction, 'async '); + } const returnStatement = finalNode.parent?.type === AST_NODE_TYPES.ReturnStatement ? finalNode.parent From 10e7ece95eb4a408212f4da0e087348f2b3b6dd7 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Fri, 3 May 2024 15:11:45 +0900 Subject: [PATCH 02/11] test: tests for adding async --- src/rules/__tests__/valid-expect.test.ts | 119 ++++++++++++++++++++++- 1 file changed, 116 insertions(+), 3 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index 883a5d958..c89cc3254 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -144,7 +144,6 @@ ruleTester.run('valid-expect', rule, { }, ], }, - { code: 'expect().toBe(true);', errors: [ @@ -417,7 +416,6 @@ ruleTester.run('valid-expect', rule, { }, ], }, - { code: dedent` expect.extend({ @@ -428,6 +426,15 @@ ruleTester.run('valid-expect', rule, { } }); `, + output: dedent` + expect.extend({ + async toResolve(obj) { + this.isNot + ? expect(obj).toBe(true) + : expect(obj).resolves.not.toThrow(); + } + }); + `, errors: [ { column: 9, @@ -446,6 +453,15 @@ ruleTester.run('valid-expect', rule, { } }); `, + output: dedent` + expect.extend({ + async toResolve(obj) { + this.isNot + ? expect(obj).resolves.not.toThrow() + : expect(obj).toBe(true); + } + }); + `, errors: [ { column: 9, @@ -466,6 +482,17 @@ ruleTester.run('valid-expect', rule, { } }); `, + output: dedent` + expect.extend({ + async toResolve(obj) { + this.isNot + ? expect(obj).toBe(true) + : anotherCondition + ? expect(obj).resolves.not.toThrow() + : expect(obj).toBe(false) + } + }); + `, errors: [ { column: 9, @@ -478,6 +505,8 @@ ruleTester.run('valid-expect', rule, { // expect().resolves { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ { column: 30, @@ -489,6 +518,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -500,6 +531,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ { @@ -512,6 +545,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -523,6 +558,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).not.toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -535,6 +572,8 @@ ruleTester.run('valid-expect', rule, { // expect().resolves.not { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ { column: 30, @@ -547,6 +586,8 @@ ruleTester.run('valid-expect', rule, { // expect().rejects { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ { column: 30, @@ -559,6 +600,8 @@ ruleTester.run('valid-expect', rule, { // expect().rejects.not { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', + output: + 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ { column: 30, @@ -597,6 +640,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); });', + output: + 'test("valid-expect", async () => { expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -608,6 +653,8 @@ ruleTester.run('valid-expect', rule, { }, { code: 'test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); });', + output: + 'test("valid-expect", async () => { expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -785,6 +832,11 @@ ruleTester.run('valid-expect', rule, { Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + `, errors: [ { line: 2, @@ -801,6 +853,11 @@ ruleTester.run('valid-expect', rule, { Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + `, errors: [ { line: 2, @@ -838,6 +895,11 @@ ruleTester.run('valid-expect', rule, { Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + `, errors: [ { line: 2, @@ -855,6 +917,11 @@ ruleTester.run('valid-expect', rule, { Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + }); + `, options: [{ alwaysAwait: true }], errors: [ { @@ -875,6 +942,14 @@ ruleTester.run('valid-expect', rule, { ]); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.all([ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ]); + }); + `, errors: [ { line: 2, @@ -896,6 +971,14 @@ ruleTester.run('valid-expect', rule, { ]); }); `, + output: dedent` + test("valid-expect", async () => { + Promise.x([ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ]); + }); + `, errors: [ { line: 2, @@ -907,7 +990,6 @@ ruleTester.run('valid-expect', rule, { }, ], }, - // { code: dedent` test("valid-expect", () => { @@ -917,6 +999,14 @@ ruleTester.run('valid-expect', rule, { ] }); `, + output: dedent` + test("valid-expect", async () => { + const assertions = [ + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), + ] + }); + `, errors: [ { line: 3, @@ -945,6 +1035,14 @@ ruleTester.run('valid-expect', rule, { ] }); `, + output: dedent` + test("valid-expect", async () => { + const assertions = [ + expect(Promise.resolve(2)).toResolve(), + expect(Promise.resolve(3)).toReject(), + ] + }); + `, errors: [ { messageId: 'asyncMustBeAwaited', @@ -969,6 +1067,14 @@ ruleTester.run('valid-expect', rule, { ] }); `, + output: dedent` + test("valid-expect", async () => { + const assertions = [ + expect(Promise.resolve(2)).not.toResolve(), + expect(Promise.resolve(3)).resolves.toReject(), + ] + }); + `, errors: [ { messageId: 'asyncMustBeAwaited', @@ -1002,6 +1108,13 @@ ruleTester.run('valid-expect', rule, { }); }); `, + output: dedent` + test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { + expect(Promise.resolve(2)).resolves.toBe(1); + }); + }); + `, errors: [ { line: 3, From 6788264f88ebbc9c83771ec32d42973e192ff97d Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sun, 5 May 2024 16:23:08 +0900 Subject: [PATCH 03/11] feat: add await --- src/rules/__tests__/valid-expect.test.ts | 48 ++++++++++++------------ src/rules/valid-expect.ts | 12 ++++-- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index c89cc3254..351eee7cb 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -431,7 +431,7 @@ ruleTester.run('valid-expect', rule, { async toResolve(obj) { this.isNot ? expect(obj).toBe(true) - : expect(obj).resolves.not.toThrow(); + : await expect(obj).resolves.not.toThrow(); } }); `, @@ -457,7 +457,7 @@ ruleTester.run('valid-expect', rule, { expect.extend({ async toResolve(obj) { this.isNot - ? expect(obj).resolves.not.toThrow() + ? await expect(obj).resolves.not.toThrow() : expect(obj).toBe(true); } }); @@ -488,7 +488,7 @@ ruleTester.run('valid-expect', rule, { this.isNot ? expect(obj).toBe(true) : anotherCondition - ? expect(obj).resolves.not.toThrow() + ? await expect(obj).resolves.not.toThrow() : expect(obj).toBe(false) } }); @@ -506,7 +506,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ { column: 30, @@ -519,7 +519,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -532,7 +532,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ { @@ -546,7 +546,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toReject(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -559,7 +559,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).not.toReject(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).not.toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -573,7 +573,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ { column: 30, @@ -587,7 +587,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ { column: 30, @@ -601,7 +601,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ { column: 30, @@ -641,7 +641,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); });', output: - 'test("valid-expect", async () => { expect(Promise.reject(2)).toRejectWith(2); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -654,7 +654,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); });', output: - 'test("valid-expect", async () => { expect(Promise.reject(2)).rejects.toBe(2); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -834,7 +834,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -855,7 +855,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -897,7 +897,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -919,7 +919,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, options: [{ alwaysAwait: true }], @@ -944,7 +944,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.all([ + await Promise.all([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -973,7 +973,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.x([ + await Promise.x([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -1002,8 +1002,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).resolves.not.toBeDefined(), - expect(Promise.resolve(3)).resolves.not.toBeDefined(), + await expect(Promise.resolve(2)).resolves.not.toBeDefined(), + await expect(Promise.resolve(3)).resolves.not.toBeDefined(), ] }); `, @@ -1070,8 +1070,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).not.toResolve(), - expect(Promise.resolve(3)).resolves.toReject(), + await expect(Promise.resolve(2)).not.toResolve(), + await expect(Promise.resolve(3)).resolves.toReject(), ] }); `, @@ -1111,7 +1111,7 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", () => { return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { - expect(Promise.resolve(2)).resolves.toBe(1); + await expect(Promise.resolve(2)).resolves.toBe(1); }); }); `, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 3fb208e64..4fbe9a501 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -63,7 +63,10 @@ const findFirstFunctionExpression = ({ const getNormalizeFunctionExpression = ( functionExpression: FunctionExpression, -): TSESTree.Node => { +): + | TSESTree.PropertyComputedName + | TSESTree.PropertyNonComputedName + | FunctionExpression => { if ( functionExpression.parent.type === AST_NODE_TYPES.Property && functionExpression.type === AST_NODE_TYPES.FunctionExpression @@ -377,10 +380,13 @@ export default createRule<[Options], MessageIds>({ const targetFunction = getNormalizeFunctionExpression(functionExpression); - return fixer.insertTextBefore(targetFunction, 'async '); + return [ + fixer.insertTextBefore(targetFunction, 'async '), + fixer.insertTextBefore(finalNode, 'await '), + ]; } const returnStatement = - finalNode.parent?.type === AST_NODE_TYPES.ReturnStatement + finalNode.parent.type === AST_NODE_TYPES.ReturnStatement ? finalNode.parent : null; From e652a255e1af3d744417f7b8da9e9c113ea55696 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Mon, 6 May 2024 14:53:35 +0900 Subject: [PATCH 04/11] fix: valid-expect test --- src/rules/__tests__/valid-expect.test.ts | 48 ++++++++++++------------ src/rules/valid-expect.ts | 5 +-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index 351eee7cb..c89cc3254 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -431,7 +431,7 @@ ruleTester.run('valid-expect', rule, { async toResolve(obj) { this.isNot ? expect(obj).toBe(true) - : await expect(obj).resolves.not.toThrow(); + : expect(obj).resolves.not.toThrow(); } }); `, @@ -457,7 +457,7 @@ ruleTester.run('valid-expect', rule, { expect.extend({ async toResolve(obj) { this.isNot - ? await expect(obj).resolves.not.toThrow() + ? expect(obj).resolves.not.toThrow() : expect(obj).toBe(true); } }); @@ -488,7 +488,7 @@ ruleTester.run('valid-expect', rule, { this.isNot ? expect(obj).toBe(true) : anotherCondition - ? await expect(obj).resolves.not.toThrow() + ? expect(obj).resolves.not.toThrow() : expect(obj).toBe(false) } }); @@ -506,7 +506,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ { column: 30, @@ -519,7 +519,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -532,7 +532,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ { @@ -546,7 +546,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toReject(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -559,7 +559,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).not.toReject(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).not.toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -573,7 +573,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ { column: 30, @@ -587,7 +587,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.toBeDefined(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ { column: 30, @@ -601,7 +601,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', + 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ { column: 30, @@ -641,7 +641,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); });', output: - 'test("valid-expect", async () => { await expect(Promise.reject(2)).toRejectWith(2); });', + 'test("valid-expect", async () => { expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -654,7 +654,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); });', output: - 'test("valid-expect", async () => { await expect(Promise.reject(2)).rejects.toBe(2); });', + 'test("valid-expect", async () => { expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -834,7 +834,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -855,7 +855,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -897,7 +897,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -919,7 +919,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, options: [{ alwaysAwait: true }], @@ -944,7 +944,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.all([ + Promise.all([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -973,7 +973,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - await Promise.x([ + Promise.x([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -1002,8 +1002,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - await expect(Promise.resolve(2)).resolves.not.toBeDefined(), - await expect(Promise.resolve(3)).resolves.not.toBeDefined(), + expect(Promise.resolve(2)).resolves.not.toBeDefined(), + expect(Promise.resolve(3)).resolves.not.toBeDefined(), ] }); `, @@ -1070,8 +1070,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - await expect(Promise.resolve(2)).not.toResolve(), - await expect(Promise.resolve(3)).resolves.toReject(), + expect(Promise.resolve(2)).not.toResolve(), + expect(Promise.resolve(3)).resolves.toReject(), ] }); `, @@ -1111,7 +1111,7 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", () => { return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { - await expect(Promise.resolve(2)).resolves.toBe(1); + expect(Promise.resolve(2)).resolves.toBe(1); }); }); `, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 4fbe9a501..eda1ea9c8 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -380,10 +380,7 @@ export default createRule<[Options], MessageIds>({ const targetFunction = getNormalizeFunctionExpression(functionExpression); - return [ - fixer.insertTextBefore(targetFunction, 'async '), - fixer.insertTextBefore(finalNode, 'await '), - ]; + return fixer.insertTextBefore(targetFunction, 'async '); } const returnStatement = finalNode.parent.type === AST_NODE_TYPES.ReturnStatement From 004f14ed43a410c7f30e88ffb6e85ae14cb1966d Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 11 May 2024 09:00:31 +0900 Subject: [PATCH 05/11] Revert "fix: valid-expect test" This reverts commit e652a255e1af3d744417f7b8da9e9c113ea55696. --- src/rules/__tests__/valid-expect.test.ts | 48 ++++++++++++------------ src/rules/valid-expect.ts | 5 ++- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index c89cc3254..351eee7cb 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -431,7 +431,7 @@ ruleTester.run('valid-expect', rule, { async toResolve(obj) { this.isNot ? expect(obj).toBe(true) - : expect(obj).resolves.not.toThrow(); + : await expect(obj).resolves.not.toThrow(); } }); `, @@ -457,7 +457,7 @@ ruleTester.run('valid-expect', rule, { expect.extend({ async toResolve(obj) { this.isNot - ? expect(obj).resolves.not.toThrow() + ? await expect(obj).resolves.not.toThrow() : expect(obj).toBe(true); } }); @@ -488,7 +488,7 @@ ruleTester.run('valid-expect', rule, { this.isNot ? expect(obj).toBe(true) : anotherCondition - ? expect(obj).resolves.not.toThrow() + ? await expect(obj).resolves.not.toThrow() : expect(obj).toBe(false) } }); @@ -506,7 +506,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ { column: 30, @@ -519,7 +519,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -532,7 +532,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toResolve(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toResolve(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ { @@ -546,7 +546,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).toReject(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).toReject(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -559,7 +559,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).not.toReject(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).not.toReject(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).not.toReject(); });', errors: [ { messageId: 'asyncMustBeAwaited', @@ -573,7 +573,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ { column: 30, @@ -587,7 +587,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ { column: 30, @@ -601,7 +601,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', output: - 'test("valid-expect", async () => { expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', + 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ { column: 30, @@ -641,7 +641,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).toRejectWith(2); });', output: - 'test("valid-expect", async () => { expect(Promise.reject(2)).toRejectWith(2); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -654,7 +654,7 @@ ruleTester.run('valid-expect', rule, { { code: 'test("valid-expect", () => { expect(Promise.reject(2)).rejects.toBe(2); });', output: - 'test("valid-expect", async () => { expect(Promise.reject(2)).rejects.toBe(2); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ { @@ -834,7 +834,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -855,7 +855,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -897,7 +897,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.x(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, errors: [ @@ -919,7 +919,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); + await Promise.resolve(expect(Promise.resolve(2)).resolves.not.toBeDefined()); }); `, options: [{ alwaysAwait: true }], @@ -944,7 +944,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.all([ + await Promise.all([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -973,7 +973,7 @@ ruleTester.run('valid-expect', rule, { `, output: dedent` test("valid-expect", async () => { - Promise.x([ + await Promise.x([ expect(Promise.resolve(2)).resolves.not.toBeDefined(), expect(Promise.resolve(3)).resolves.not.toBeDefined(), ]); @@ -1002,8 +1002,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).resolves.not.toBeDefined(), - expect(Promise.resolve(3)).resolves.not.toBeDefined(), + await expect(Promise.resolve(2)).resolves.not.toBeDefined(), + await expect(Promise.resolve(3)).resolves.not.toBeDefined(), ] }); `, @@ -1070,8 +1070,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).not.toResolve(), - expect(Promise.resolve(3)).resolves.toReject(), + await expect(Promise.resolve(2)).not.toResolve(), + await expect(Promise.resolve(3)).resolves.toReject(), ] }); `, @@ -1111,7 +1111,7 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", () => { return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { - expect(Promise.resolve(2)).resolves.toBe(1); + await expect(Promise.resolve(2)).resolves.toBe(1); }); }); `, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index eda1ea9c8..4fbe9a501 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -380,7 +380,10 @@ export default createRule<[Options], MessageIds>({ const targetFunction = getNormalizeFunctionExpression(functionExpression); - return fixer.insertTextBefore(targetFunction, 'async '); + return [ + fixer.insertTextBefore(targetFunction, 'async '), + fixer.insertTextBefore(finalNode, 'await '), + ]; } const returnStatement = finalNode.parent.type === AST_NODE_TYPES.ReturnStatement From 58c4046bde1f786e948d56f06b4541a03c6771a3 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 11 May 2024 11:54:22 +0900 Subject: [PATCH 06/11] fix: refactor to return an array --- src/rules/valid-expect.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 4fbe9a501..784993f37 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -4,6 +4,7 @@ */ import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; +import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; import { type FunctionExpression, ModifierName, @@ -376,14 +377,13 @@ export default createRule<[Options], MessageIds>({ return []; } + const fixes: RuleFix[] = []; + if (!functionExpression.async) { const targetFunction = getNormalizeFunctionExpression(functionExpression); - return [ - fixer.insertTextBefore(targetFunction, 'async '), - fixer.insertTextBefore(finalNode, 'await '), - ]; + fixes.push(fixer.insertTextBefore(targetFunction, 'async ')); } const returnStatement = finalNode.parent.type === AST_NODE_TYPES.ReturnStatement @@ -395,10 +395,13 @@ export default createRule<[Options], MessageIds>({ getSourceCode(context).getText(returnStatement); const replacedText = sourceCodeText.replace('return', 'await'); - return fixer.replaceText(returnStatement, replacedText); + return [ + ...fixes, + fixer.replaceText(returnStatement, replacedText), + ]; } - return fixer.insertTextBefore(finalNode, 'await '); + return [...fixes, fixer.insertTextBefore(finalNode, 'await ')]; }, }); From ae8ecac847245d5131e420d0efebfc36b202787a Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sun, 12 May 2024 15:51:23 +0900 Subject: [PATCH 07/11] fix: valid-expect logic --- src/rules/__tests__/valid-expect.test.ts | 168 ++++++++++++++++++++++- src/rules/valid-expect.ts | 39 +++--- 2 files changed, 188 insertions(+), 19 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index 351eee7cb..555ad3d6d 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -436,6 +436,11 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + column: 12, + endColumn: 4, + messageId: 'asyncMustBeAwaited', + }, { column: 9, endColumn: 43, @@ -463,6 +468,11 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + column: 12, + endColumn: 4, + messageId: 'asyncMustBeAwaited', + }, { column: 9, endColumn: 43, @@ -494,6 +504,11 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + column: 12, + endColumn: 4, + messageId: 'asyncMustBeAwaited', + }, { column: 9, endColumn: 43, @@ -508,6 +523,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ + { + column: 22, + endColumn: 82, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { column: 30, endColumn: 79, @@ -521,6 +542,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -535,6 +562,12 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -548,6 +581,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toReject(); });', errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -561,6 +600,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).not.toReject(); });', errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -575,6 +620,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ + { + column: 22, + endColumn: 86, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { column: 30, endColumn: 83, @@ -589,6 +640,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ + { + column: 22, + endColumn: 81, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { column: 30, endColumn: 78, @@ -603,6 +660,12 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ + { + column: 22, + endColumn: 85, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { column: 30, endColumn: 82, @@ -644,6 +707,11 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -657,6 +725,11 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -838,6 +911,13 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 2, column: 3, @@ -859,6 +939,13 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 2, column: 3, @@ -901,6 +988,13 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 2, column: 3, @@ -924,6 +1018,12 @@ ruleTester.run('valid-expect', rule, { `, options: [{ alwaysAwait: true }], errors: [ + { + line: 1, + column: 22, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + }, { line: 2, column: 3, @@ -951,6 +1051,14 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endLine: 6, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 2, column: 3, @@ -980,6 +1088,14 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endLine: 6, + endColumn: 2, + messageId: 'promisesWithAsyncAssertionsMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 2, column: 3, @@ -1008,6 +1124,22 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 1, + column: 22, + endLine: 6, + endColumn: 2, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, + { + line: 1, + column: 22, + endLine: 6, + endColumn: 2, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 3, column: 5, @@ -1038,12 +1170,24 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).toResolve(), - expect(Promise.resolve(3)).toReject(), + await expect(Promise.resolve(2)).toResolve(), + await expect(Promise.resolve(3)).toReject(), ] }); `, errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -1076,6 +1220,18 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, + { + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + column: 22, + line: 1, + }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -1116,6 +1272,14 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ + { + line: 2, + column: 71, + endLine: 4, + endColumn: 4, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, { line: 3, column: 5, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 784993f37..e5920fc06 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -4,7 +4,6 @@ */ import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; -import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; import { type FunctionExpression, ModifierName, @@ -362,6 +361,25 @@ export default createRule<[Options], MessageIds>({ // if we didn't warn user already !promiseArrayExceptionExists(finalNode.loc) ) { + const functionExpression = findFirstFunctionExpression(finalNode); + + if (functionExpression && !functionExpression.async) { + context.report({ + loc: functionExpression.loc, + data: { orReturned }, + messageId: + finalNode === targetNode + ? 'asyncMustBeAwaited' + : 'promisesWithAsyncAssertionsMustBeAwaited', + node, + fix(fixer) { + const targetFunction = + getNormalizeFunctionExpression(functionExpression); + + return fixer.insertTextBefore(targetFunction, 'async '); + }, + }); + } context.report({ loc: finalNode.loc, data: { orReturned }, @@ -371,20 +389,10 @@ export default createRule<[Options], MessageIds>({ : 'promisesWithAsyncAssertionsMustBeAwaited', node, fix(fixer) { - const functionExpression = findFirstFunctionExpression(finalNode); - if (!functionExpression) { - return []; + return null; } - const fixes: RuleFix[] = []; - - if (!functionExpression.async) { - const targetFunction = - getNormalizeFunctionExpression(functionExpression); - - fixes.push(fixer.insertTextBefore(targetFunction, 'async ')); - } const returnStatement = finalNode.parent.type === AST_NODE_TYPES.ReturnStatement ? finalNode.parent @@ -395,13 +403,10 @@ export default createRule<[Options], MessageIds>({ getSourceCode(context).getText(returnStatement); const replacedText = sourceCodeText.replace('return', 'await'); - return [ - ...fixes, - fixer.replaceText(returnStatement, replacedText), - ]; + return fixer.replaceText(returnStatement, replacedText); } - return [...fixes, fixer.insertTextBefore(finalNode, 'await ')]; + return fixer.insertTextBefore(finalNode, 'await '); }, }); From 9d15df1fb301a2e7bb7b74963df9e8c19dc56f4d Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Thu, 23 May 2024 22:51:43 +0900 Subject: [PATCH 08/11] Revert "fix: valid-expect logic" This reverts commit ae8ecac847245d5131e420d0efebfc36b202787a. --- src/rules/__tests__/valid-expect.test.ts | 168 +---------------------- src/rules/valid-expect.ts | 39 +++--- 2 files changed, 19 insertions(+), 188 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index 555ad3d6d..351eee7cb 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -436,11 +436,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - column: 12, - endColumn: 4, - messageId: 'asyncMustBeAwaited', - }, { column: 9, endColumn: 43, @@ -468,11 +463,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - column: 12, - endColumn: 4, - messageId: 'asyncMustBeAwaited', - }, { column: 9, endColumn: 43, @@ -504,11 +494,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - column: 12, - endColumn: 4, - messageId: 'asyncMustBeAwaited', - }, { column: 9, endColumn: 43, @@ -523,12 +508,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });', errors: [ - { - column: 22, - endColumn: 82, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { column: 30, endColumn: 79, @@ -542,12 +521,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -562,12 +535,6 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toResolve(); });', options: [{ asyncMatchers: undefined }], errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -581,12 +548,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).toReject(); });', errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -600,12 +561,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).not.toReject(); });', errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -620,12 +575,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });', errors: [ - { - column: 22, - endColumn: 86, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { column: 30, endColumn: 83, @@ -640,12 +589,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.toBeDefined(); });', errors: [ - { - column: 22, - endColumn: 81, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { column: 30, endColumn: 78, @@ -660,12 +603,6 @@ ruleTester.run('valid-expect', rule, { output: 'test("valid-expect", async () => { await expect(Promise.resolve(2)).rejects.not.toBeDefined(); });', errors: [ - { - column: 22, - endColumn: 85, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { column: 30, endColumn: 82, @@ -707,11 +644,6 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.reject(2)).toRejectWith(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -725,11 +657,6 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await expect(Promise.reject(2)).rejects.toBe(2); });', options: [{ asyncMatchers: ['toRejectWith'] }], errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -911,13 +838,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 2, column: 3, @@ -939,13 +859,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 2, column: 3, @@ -988,13 +901,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 2, column: 3, @@ -1018,12 +924,6 @@ ruleTester.run('valid-expect', rule, { `, options: [{ alwaysAwait: true }], errors: [ - { - line: 1, - column: 22, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - }, { line: 2, column: 3, @@ -1051,14 +951,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endLine: 6, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 2, column: 3, @@ -1088,14 +980,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endLine: 6, - endColumn: 2, - messageId: 'promisesWithAsyncAssertionsMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 2, column: 3, @@ -1124,22 +1008,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 1, - column: 22, - endLine: 6, - endColumn: 2, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, - { - line: 1, - column: 22, - endLine: 6, - endColumn: 2, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 3, column: 5, @@ -1170,24 +1038,12 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - await expect(Promise.resolve(2)).toResolve(), - await expect(Promise.resolve(3)).toReject(), + expect(Promise.resolve(2)).toResolve(), + expect(Promise.resolve(3)).toReject(), ] }); `, errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -1220,18 +1076,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, - { - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - column: 22, - line: 1, - }, { messageId: 'asyncMustBeAwaited', data: { orReturned: ' or returned' }, @@ -1272,14 +1116,6 @@ ruleTester.run('valid-expect', rule, { }); `, errors: [ - { - line: 2, - column: 71, - endLine: 4, - endColumn: 4, - messageId: 'asyncMustBeAwaited', - data: { orReturned: ' or returned' }, - }, { line: 3, column: 5, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index e5920fc06..784993f37 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -4,6 +4,7 @@ */ import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; +import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; import { type FunctionExpression, ModifierName, @@ -361,25 +362,6 @@ export default createRule<[Options], MessageIds>({ // if we didn't warn user already !promiseArrayExceptionExists(finalNode.loc) ) { - const functionExpression = findFirstFunctionExpression(finalNode); - - if (functionExpression && !functionExpression.async) { - context.report({ - loc: functionExpression.loc, - data: { orReturned }, - messageId: - finalNode === targetNode - ? 'asyncMustBeAwaited' - : 'promisesWithAsyncAssertionsMustBeAwaited', - node, - fix(fixer) { - const targetFunction = - getNormalizeFunctionExpression(functionExpression); - - return fixer.insertTextBefore(targetFunction, 'async '); - }, - }); - } context.report({ loc: finalNode.loc, data: { orReturned }, @@ -389,10 +371,20 @@ export default createRule<[Options], MessageIds>({ : 'promisesWithAsyncAssertionsMustBeAwaited', node, fix(fixer) { + const functionExpression = findFirstFunctionExpression(finalNode); + if (!functionExpression) { - return null; + return []; } + const fixes: RuleFix[] = []; + + if (!functionExpression.async) { + const targetFunction = + getNormalizeFunctionExpression(functionExpression); + + fixes.push(fixer.insertTextBefore(targetFunction, 'async ')); + } const returnStatement = finalNode.parent.type === AST_NODE_TYPES.ReturnStatement ? finalNode.parent @@ -403,10 +395,13 @@ export default createRule<[Options], MessageIds>({ getSourceCode(context).getText(returnStatement); const replacedText = sourceCodeText.replace('return', 'await'); - return fixer.replaceText(returnStatement, replacedText); + return [ + ...fixes, + fixer.replaceText(returnStatement, replacedText), + ]; } - return fixer.insertTextBefore(finalNode, 'await '); + return [...fixes, fixer.insertTextBefore(finalNode, 'await ')]; }, }); From ce98868eff92cc7a66781a9064dba3f65b702676 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Thu, 23 May 2024 23:18:24 +0900 Subject: [PATCH 09/11] fix: valid-expect fixer logic --- src/rules/__tests__/valid-expect.test.ts | 4 +- src/rules/valid-expect.ts | 62 +++++++++++++++--------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/rules/__tests__/valid-expect.test.ts b/src/rules/__tests__/valid-expect.test.ts index 351eee7cb..f30bafc66 100644 --- a/src/rules/__tests__/valid-expect.test.ts +++ b/src/rules/__tests__/valid-expect.test.ts @@ -1038,8 +1038,8 @@ ruleTester.run('valid-expect', rule, { output: dedent` test("valid-expect", async () => { const assertions = [ - expect(Promise.resolve(2)).toResolve(), - expect(Promise.resolve(3)).toReject(), + await expect(Promise.resolve(2)).toResolve(), + await expect(Promise.resolve(3)).toReject(), ] }); `, diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 784993f37..0d973ce37 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -205,6 +205,13 @@ export default createRule<[Options], MessageIds>({ ) { // Context state const arrayExceptions = new Set(); + const descriptors: Array<{ + node: TSESTree.Node; + messageId: Extract< + MessageIds, + 'asyncMustBeAwaited' | 'promisesWithAsyncAssertionsMustBeAwaited' + >; + }> = []; const pushPromiseArrayException = (loc: TSESTree.SourceLocation) => arrayExceptions.add(promiseArrayExceptionKey(loc)); @@ -336,7 +343,7 @@ export default createRule<[Options], MessageIds>({ jestFnCall.modifiers.some(nod => getAccessorValue(nod) !== 'not') || asyncMatchers.includes(getAccessorValue(matcher)); - if (!parentNode?.parent || !shouldBeAwaited) { + if (!parentNode.parent || !shouldBeAwaited) { return; } /** @@ -345,7 +352,6 @@ export default createRule<[Options], MessageIds>({ */ const isParentArrayExpression = parentNode.parent.type === AST_NODE_TYPES.ArrayExpression; - const orReturned = alwaysAwait ? '' : ' or returned'; /** * An async assertion can be chained with `then` or `catch` statements. * In that case our target CallExpression node is the one with @@ -362,32 +368,47 @@ export default createRule<[Options], MessageIds>({ // if we didn't warn user already !promiseArrayExceptionExists(finalNode.loc) ) { - context.report({ - loc: finalNode.loc, - data: { orReturned }, + descriptors.push({ + node: finalNode, messageId: - finalNode === targetNode + targetNode === finalNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited', + }); + } + if (isParentArrayExpression) { + pushPromiseArrayException(finalNode.loc); + } + }, + 'Program:exit'() { + const fixes: RuleFix[] = []; + + descriptors.forEach(({ node, messageId }, index) => { + const orReturned = alwaysAwait ? '' : ' or returned'; + + context.report({ + loc: node.loc, + data: { orReturned }, + messageId, node, fix(fixer) { - const functionExpression = findFirstFunctionExpression(finalNode); + const functionExpression = findFirstFunctionExpression(node); if (!functionExpression) { - return []; + return null; } + const foundAsyncFixer = fixes.some(fix => fix.text === 'async '); - const fixes: RuleFix[] = []; - - if (!functionExpression.async) { + if (!functionExpression.async && !foundAsyncFixer) { const targetFunction = getNormalizeFunctionExpression(functionExpression); fixes.push(fixer.insertTextBefore(targetFunction, 'async ')); } + const returnStatement = - finalNode.parent.type === AST_NODE_TYPES.ReturnStatement - ? finalNode.parent + node.parent?.type === AST_NODE_TYPES.ReturnStatement + ? node.parent : null; if (alwaysAwait && returnStatement) { @@ -395,20 +416,15 @@ export default createRule<[Options], MessageIds>({ getSourceCode(context).getText(returnStatement); const replacedText = sourceCodeText.replace('return', 'await'); - return [ - ...fixes, - fixer.replaceText(returnStatement, replacedText), - ]; + fixes.push(fixer.replaceText(returnStatement, replacedText)); + } else { + fixes.push(fixer.insertTextBefore(node, 'await ')); } - return [...fixes, fixer.insertTextBefore(finalNode, 'await ')]; + return index === descriptors.length - 1 ? fixes : null; }, }); - - if (isParentArrayExpression) { - pushPromiseArrayException(finalNode.loc); - } - } + }); }, }; }, From 3b27d09620a9c93b20d7366aba049bf24dc63064 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 4 Jun 2024 21:19:15 +0900 Subject: [PATCH 10/11] refactor: fix import --- src/rules/valid-expect.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 0d973ce37..16bf1daa9 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -3,8 +3,11 @@ * MIT license, Tom Vincent. */ -import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; -import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; +import { + AST_NODE_TYPES, + type TSESLint, + type TSESTree, +} from '@typescript-eslint/utils'; import { type FunctionExpression, ModifierName, @@ -381,7 +384,7 @@ export default createRule<[Options], MessageIds>({ } }, 'Program:exit'() { - const fixes: RuleFix[] = []; + const fixes: TSESLint.RuleFix[] = []; descriptors.forEach(({ node, messageId }, index) => { const orReturned = alwaysAwait ? '' : ' or returned'; From 9f950598b54f03c24ba803423311ed9bc37485b6 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 4 Jun 2024 21:30:26 +0900 Subject: [PATCH 11/11] fix: write format --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a14bbd5f5..91d0f1022 100644 --- a/README.md +++ b/README.md @@ -307,10 +307,15 @@ enabled in.\ set to warn in.\ ✅ Set in the `recommended` [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\ -🎨 Set in the `style` [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\ -🔧 Automatically fixable by the +🎨 +Set in the `style` +[configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\ +🔧 +Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ -💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). +💡 +Manually fixable by +[editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). | Name                          | Description | 💼 | ⚠️ | 🔧 | 💡 | | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :-- | :-- | :-- | :-- |