Skip to content

Commit 30dbe6d

Browse files
authored
Add no-useless-undefined rule (#718)
1 parent b02f3a3 commit 30dbe6d

File tree

9 files changed

+460
-8
lines changed

9 files changed

+460
-8
lines changed

docs/rules/no-useless-undefined.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Disallow useless `undefined`
2+
3+
This rule is fixable.
4+
5+
## Fail
6+
7+
```js
8+
let foo = undefined;
9+
```
10+
11+
```js
12+
const {foo = undefined} = bar;
13+
```
14+
15+
```js
16+
const noop = () => undefined;
17+
```
18+
19+
```js
20+
function foo() {
21+
return undefined;
22+
}
23+
```
24+
25+
```js
26+
function* foo() {
27+
yield undefined;
28+
}
29+
```
30+
31+
```js
32+
function foo(bar = undefined) {
33+
}
34+
```
35+
36+
```js
37+
function foo({bar = undefined}) {
38+
}
39+
```
40+
41+
```js
42+
foo(undefined);
43+
```
44+
45+
## Pass
46+
47+
```js
48+
let foo;
49+
```
50+
51+
```js
52+
const {foo} = bar;
53+
```
54+
55+
```js
56+
const noop = () => {};
57+
```
58+
59+
```js
60+
function foo() {
61+
return;
62+
}
63+
```
64+
65+
```js
66+
function* foo() {
67+
yield;
68+
}
69+
```
70+
71+
```js
72+
function foo(bar) {
73+
}
74+
```
75+
76+
```js
77+
function foo({bar}) {
78+
}
79+
```
80+
81+
```js
82+
foo();
83+
```
84+
85+
## Conflict with ESLint `array-callback-return` rule
86+
87+
We recommend setting the ESLint [`array-callback-return`](https://eslint.org/docs/rules/array-callback-return#top) rule option [`allowImplicit`](https://eslint.org/docs/rules/array-callback-return#options) to `true`:
88+
89+
```json
90+
{
91+
"rules": {
92+
"array-callback-return": [
93+
"error",
94+
{
95+
"allowImplicit": true
96+
}
97+
]
98+
}
99+
}
100+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module.exports = {
4343
'unicorn/no-unreadable-array-destructuring': 'error',
4444
'unicorn/no-unsafe-regex': 'off',
4545
'unicorn/no-unused-properties': 'off',
46+
'unicorn/no-useless-undefined': 'error',
4647
'unicorn/no-zero-fractions': 'error',
4748
'unicorn/number-literal-case': 'error',
4849
'unicorn/prefer-add-event-listener': 'error',

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@
106106
],
107107
"rules": {
108108
"strict": "error",
109+
"array-callback-return": [
110+
"error",
111+
{
112+
"allowImplicit": true
113+
}
114+
],
109115
"unicorn/no-null": "error",
110116
"unicorn/string-content": "off"
111117
}

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Configure it in `package.json`.
5959
"unicorn/no-unreadable-array-destructuring": "error",
6060
"unicorn/no-unsafe-regex": "off",
6161
"unicorn/no-unused-properties": "off",
62+
"unicorn/no-useless-undefined": "error",
6263
"unicorn/no-zero-fractions": "error",
6364
"unicorn/number-literal-case": "error",
6465
"unicorn/prefer-add-event-listener": "error",
@@ -117,6 +118,7 @@ Configure it in `package.json`.
117118
- [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring.
118119
- [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions.
119120
- [no-unused-properties](docs/rules/no-unused-properties.md) - Disallow unused object properties.
121+
- [no-useless-undefined](docs/rules/no-useless-undefined.md) - Disallow useless `undefined`. *(fixable)*
120122
- [no-zero-fractions](docs/rules/no-zero-fractions.md) - Disallow number literals with zero fractions or dangling dots. *(fixable)*
121123
- [number-literal-case](docs/rules/number-literal-case.md) - Enforce proper case for numeric literals. *(fixable)*
122124
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*

rules/no-for-loop.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,6 @@ const resolveIdentifierName = (name, scope) => {
191191

192192
scope = scope.upper;
193193
}
194-
195-
return undefined;
196194
};
197195

198196
const scopeContains = (ancestor, descendant) => {
@@ -367,7 +365,7 @@ const create = context => {
367365
], replacement),
368366
...arrayReferences.map(reference => {
369367
if (reference === elementReference) {
370-
return undefined;
368+
return;
371369
}
372370

373371
return fixer.replaceText(reference.identifier.parent, element);

rules/no-unused-properties.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ const create = context => {
139139
return {identifier: parent};
140140
}
141141

142-
return undefined;
142+
return;
143143
}
144144

145145
if (parent.type === 'MemberExpression') {
@@ -152,7 +152,7 @@ const create = context => {
152152
return {identifier: parent};
153153
}
154154

155-
return undefined;
155+
return;
156156
}
157157

158158
if (
@@ -163,7 +163,7 @@ const create = context => {
163163
return {identifier: parent};
164164
}
165165

166-
return undefined;
166+
return;
167167
}
168168

169169
if (
@@ -174,7 +174,7 @@ const create = context => {
174174
return {identifier: parent};
175175
}
176176

177-
return undefined;
177+
return;
178178
}
179179

180180
return reference;

rules/no-useless-undefined.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
'use strict';
2+
const {isCommaToken} = require('eslint-utils');
3+
const getDocumentationUrl = require('./utils/get-documentation-url');
4+
5+
const messageId = 'no-useless-undefined';
6+
7+
const getSelector = (parent, property) =>
8+
`${parent} > Identifier.${property}[name="undefined"]`;
9+
10+
// `return undefined`
11+
const returnSelector = getSelector('ReturnStatement', 'argument');
12+
13+
// `yield undefined`
14+
const yieldSelector = getSelector('YieldExpression[delegate=false]', 'argument');
15+
16+
// `() => undefined`
17+
const arrowFunctionSelector = getSelector('ArrowFunctionExpression', 'body');
18+
19+
// `let foo = undefined` / `var foo = undefined`
20+
const variableInitSelector = getSelector(
21+
[
22+
'VariableDeclaration',
23+
'[kind!="const"]',
24+
'>',
25+
'VariableDeclarator'
26+
].join(''),
27+
'init'
28+
);
29+
30+
// `const {foo = undefined} = {}`
31+
const assignmentPatternSelector = getSelector('AssignmentPattern', 'right');
32+
33+
const isUndefined = node => node && node.type === 'Identifier' && node.name === 'undefined';
34+
35+
const create = context => {
36+
const listener = fix => node => {
37+
context.report({
38+
node,
39+
messageId,
40+
fix: fixer => fix(node, fixer)
41+
});
42+
};
43+
44+
const code = context.getSourceCode().text;
45+
46+
const removeNodeAndLeadingSpace = (node, fixer) => {
47+
const textBefore = code.slice(0, node.range[0]);
48+
return fixer.removeRange([
49+
node.range[0] - (textBefore.length - textBefore.trim().length),
50+
node.range[1]
51+
]);
52+
};
53+
54+
return {
55+
[returnSelector]: listener(removeNodeAndLeadingSpace),
56+
[yieldSelector]: listener(removeNodeAndLeadingSpace),
57+
[arrowFunctionSelector]: listener(
58+
(node, fixer) => fixer.replaceText(node, '{}')
59+
),
60+
[variableInitSelector]: listener(
61+
(node, fixer) => fixer.removeRange([node.parent.id.range[1], node.range[1]])
62+
),
63+
[assignmentPatternSelector]: listener(
64+
(node, fixer) => fixer.removeRange([node.parent.left.range[1], node.range[1]])
65+
),
66+
CallExpression: node => {
67+
const argumentNodes = node.arguments;
68+
const undefinedArguments = [];
69+
for (let index = argumentNodes.length - 1; index >= 0; index--) {
70+
const node = argumentNodes[index];
71+
if (isUndefined(node)) {
72+
undefinedArguments.unshift(node);
73+
} else {
74+
break;
75+
}
76+
}
77+
78+
if (undefinedArguments.length === 0) {
79+
return;
80+
}
81+
82+
const firstUndefined = undefinedArguments[0];
83+
const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
84+
85+
context.report({
86+
messageId,
87+
loc: {
88+
start: firstUndefined.loc.start,
89+
end: lastUndefined.loc.end
90+
},
91+
fix: fixer => {
92+
let start = firstUndefined.range[0];
93+
let end = lastUndefined.range[1];
94+
95+
const previousArgument = argumentNodes[argumentNodes.length - undefinedArguments.length - 1];
96+
97+
if (previousArgument) {
98+
start = previousArgument.range[1];
99+
} else {
100+
// If all arguments removed, and there is trailing comma, we need remove it.
101+
const tokenAfter = context.getTokenAfter(lastUndefined);
102+
if (isCommaToken(tokenAfter)) {
103+
end = tokenAfter.range[1];
104+
}
105+
}
106+
107+
return fixer.removeRange([start, end]);
108+
}
109+
});
110+
}
111+
};
112+
};
113+
114+
module.exports = {
115+
create,
116+
meta: {
117+
type: 'suggestion',
118+
docs: {
119+
url: getDocumentationUrl(__filename)
120+
},
121+
messages: {
122+
[messageId]: 'Do not use useless `undefined`.'
123+
},
124+
fixable: 'code'
125+
}
126+
};

test/filename-case.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ ruleTester.run('filename-case', rule, {
8787
testCase('src/foo/___foo-bar.js', 'kebabCase'),
8888
testCase('src/foo/_FooBar.js', 'pascalCase'),
8989
testCase('src/foo/___FooBar.js', 'pascalCase'),
90-
testManyCases('src/foo/foo-bar.js', undefined),
90+
testManyCases('src/foo/foo-bar.js'),
9191
testManyCases('src/foo/foo-bar.js', {}),
9292
testManyCases('src/foo/fooBar.js', {camelCase: true}),
9393
testManyCases('src/foo/FooBar.js', {kebabCase: true, pascalCase: true}),

0 commit comments

Comments
 (0)