diff --git a/CHANGELOG.md b/CHANGELOG.md index 013b611055fe..711ae4c4858d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * `[jest-config]` Include error message for `preset` json ([#4766](https://github.com/facebook/jest/pull/4766)) ### Features +* `[eslint-plugin-jest]` Add `prefer-to-have-length` lint rule. ([#4771](https://github.com/facebook/jest/pull/4771)) * `[jest-environment-jsdom]` [**BREAKING**] Upgrade to JSDOM@11 ([#4770](https://github.com/facebook/jest/pull/4770)) * `[jest-environment-*]` [**BREAKING**] Add Async Test Environment APIs, dispose is now teardown ([#4506](https://github.com/facebook/jest/pull/4506)) * `[jest-cli]` Add an option to clear the cache ([#4430](https://github.com/facebook/jest/pull/4430)) diff --git a/packages/eslint-plugin-jest/docs/rules/prefer-to-have-length.md b/packages/eslint-plugin-jest/docs/rules/prefer-to-have-length.md new file mode 100644 index 000000000000..0e86f89e12bc --- /dev/null +++ b/packages/eslint-plugin-jest/docs/rules/prefer-to-have-length.md @@ -0,0 +1,27 @@ +# Suggest using `toHaveLength()` (prefer-to-have-length) + +In order to have a better failure message, `toHaveLength()` should be used upon asserting expections on object's length property. + +## Rule details + +This rule triggers a warning if `toBe()` is used to assert object's length property. + +```js +expect(files.length).toBe(1); +``` + +This rule is enabled by default. + +### Default configuration + +The following pattern is considered warning: + +```js +expect(files.length).toBe(1); +``` + +The following pattern is not warning: + +```js +expect(files).toHaveLength(1); +``` diff --git a/packages/eslint-plugin-jest/src/index.js b/packages/eslint-plugin-jest/src/index.js index 18999cbfa8fd..83b270b3ca10 100644 --- a/packages/eslint-plugin-jest/src/index.js +++ b/packages/eslint-plugin-jest/src/index.js @@ -10,6 +10,7 @@ import noDisabledTests from './rules/no_disabled_tests'; import noFocusedTests from './rules/no_focused_tests'; import noIdenticalTitle from './rules/no_identical_title'; +import preferToHaveLength from './rules/prefer_to_have_length'; import validExpect from './rules/valid_expect'; module.exports = { @@ -19,6 +20,7 @@ module.exports = { 'jest/no-disabled-tests': 'warn', 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', + 'jest/prefer-to-have-length': 'warn', 'jest/valid-expect': 'error', }, }, @@ -50,6 +52,7 @@ module.exports = { 'no-disabled-tests': noDisabledTests, 'no-focused-tests': noFocusedTests, 'no-identical-title': noIdenticalTitle, + 'prefer-to-have-length': preferToHaveLength, 'valid-expect': validExpect, }, }; diff --git a/packages/eslint-plugin-jest/src/rules/__tests__/prefer_to_have_length.test.js b/packages/eslint-plugin-jest/src/rules/__tests__/prefer_to_have_length.test.js new file mode 100644 index 000000000000..d3f510acff5c --- /dev/null +++ b/packages/eslint-plugin-jest/src/rules/__tests__/prefer_to_have_length.test.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/* eslint-disable sort-keys */ + +'use strict'; + +import {RuleTester} from 'eslint'; +const {rules} = require('../../'); + +const ruleTester = new RuleTester(); + +ruleTester.run('prefer_to_have_length', rules['prefer-to-have-length'], { + valid: ['expect(files).toHaveLength(1);', "expect(files.name).toBe('file');"], + + invalid: [ + { + code: 'expect(files.length).toBe(1);', + errors: [ + { + message: 'Use toHaveLength() instead', + column: 22, + line: 1, + }, + ], + output: 'expect(files).toHaveLength(1);', + }, + ], +}); diff --git a/packages/eslint-plugin-jest/src/rules/prefer_to_have_length.js b/packages/eslint-plugin-jest/src/rules/prefer_to_have_length.js new file mode 100644 index 000000000000..725468cab08b --- /dev/null +++ b/packages/eslint-plugin-jest/src/rules/prefer_to_have_length.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {EslintContext, CallExpression} from './types'; + +export default (context: EslintContext) => { + return { + CallExpression(node: CallExpression) { + const calleeName = node.callee.name; + + if ( + calleeName === 'expect' && + node.arguments.length == 1 && + node.parent && + node.parent.type === 'MemberExpression' && + node.parent.parent + ) { + const parentProperty = node.parent.property; + const propertyName = parentProperty.name; + const argumentObject = node.arguments[0].object; + const argumentProperty = node.arguments[0].property; + + if (propertyName === 'toBe' && argumentProperty.name === 'length') { + // $FlowFixMe + const propertyDot = context + .getSourceCode() + .getFirstTokenBetween( + argumentObject, + argumentProperty, + token => token.value === '.', + ); + context.report({ + fix(fixer) { + return [ + fixer.remove(propertyDot), + fixer.remove(argumentProperty), + fixer.replaceText(parentProperty, 'toHaveLength'), + ]; + }, + message: 'Use toHaveLength() instead', + node: parentProperty, + }); + } + } + }, + }; +}; diff --git a/packages/eslint-plugin-jest/src/rules/types.js b/packages/eslint-plugin-jest/src/rules/types.js index ec2f39a8ef4b..393ba04a6419 100644 --- a/packages/eslint-plugin-jest/src/rules/types.js +++ b/packages/eslint-plugin-jest/src/rules/types.js @@ -44,6 +44,8 @@ export type Literal = { value?: string, rawValue?: string, parent: ParentNode, + property: Identifier, + object: Identifier, loc: NodeLocation, };