Skip to content

Commit 1675118

Browse files
authored
Add no-useless-fallback-in-spread rule (#1481)
1 parent 4162145 commit 1675118

File tree

7 files changed

+566
-0
lines changed

7 files changed

+566
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Forbid useless fallback when spreading in object literals
2+
3+
Spreading [falsy values](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) in object literals won't add any unexpected properties, so it's unnecessary to add an empty object as fallback.
4+
5+
This rule is fixable.
6+
7+
## Fail
8+
9+
```js
10+
const object = {...(foo || {})};
11+
```
12+
13+
```js
14+
const object = {...(foo ?? {})};
15+
```
16+
17+
## Pass
18+
19+
```js
20+
const object = {...foo};
21+
```
22+
23+
```js
24+
const object = {...(foo && {})};
25+
```
26+
27+
```js
28+
const array = [...(foo || [])];
29+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ module.exports = {
7878
'unicorn/no-unreadable-array-destructuring': 'error',
7979
'unicorn/no-unsafe-regex': 'off',
8080
'unicorn/no-unused-properties': 'off',
81+
'unicorn/no-useless-fallback-in-spread': 'error',
8182
'unicorn/no-useless-length-check': 'error',
8283
'unicorn/no-useless-spread': 'error',
8384
'unicorn/no-useless-undefined': 'error',

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Configure it in `package.json`.
7575
"unicorn/no-unreadable-array-destructuring": "error",
7676
"unicorn/no-unsafe-regex": "off",
7777
"unicorn/no-unused-properties": "off",
78+
"unicorn/no-useless-fallback-in-spread": "error",
7879
"unicorn/no-useless-length-check": "error",
7980
"unicorn/no-useless-spread": "error",
8081
"unicorn/no-useless-undefined": "error",
@@ -194,6 +195,7 @@ Each rule has emojis denoting:
194195
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. || 🔧 | |
195196
| [no-unsafe-regex](docs/rules/no-unsafe-regex.md) | Disallow unsafe regular expressions. | | | |
196197
| [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
198+
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Forbid useless fallback when spreading in object literals. || 🔧 | |
197199
| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. || 🔧 | |
198200
| [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. || 🔧 | |
199201
| [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. || 🔧 | |
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
const {matches} = require('./selectors/index.js');
3+
const {
4+
isParenthesized,
5+
getParenthesizedRange,
6+
getParentheses,
7+
} = require('./utils/parentheses.js');
8+
const shouldAddParenthesesToSpreadElementArgument = require('./utils/should-add-parentheses-to-spread-element-argument.js');
9+
10+
const MESSAGE_ID = 'no-useless-fallback-in-spread';
11+
const messages = {
12+
[MESSAGE_ID]: 'The empty object is useless.',
13+
};
14+
15+
const selector = [
16+
'ObjectExpression',
17+
' > ',
18+
'SpreadElement.properties',
19+
' > ',
20+
'LogicalExpression.argument',
21+
matches([
22+
'[operator="||"]',
23+
'[operator="??"]',
24+
]),
25+
' > ',
26+
'ObjectExpression[properties.length=0].right',
27+
].join('');
28+
29+
/** @param {import('eslint').Rule.RuleContext} context */
30+
const create = context => ({
31+
[selector](emptyObject) {
32+
return {
33+
node: emptyObject,
34+
messageId: MESSAGE_ID,
35+
/** @param {import('eslint').Rule.RuleFixer} fixer */
36+
* fix(fixer) {
37+
const sourceCode = context.getSourceCode();
38+
const logicalExpression = emptyObject.parent;
39+
const {left} = logicalExpression;
40+
const isLeftObjectParenthesized = isParenthesized(left, sourceCode);
41+
const [, start] = isLeftObjectParenthesized
42+
? getParenthesizedRange(left, sourceCode)
43+
: left.range;
44+
const [, end] = logicalExpression.range;
45+
46+
yield fixer.removeRange([start, end]);
47+
48+
if (
49+
isLeftObjectParenthesized
50+
|| !shouldAddParenthesesToSpreadElementArgument(left)
51+
) {
52+
const parentheses = getParentheses(logicalExpression, sourceCode);
53+
54+
for (const token of parentheses) {
55+
yield fixer.remove(token);
56+
}
57+
}
58+
},
59+
};
60+
},
61+
});
62+
63+
const schema = [];
64+
65+
module.exports = {
66+
create,
67+
meta: {
68+
type: 'suggestion',
69+
docs: {
70+
description: 'Forbid useless fallback when spreading in object literals.',
71+
},
72+
fixable: 'code',
73+
schema,
74+
messages,
75+
},
76+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {getTester} from './utils/test.mjs';
2+
3+
const {test} = getTester(import.meta);
4+
5+
test.snapshot({
6+
valid: [
7+
'const array = [...(foo || [])]',
8+
'const array = [...(foo || {})]',
9+
'const array = [...(foo && {})]',
10+
'const object = {...(foo && {})}',
11+
'const object = {...({} || foo)}',
12+
'const object = {...({} && foo)}',
13+
'const object = {...({} ?? foo)}',
14+
'const object = {...(foo ? foo : {})}',
15+
'const object = {...foo}',
16+
'const object = {...(foo ?? ({} || {}))}',
17+
'const {...foo} = object',
18+
'function foo({...bar}){}',
19+
'const object = {...(foo || {}).toString()}',
20+
'const object = {...fn(foo || {})}',
21+
'const object = call({}, ...(foo || {}))',
22+
'const object = {...(foo || {not: "empty"})}',
23+
'const object = {...(foo || {...{}})}',
24+
],
25+
invalid: [
26+
'const object = {...(foo || {})}',
27+
'const object = {...(foo ?? {})}',
28+
'const object = {...(foo ?? (( {} )))}',
29+
'const object = {...((( foo )) ?? (( {} )))}',
30+
'const object = {...(( (( foo )) ?? (( {} )) ))}',
31+
'async ()=> ({...((await foo) || {})})',
32+
'const object = {...(0 || {})}',
33+
'const object = {...((-0) || {})}',
34+
'const object = {...(.0 || {})}',
35+
'const object = {...(0n || {})}',
36+
'const object = {...(false || {})}',
37+
'const object = {...(null || {})}',
38+
'const object = {...(undefined || {})}',
39+
'const object = {...((a && b) || {})}',
40+
'const object = {...(NaN || {})}',
41+
'const object = {...("" || {})}',
42+
'const object = {...([] || {})}',
43+
'const object = {...({} || {})}',
44+
'const object = {...(foo || {}),}',
45+
'const object = {...((foo ?? {}) || {})}',
46+
'const object = {...((foo && {}) || {})}',
47+
'const object = {...(foo && {} || {})}',
48+
'const object = {...({...(foo || {})})}',
49+
'function foo(a = {...(bar || {})}){}',
50+
// The only case we'll break, but we should not care about it.
51+
'const object = {...(document.all || {})}',
52+
],
53+
});

0 commit comments

Comments
 (0)