From cf3cfbfdc10095d7bd7e663d1f0e71f6f883576d Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 15 Aug 2019 13:58:23 +1200 Subject: [PATCH] chore(prefer-to-be-undefined): convert to typescript --- ...test.js => prefer-to-be-undefined.test.ts} | 5 +- src/rules/prefer-to-be-undefined.js | 54 ------------ src/rules/prefer-to-be-undefined.ts | 75 ++++++++++++++++ src/rules/util.js | 88 ------------------- 4 files changed, 78 insertions(+), 144 deletions(-) rename src/rules/__tests__/{prefer-to-be-undefined.test.js => prefer-to-be-undefined.test.ts} (90%) delete mode 100644 src/rules/prefer-to-be-undefined.js create mode 100644 src/rules/prefer-to-be-undefined.ts diff --git a/src/rules/__tests__/prefer-to-be-undefined.test.js b/src/rules/__tests__/prefer-to-be-undefined.test.ts similarity index 90% rename from src/rules/__tests__/prefer-to-be-undefined.test.js rename to src/rules/__tests__/prefer-to-be-undefined.test.ts index f75767a29..5e1134b56 100644 --- a/src/rules/__tests__/prefer-to-be-undefined.test.js +++ b/src/rules/__tests__/prefer-to-be-undefined.test.ts @@ -1,7 +1,7 @@ -import { RuleTester } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../prefer-to-be-undefined'; -const ruleTester = new RuleTester(); +const ruleTester = new TSESLint.RuleTester(); ruleTester.run('prefer-to-be-undefined', rule, { valid: [ @@ -9,6 +9,7 @@ ruleTester.run('prefer-to-be-undefined', rule, { 'expect(true).not.toBeUndefined();', 'expect({}).toEqual({});', 'expect(null).toEqual(null);', + 'expect(something).toBe()', 'expect(something).toBe(somethingElse)', 'expect(something).toEqual(somethingElse)', 'expect(something).not.toBe(somethingElse)', diff --git a/src/rules/prefer-to-be-undefined.js b/src/rules/prefer-to-be-undefined.js deleted file mode 100644 index 1aff3f98e..000000000 --- a/src/rules/prefer-to-be-undefined.js +++ /dev/null @@ -1,54 +0,0 @@ -import { - argument, - argument2, - expectNotToBeCase, - expectNotToEqualCase, - expectToBeCase, - expectToEqualCase, - getDocsUrl, - method, - method2, -} from './util'; - -export default { - meta: { - docs: { - url: getDocsUrl(__filename), - }, - messages: { - useToBeUndefined: 'Use toBeUndefined() instead', - }, - fixable: 'code', - schema: [], - }, - create(context) { - return { - CallExpression(node) { - const is = - expectToBeCase(node, undefined) || expectToEqualCase(node, undefined); - const isNot = - expectNotToEqualCase(node, undefined) || - expectNotToBeCase(node, undefined); - - if (is || isNot) { - context.report({ - fix(fixer) { - if (is) { - return [ - fixer.replaceText(method(node), 'toBeUndefined'), - fixer.remove(argument(node)), - ]; - } - return [ - fixer.replaceText(method2(node), 'toBeUndefined'), - fixer.remove(argument2(node)), - ]; - }, - messageId: 'useToBeUndefined', - node: is ? method(node) : method2(node), - }); - } - }, - }; - }, -}; diff --git a/src/rules/prefer-to-be-undefined.ts b/src/rules/prefer-to-be-undefined.ts new file mode 100644 index 000000000..d4c196a81 --- /dev/null +++ b/src/rules/prefer-to-be-undefined.ts @@ -0,0 +1,75 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import { + ParsedEqualityMatcherCall, + ParsedExpectMatcher, + createRule, + isExpectCall, + isParsedEqualityMatcherCall, + parseExpectCall, +} from './tsUtils'; + +interface UndefinedIdentifier extends TSESTree.Identifier { + name: 'undefined'; +} + +const isUndefinedIdentifier = ( + node: TSESTree.Node, +): node is UndefinedIdentifier => + node.type === AST_NODE_TYPES.Identifier && node.name === 'undefined'; + +/** + * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * with a `undefined` identifier as the sole argument. + * + * @param {ParsedExpectMatcher} matcher + * + * @return {matcher is ParsedEqualityMatcherCall} + */ +const isUndefinedEqualityMatcher = ( + matcher: ParsedExpectMatcher, +): matcher is ParsedEqualityMatcherCall => + isParsedEqualityMatcherCall(matcher) && + isUndefinedIdentifier(matcher.arguments[0]); + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Suggest using `toBeUndefined()`', + recommended: false, + }, + messages: { + useToBeUndefined: 'Use toBeUndefined() instead', + }, + fixable: 'code', + type: 'suggestion', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + CallExpression(node) { + if (!isExpectCall(node)) { + return; + } + + const { matcher } = parseExpectCall(node); + + if (matcher && isUndefinedEqualityMatcher(matcher)) { + context.report({ + fix: fixer => [ + fixer.replaceText(matcher.node.property, 'toBeUndefined'), + fixer.remove(matcher.arguments[0]), + ], + messageId: 'useToBeUndefined', + node: matcher.node.property, + }); + } + }, + }; + }, +}); diff --git a/src/rules/util.js b/src/rules/util.js index 42418800f..20540da90 100644 --- a/src/rules/util.js +++ b/src/rules/util.js @@ -1,90 +1,2 @@ -import { basename } from 'path'; -import { version } from '../../package.json'; - -const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest'; - -export const expectCase = node => - node && node.callee && node.callee.name === 'expect'; - -export const expectCaseWithParent = node => - expectCase(node) && - node.parent && - node.parent.type === 'MemberExpression' && - node.parent.parent; - -export const expectNotCase = node => - expectCase(node) && - node.parent.parent.type === 'MemberExpression' && - methodName(node) === 'not'; - -export const expectResolvesCase = node => - expectCase(node) && - node.parent.parent.type === 'MemberExpression' && - methodName(node) === 'resolves'; - -export const expectRejectsCase = node => - expectCase(node) && - node.parent.parent.type === 'MemberExpression' && - methodName(node) === 'rejects'; - -export const expectToBeCase = (node, arg) => - !( - expectNotCase(node) || - expectResolvesCase(node) || - expectRejectsCase(node) - ) && - expectCase(node) && - methodName(node) === 'toBe' && - argument(node) && - (argument(node).name === 'undefined' && arg === undefined); - -export const expectNotToBeCase = (node, arg) => - expectNotCase(node) && - methodName2(node) === 'toBe' && - argument2(node) && - (argument2(node).name === 'undefined' && arg === undefined); - -export const expectToEqualCase = (node, arg) => - !( - expectNotCase(node) || - expectResolvesCase(node) || - expectRejectsCase(node) - ) && - expectCase(node) && - methodName(node) === 'toEqual' && - argument(node) && - (argument(node).name === 'undefined' && arg === undefined); - -export const expectNotToEqualCase = (node, arg) => - expectNotCase(node) && - methodName2(node) === 'toEqual' && - argument2(node) && - (argument2(node).name === 'undefined' && arg === undefined); - -export const method = node => node.parent.property; - -export const method2 = node => node.parent.parent.property; - -const methodName = node => method(node) && method(node).name; - -const methodName2 = node => method2(node) && method2(node).name; - export const argument = node => node.parent.parent.arguments && node.parent.parent.arguments[0]; - -export const argument2 = node => - node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0]; - -/** - * Generates the URL to documentation for the given rule name. It uses the - * package version to build the link to a tagged version of the - * documentation file. - * - * @param {string} filename - Name of the eslint rule - * @returns {string} URL to the documentation for the given rule - */ -export const getDocsUrl = filename => { - const ruleName = basename(filename, '.js'); - - return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`; -};