Skip to content

Commit 8ba2da4

Browse files
xfumihirocpojer
authored andcommitted
Add prefer-to-have-length rule (#4771)
1 parent b38a897 commit 8ba2da4

File tree

5 files changed

+120
-0
lines changed

5 files changed

+120
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Suggest using `toHaveLength()` (prefer-to-have-length)
2+
3+
In order to have a better failure message, `toHaveLength()` should be used upon asserting expections on object's length property.
4+
5+
## Rule details
6+
7+
This rule triggers a warning if `toBe()` is used to assert object's length property.
8+
9+
```js
10+
expect(files.length).toBe(1);
11+
```
12+
13+
This rule is enabled by default.
14+
15+
### Default configuration
16+
17+
The following pattern is considered warning:
18+
19+
```js
20+
expect(files.length).toBe(1);
21+
```
22+
23+
The following pattern is not warning:
24+
25+
```js
26+
expect(files).toHaveLength(1);
27+
```

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import noDisabledTests from './rules/no_disabled_tests';
1111
import noFocusedTests from './rules/no_focused_tests';
1212
import noIdenticalTitle from './rules/no_identical_title';
13+
import preferToHaveLength from './rules/prefer_to_have_length';
1314
import validExpect from './rules/valid_expect';
1415

1516
module.exports = {
@@ -19,6 +20,7 @@ module.exports = {
1920
'jest/no-disabled-tests': 'warn',
2021
'jest/no-focused-tests': 'error',
2122
'jest/no-identical-title': 'error',
23+
'jest/prefer-to-have-length': 'warn',
2224
'jest/valid-expect': 'error',
2325
},
2426
},
@@ -50,6 +52,7 @@ module.exports = {
5052
'no-disabled-tests': noDisabledTests,
5153
'no-focused-tests': noFocusedTests,
5254
'no-identical-title': noIdenticalTitle,
55+
'prefer-to-have-length': preferToHaveLength,
5356
'valid-expect': validExpect,
5457
},
5558
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
/* eslint-disable sort-keys */
11+
12+
'use strict';
13+
14+
import {RuleTester} from 'eslint';
15+
const {rules} = require('../../');
16+
17+
const ruleTester = new RuleTester();
18+
19+
ruleTester.run('prefer_to_have_length', rules['prefer-to-have-length'], {
20+
valid: ['expect(files).toHaveLength(1);', "expect(files.name).toBe('file');"],
21+
22+
invalid: [
23+
{
24+
code: 'expect(files.length).toBe(1);',
25+
errors: [
26+
{
27+
message: 'Use toHaveLength() instead',
28+
column: 22,
29+
line: 1,
30+
},
31+
],
32+
output: 'expect(files).toHaveLength(1);',
33+
},
34+
],
35+
});

src/rules/prefer_to_have_length.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {EslintContext, CallExpression} from './types';
11+
12+
export default (context: EslintContext) => {
13+
return {
14+
CallExpression(node: CallExpression) {
15+
const calleeName = node.callee.name;
16+
17+
if (
18+
calleeName === 'expect' &&
19+
node.arguments.length == 1 &&
20+
node.parent &&
21+
node.parent.type === 'MemberExpression' &&
22+
node.parent.parent
23+
) {
24+
const parentProperty = node.parent.property;
25+
const propertyName = parentProperty.name;
26+
const argumentObject = node.arguments[0].object;
27+
const argumentProperty = node.arguments[0].property;
28+
29+
if (propertyName === 'toBe' && argumentProperty.name === 'length') {
30+
// $FlowFixMe
31+
const propertyDot = context
32+
.getSourceCode()
33+
.getFirstTokenBetween(
34+
argumentObject,
35+
argumentProperty,
36+
token => token.value === '.',
37+
);
38+
context.report({
39+
fix(fixer) {
40+
return [
41+
fixer.remove(propertyDot),
42+
fixer.remove(argumentProperty),
43+
fixer.replaceText(parentProperty, 'toHaveLength'),
44+
];
45+
},
46+
message: 'Use toHaveLength() instead',
47+
node: parentProperty,
48+
});
49+
}
50+
}
51+
},
52+
};
53+
};

src/rules/types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export type Literal = {
4444
value?: string,
4545
rawValue?: string,
4646
parent: ParentNode,
47+
property: Identifier,
48+
object: Identifier,
4749
loc: NodeLocation,
4850
};
4951

0 commit comments

Comments
 (0)