From 5ecd4dd080dbbcbdb7d75da31e90d506959f83f5 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 29 May 2022 11:48:43 +1200 Subject: [PATCH 1/6] fix(prefer-todo): support fixing indexed property access --- src/rules/__tests__/prefer-todo.test.ts | 10 ++++++++++ src/rules/prefer-todo.ts | 20 +++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/rules/__tests__/prefer-todo.test.ts b/src/rules/__tests__/prefer-todo.test.ts index b4695c6e7..443298692 100644 --- a/src/rules/__tests__/prefer-todo.test.ts +++ b/src/rules/__tests__/prefer-todo.test.ts @@ -58,5 +58,15 @@ ruleTester.run('prefer-todo', rule, { output: 'test.todo("i need to write this test");', errors: [{ messageId: 'emptyTest' }], }, + { + code: `test["skip"]("i need to write this test", function() {});`, + output: 'test[\'todo\']("i need to write this test");', + errors: [{ messageId: 'emptyTest' }], + }, + { + code: `test[\`skip\`]("i need to write this test", function() {});`, + output: 'test[\'todo\']("i need to write this test");', + errors: [{ messageId: 'emptyTest' }], + }, ], }); diff --git a/src/rules/prefer-todo.ts b/src/rules/prefer-todo.ts index 31e81248b..683b5f97b 100644 --- a/src/rules/prefer-todo.ts +++ b/src/rules/prefer-todo.ts @@ -23,20 +23,18 @@ function createTodoFixer( jestFnCall: ParsedJestFnCall, fixer: TSESLint.RuleFixer, ) { - const fixes = [ - fixer.replaceText(jestFnCall.head.node, `${jestFnCall.head.local}.todo`), - ]; - if (jestFnCall.members.length) { - fixes.unshift( - fixer.removeRange([ - jestFnCall.head.node.range[1], - jestFnCall.members[0].range[1], - ]), - ); + const text = + jestFnCall.members[0].type === AST_NODE_TYPES.Identifier + ? 'todo' + : "'todo'"; + + return [fixer.replaceText(jestFnCall.members[0], text)]; } - return fixes; + return [ + fixer.replaceText(jestFnCall.head.node, `${jestFnCall.head.local}.todo`), + ]; } const isTargetedTestCase = (jestFnCall: ParsedJestFnCall): boolean => { From db4246840f94b58862a2b2e73929c6f8ffa38f33 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 29 May 2022 11:54:57 +1200 Subject: [PATCH 2/6] fix(prefer-to-be): support fixing indexed property access --- src/rules/__tests__/prefer-to-be.test.ts | 35 ++++++++++++++++++++++++ src/rules/prefer-to-be.ts | 8 ++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index 0f617f973..df0286901 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -45,6 +45,11 @@ ruleTester.run('prefer-to-be', rule, { output: 'expect(value).toBe(`my string`);', errors: [{ messageId: 'useToBe', column: 15, line: 1 }], }, + { + code: 'expect(value)["toEqual"](`my string`);', + output: "expect(value)['toBe'](`my string`);", + errors: [{ messageId: 'useToBe', column: 15, line: 1 }], + }, { code: 'expect(value).toStrictEqual(`my ${string}`);', output: 'expect(value).toBe(`my ${string}`);', @@ -55,6 +60,16 @@ ruleTester.run('prefer-to-be', rule, { output: 'expect(loadMessage()).resolves.toBe("hello world");', errors: [{ messageId: 'useToBe', column: 32, line: 1 }], }, + { + code: 'expect(loadMessage()).resolves["toStrictEqual"]("hello world");', + output: 'expect(loadMessage()).resolves[\'toBe\']("hello world");', + errors: [{ messageId: 'useToBe', column: 32, line: 1 }], + }, + { + code: 'expect(loadMessage())["resolves"].toStrictEqual("hello world");', + output: 'expect(loadMessage())["resolves"].toBe("hello world");', + errors: [{ messageId: 'useToBe', column: 35, line: 1 }], + }, { code: 'expect(loadMessage()).resolves.toStrictEqual(false);', output: 'expect(loadMessage()).resolves.toBe(false);', @@ -103,6 +118,16 @@ ruleTester.run('prefer-to-be: null', rule, { output: 'expect("a string").not.toBeNull();', errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], }, + { + code: 'expect("a string").not["toBe"](null);', + output: 'expect("a string").not[\'toBeNull\']();', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + }, + { + code: 'expect("a string")["not"]["toBe"](null);', + output: 'expect("a string")["not"][\'toBeNull\']();', + errors: [{ messageId: 'useToBeNull', column: 27, line: 1 }], + }, { code: 'expect("a string").not.toEqual(null);', output: 'expect("a string").not.toBeNull();', @@ -156,6 +181,11 @@ ruleTester.run('prefer-to-be: undefined', rule, { output: 'expect("a string").rejects.toBeDefined();', errors: [{ messageId: 'useToBeDefined', column: 32, line: 1 }], }, + { + code: 'expect("a string").rejects.not["toBe"](undefined);', + output: 'expect("a string").rejects[\'toBeDefined\']();', + errors: [{ messageId: 'useToBeDefined', column: 32, line: 1 }], + }, { code: 'expect("a string").not.toEqual(undefined);', output: 'expect("a string").toBeDefined();', @@ -208,6 +238,11 @@ ruleTester.run('prefer-to-be: NaN', rule, { output: 'expect("a string").rejects.not.toBeNaN();', errors: [{ messageId: 'useToBeNaN', column: 32, line: 1 }], }, + { + code: 'expect("a string")["rejects"].not.toBe(NaN);', + output: 'expect("a string")["rejects"].not.toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 35, line: 1 }], + }, { code: 'expect("a string").not.toEqual(NaN);', output: 'expect("a string").not.toBeNaN();', diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index e8bbb3f33..ff1484b81 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -69,9 +69,11 @@ const reportPreferToBe = ( context.report({ messageId: `useToBe${whatToBe}`, fix(fixer) { - const fixes = [ - fixer.replaceText(matcher.node.property, `toBe${whatToBe}`), - ]; + const r = `toBe${whatToBe}`; + const text = + matcher.node.property.type === AST_NODE_TYPES.Identifier ? r : `'${r}'`; + + const fixes = [fixer.replaceText(matcher.node.property, text)]; if (matcher.arguments?.length && whatToBe !== '') { fixes.push(fixer.remove(matcher.arguments[0])); From 1d0bd686ca3b5e48f962929f0221842cfa70ea8e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 29 May 2022 11:59:45 +1200 Subject: [PATCH 3/6] fix(no-alias-methods): support fixing indexed property access --- src/rules/__tests__/no-alias-methods.test.ts | 15 +++++++++++++++ src/rules/no-alias-methods.ts | 10 +++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/rules/__tests__/no-alias-methods.test.ts b/src/rules/__tests__/no-alias-methods.test.ts index e9312d904..22b1bf954 100644 --- a/src/rules/__tests__/no-alias-methods.test.ts +++ b/src/rules/__tests__/no-alias-methods.test.ts @@ -231,5 +231,20 @@ ruleTester.run('no-alias-methods', rule, { }, ], }, + { + code: 'expect(a).not["toThrowError"]()', + output: "expect(a).not['toThrow']()", + errors: [ + { + messageId: 'replaceAlias', + data: { + alias: 'toThrowError', + canonical: 'toThrow', + }, + column: 15, + line: 1, + }, + ], + }, ], }); diff --git a/src/rules/no-alias-methods.ts b/src/rules/no-alias-methods.ts index 1055053dd..ed09fcd2b 100644 --- a/src/rules/no-alias-methods.ts +++ b/src/rules/no-alias-methods.ts @@ -1,3 +1,4 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isExpectCall, parseExpectCall } from './utils'; export default createRule({ @@ -56,7 +57,14 @@ export default createRule({ canonical, }, node: matcher.node.property, - fix: fixer => [fixer.replaceText(matcher.node.property, canonical)], + fix: fixer => [ + fixer.replaceText( + matcher.node.property, + matcher.node.property.type === AST_NODE_TYPES.Identifier + ? canonical + : `'${canonical}'`, + ), + ], }); } }, From a55f016a3b7f6708ec1de7eaf66b0d9bcecaa850 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 May 2022 06:43:39 +1200 Subject: [PATCH 4/6] fix(prefer-strict-equal): support fixing indexed property access --- src/rules/__tests__/prefer-strict-equal.test.ts | 16 ++++++++++++++++ src/rules/prefer-strict-equal.ts | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/rules/__tests__/prefer-strict-equal.test.ts b/src/rules/__tests__/prefer-strict-equal.test.ts index bc0d5ab2e..fa1e16908 100644 --- a/src/rules/__tests__/prefer-strict-equal.test.ts +++ b/src/rules/__tests__/prefer-strict-equal.test.ts @@ -26,5 +26,21 @@ ruleTester.run('prefer-strict-equal', rule, { }, ], }, + { + code: 'expect(something)["toEqual"](somethingElse);', + errors: [ + { + messageId: 'useToStrictEqual', + column: 19, + line: 1, + suggestions: [ + { + messageId: 'suggestReplaceWithStrictEqual', + output: "expect(something)['toStrictEqual'](somethingElse);", + }, + ], + }, + ], + }, ], }); diff --git a/src/rules/prefer-strict-equal.ts b/src/rules/prefer-strict-equal.ts index e4b6c1fde..182e834b9 100644 --- a/src/rules/prefer-strict-equal.ts +++ b/src/rules/prefer-strict-equal.ts @@ -1,3 +1,4 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { EqualityMatcher, createRule, @@ -46,7 +47,9 @@ export default createRule({ fix: fixer => [ fixer.replaceText( matcher.node.property, - EqualityMatcher.toStrictEqual, + matcher.node.property.type === AST_NODE_TYPES.Identifier + ? EqualityMatcher.toStrictEqual + : `'${EqualityMatcher.toStrictEqual}'`, ), ], }, From 395ff634e2522a247921d4235636c9f1391c4a90 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 May 2022 07:00:01 +1200 Subject: [PATCH 5/6] refactor: use helper for wrapping text in quotes --- src/rules/no-alias-methods.ts | 15 +++++++-------- src/rules/prefer-strict-equal.ts | 9 ++++----- src/rules/prefer-to-be.ts | 9 ++++----- src/rules/prefer-todo.ts | 8 ++------ src/rules/utils/misc.ts | 17 +++++++++++++++++ 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/rules/no-alias-methods.ts b/src/rules/no-alias-methods.ts index ed09fcd2b..395f13dae 100644 --- a/src/rules/no-alias-methods.ts +++ b/src/rules/no-alias-methods.ts @@ -1,5 +1,9 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, isExpectCall, parseExpectCall } from './utils'; +import { + createRule, + isExpectCall, + parseExpectCall, + replaceAccessorFixer, +} from './utils'; export default createRule({ name: __filename, @@ -58,12 +62,7 @@ export default createRule({ }, node: matcher.node.property, fix: fixer => [ - fixer.replaceText( - matcher.node.property, - matcher.node.property.type === AST_NODE_TYPES.Identifier - ? canonical - : `'${canonical}'`, - ), + replaceAccessorFixer(fixer, matcher.node.property, canonical), ], }); } diff --git a/src/rules/prefer-strict-equal.ts b/src/rules/prefer-strict-equal.ts index 182e834b9..ef2f792dc 100644 --- a/src/rules/prefer-strict-equal.ts +++ b/src/rules/prefer-strict-equal.ts @@ -1,10 +1,10 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { EqualityMatcher, createRule, isExpectCall, isParsedEqualityMatcherCall, parseExpectCall, + replaceAccessorFixer, } from './utils'; export default createRule({ @@ -45,11 +45,10 @@ export default createRule({ { messageId: 'suggestReplaceWithStrictEqual', fix: fixer => [ - fixer.replaceText( + replaceAccessorFixer( + fixer, matcher.node.property, - matcher.node.property.type === AST_NODE_TYPES.Identifier - ? EqualityMatcher.toStrictEqual - : `'${EqualityMatcher.toStrictEqual}'`, + EqualityMatcher.toStrictEqual, ), ], }, diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index ff1484b81..e25a9533f 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -12,6 +12,7 @@ import { isIdentifier, isParsedEqualityMatcherCall, parseExpectCall, + replaceAccessorFixer, } from './utils'; const isNullLiteral = (node: TSESTree.Node): node is TSESTree.NullLiteral => @@ -69,11 +70,9 @@ const reportPreferToBe = ( context.report({ messageId: `useToBe${whatToBe}`, fix(fixer) { - const r = `toBe${whatToBe}`; - const text = - matcher.node.property.type === AST_NODE_TYPES.Identifier ? r : `'${r}'`; - - const fixes = [fixer.replaceText(matcher.node.property, text)]; + const fixes = [ + replaceAccessorFixer(fixer, matcher.node.property, `toBe${whatToBe}`), + ]; if (matcher.arguments?.length && whatToBe !== '') { fixes.push(fixer.remove(matcher.arguments[0])); diff --git a/src/rules/prefer-todo.ts b/src/rules/prefer-todo.ts index 683b5f97b..973495446 100644 --- a/src/rules/prefer-todo.ts +++ b/src/rules/prefer-todo.ts @@ -7,6 +7,7 @@ import { isFunction, isStringNode, parseJestFnCall, + replaceAccessorFixer, } from './utils'; function isEmptyFunction(node: TSESTree.CallExpressionArgument) { @@ -24,12 +25,7 @@ function createTodoFixer( fixer: TSESLint.RuleFixer, ) { if (jestFnCall.members.length) { - const text = - jestFnCall.members[0].type === AST_NODE_TYPES.Identifier - ? 'todo' - : "'todo'"; - - return [fixer.replaceText(jestFnCall.members[0], text)]; + return [replaceAccessorFixer(fixer, jestFnCall.members[0], 'todo')]; } return [ diff --git a/src/rules/utils/misc.ts b/src/rules/utils/misc.ts index 50646da3e..2039d0053 100644 --- a/src/rules/utils/misc.ts +++ b/src/rules/utils/misc.ts @@ -137,3 +137,20 @@ export const getTestCallExpressionsFromDeclaredVariables = ( [], ); }; + +/** + * Replaces an accessor node with the given `text`, surrounding it in quotes if required. + * + * This ensures that fixes produce valid code when replacing both dot-based and + * bracket-based property accessors. + */ +export const replaceAccessorFixer = ( + fixer: TSESLint.RuleFixer, + node: AccessorNode, + text: string, +) => { + return fixer.replaceText( + node, + node.type === AST_NODE_TYPES.Identifier ? text : `'${text}'`, + ); +}; From 595e570d528147101e683a19870f6c776183001e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 May 2022 07:11:30 +1200 Subject: [PATCH 6/6] refactor(prefer-todo): reduce code a little --- src/rules/prefer-todo.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rules/prefer-todo.ts b/src/rules/prefer-todo.ts index 973495446..0954ec9fd 100644 --- a/src/rules/prefer-todo.ts +++ b/src/rules/prefer-todo.ts @@ -25,12 +25,13 @@ function createTodoFixer( fixer: TSESLint.RuleFixer, ) { if (jestFnCall.members.length) { - return [replaceAccessorFixer(fixer, jestFnCall.members[0], 'todo')]; + return replaceAccessorFixer(fixer, jestFnCall.members[0], 'todo'); } - return [ - fixer.replaceText(jestFnCall.head.node, `${jestFnCall.head.local}.todo`), - ]; + return fixer.replaceText( + jestFnCall.head.node, + `${jestFnCall.head.local}.todo`, + ); } const isTargetedTestCase = (jestFnCall: ParsedJestFnCall): boolean => { @@ -85,7 +86,7 @@ export default createRule({ node, fix: fixer => [ fixer.removeRange([title.range[1], callback.range[1]]), - ...createTodoFixer(jestFnCall, fixer), + createTodoFixer(jestFnCall, fixer), ], }); }