Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/rules/no-useless-collection-argument.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Disallow useless values or fallbacks in `Set`, `Map`, `WeakSet`, or `WeakMap`

💼 This rule is enabled in the following [configs](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config): ✅ `recommended`, ☑️ `unopinionated`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

It's unnecessary to pass an empty array or string when constructing a `Set`, `Map`, `WeakSet`, or `WeakMap`, since they accept nullish values.

It's also unnecessary to provide a fallback for possible nullish values.

## Examples

```js
// ❌
const set = new Set([]);
// ❌
const set = new Set("");

// ✅
const set = new Set();
```

```js
// ❌
const set = new Set(foo ?? []);
// ❌
const set = new Set(foo ?? "");

// ✅
const set = new Set(foo);
```
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default [
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ ☑️ | 🔧 | |
| [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | ✅ ☑️ | | |
| [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
| [no-useless-collection-argument](docs/rules/no-useless-collection-argument.md) | Disallow useless values or fallbacks in `Set`, `Map`, `WeakSet`, or `WeakMap`. | ✅ ☑️ | 🔧 | |
| [no-useless-error-capture-stack-trace](docs/rules/no-useless-error-capture-stack-trace.md) | Disallow unnecessary `Error.captureStackTrace(…)`. | ✅ ☑️ | 🔧 | |
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Disallow useless fallback when spreading in object literals. | ✅ ☑️ | 🔧 | |
| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | ✅ ☑️ | 🔧 | |
Expand Down
2 changes: 2 additions & 0 deletions rules/ast/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
isBigIntLiteral,
isNullLiteral,
isRegexLiteral,
isEmptyStringLiteral,
} from './literal.js';

export {
Expand All @@ -16,6 +17,7 @@ export {
export {default as isArrowFunctionBody} from './is-arrow-function-body.js';
export {default as isDirective} from './is-directive.js';
export {default as isEmptyNode} from './is-empty-node.js';
export {default as isEmptyArrayExpression} from './is-empty-array-expression.js';
export {default as isExpressionStatement} from './is-expression-statement.js';
export {default as isFunction} from './is-function.js';
export {default as isMemberExpression} from './is-member-expression.js';
Expand Down
5 changes: 5 additions & 0 deletions rules/ast/is-empty-array-expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const isEmptyArrayExpression = node =>
node.type === 'ArrayExpression'
&& node.elements.length === 0;

export default isEmptyArrayExpression;
1 change: 1 addition & 0 deletions rules/ast/literal.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export const isNullLiteral = node => isLiteral(node, null);

export const isBigIntLiteral = node => node.type === 'Literal' && Boolean(node.bigint);

export const isEmptyStringLiteral = node => isLiteral(node, '');
10 changes: 4 additions & 6 deletions rules/fix/remove-argument.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {isCommaToken} from '@eslint-community/eslint-utils';
import {getParentheses} from '../utils/parentheses.js';

export default function removeArgument(fixer, node, sourceCode) {
const callExpression = node.parent;
const index = callExpression.arguments.indexOf(node);
const callOrNewExpression = node.parent;
const index = callOrNewExpression.arguments.indexOf(node);
const parentheses = getParentheses(node, sourceCode);
const firstToken = parentheses[0] || node;
const lastToken = parentheses.at(-1) || node;
Expand All @@ -17,14 +17,12 @@ export default function removeArgument(fixer, node, sourceCode) {
}

// If the removed argument is the only argument, the trailing comma must be removed too
/* c8 ignore start */
if (callExpression.arguments.length === 1) {
const tokenAfter = sourceCode.getTokenBefore(lastToken);
if (callOrNewExpression.arguments.length === 1) {
const tokenAfter = sourceCode.getTokenAfter(lastToken);
if (isCommaToken(tokenAfter)) {
[, end] = sourceCode.getRange(tokenAfter);
}
}
/* c8 ignore end */

return fixer.removeRange([start, end]);
}
1 change: 1 addition & 0 deletions rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export {default as 'no-unnecessary-slice-end'} from './no-unnecessary-slice-end.
export {default as 'no-unreadable-array-destructuring'} from './no-unreadable-array-destructuring.js';
export {default as 'no-unreadable-iife'} from './no-unreadable-iife.js';
export {default as 'no-unused-properties'} from './no-unused-properties.js';
export {default as 'no-useless-collection-argument'} from './no-useless-collection-argument.js';
export {default as 'no-useless-error-capture-stack-trace'} from './no-useless-error-capture-stack-trace.js';
export {default as 'no-useless-fallback-in-spread'} from './no-useless-fallback-in-spread.js';
export {default as 'no-useless-length-check'} from './no-useless-length-check.js';
Expand Down
101 changes: 101 additions & 0 deletions rules/no-useless-collection-argument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {isParenthesized, getParenthesizedRange} from './utils/index.js';
import {
isNewExpression,
isEmptyArrayExpression,
isEmptyStringLiteral,
isNullLiteral,
isUndefined,
} from './ast/index.js';
import {removeParentheses, removeArgument} from './fix/index.js';

const MESSAGE_ID = 'no-useless-collection-argument';
const messages = {
[MESSAGE_ID]: 'The {{description}} is useless.',
};

const getDescription = node => {
if (isEmptyArrayExpression(node)) {
return 'empty array';
}

if (isEmptyStringLiteral(node)) {
return 'empty string';
}

if (isNullLiteral(node)) {
return '`null`';
}

if (isUndefined(node)) {
return '`undefined`';
}
};

const removeFallback = (node, context) =>
// Same code from rules/no-useless-fallback-in-spread.js
/** @param {import('eslint').Rule.RuleFixer} fixer */
function * fix(fixer) {
const {sourceCode} = context;
const logicalExpression = node.parent;
const {left} = logicalExpression;
const isLeftObjectParenthesized = isParenthesized(left, sourceCode);
const [, start] = isLeftObjectParenthesized
? getParenthesizedRange(left, sourceCode)
: sourceCode.getRange(left);
const [, end] = sourceCode.getRange(logicalExpression);

yield fixer.removeRange([start, end]);

if (
isLeftObjectParenthesized
|| left.type !== 'SequenceExpression'
) {
yield * removeParentheses(logicalExpression, fixer, sourceCode);
}
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
NewExpression(newExpression) {
if (!isNewExpression(newExpression, {
names: ['Set', 'Map', 'WeakSet', 'WeakMap'],
argumentsLength: 1,
})) {
return;
}

const [iterable] = newExpression.arguments;
const isCheckingFallback = iterable.type === 'LogicalExpression' && iterable.operator === '??';
const node = isCheckingFallback ? iterable.right : iterable;
const description = getDescription(node);

if (!description) {
return;
}

return {
node,
messageId: MESSAGE_ID,
data: {description},
fix: isCheckingFallback
? removeFallback(node, context)
: fixer => removeArgument(fixer, node, context.sourceCode),
};
},
});

/** @type {import('eslint').Rule.RuleModule} */
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Disallow useless values or fallbacks in `Set`, `Map`, `WeakSet`, or `WeakMap`.',
recommended: 'unopinionated',
},
fixable: 'code',
messages,
},
};

export default config;
6 changes: 1 addition & 5 deletions rules/prefer-array-flat.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@
shouldAddParenthesesToMemberExpressionObject,
} from './utils/index.js';
import {fixSpaceAroundKeyword} from './fix/index.js';
import {isMethodCall, isCallExpression} from './ast/index.js';
import {isMethodCall, isCallExpression, isEmptyArrayExpression} from './ast/index.js';

const MESSAGE_ID = 'prefer-array-flat';
const messages = {
[MESSAGE_ID]: 'Prefer `Array#flat()` over `{{description}}` to flatten an array.',
};

const isEmptyArrayExpression = node =>
node.type === 'ArrayExpression'
&& node.elements.length === 0;

// `array.flatMap(x => x)`
const arrayFlatMap = {
testFunction(node) {
Expand Down Expand Up @@ -161,7 +157,7 @@
'underscore.flatten',
];

function fix(node, array, sourceCode, shouldSwitchToArray, optional) {

Check warning on line 160 in rules/prefer-array-flat.js

View workflow job for this annotation

GitHub Actions / lint-test (windows-latest)

Function 'fix' has too many parameters (5). Maximum allowed is 4

Check warning on line 160 in rules/prefer-array-flat.js

View workflow job for this annotation

GitHub Actions / lint-test (ubuntu-latest)

Function 'fix' has too many parameters (5). Maximum allowed is 4
if (typeof shouldSwitchToArray === 'function') {
shouldSwitchToArray = shouldSwitchToArray(node);
}
Expand Down
75 changes: 75 additions & 0 deletions test/no-useless-collection-argument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {getTester} from './utils/test.js';

const {test} = getTester(import.meta);

// Useless value
test.snapshot({
valid: [
'new Set()',
'new Set',
'new Set(foo)',
'new Set(foo || [])',
'new Set(foo && [])',
'new Not_Set([])',
'Set([])',
'new Set([], extraArgument)',
'new Set(...([]))',
'new Set([""])',
'new Set("not-empty")',
'new Set(0)',
'new ([])(Set)',
// Not checking
'new globalThis.Set([])',
],
invalid: [
'new Set([])',
'new Set("")',
'new Set(undefined)',
'new Set(null)',
'new WeakSet([])',
'new Map([])',
'new WeakMap([])',
'new Set( (([])) )',
'new Set([],)',
'new Set( (([])), )',
],
});

// Fallbacks
test.snapshot({
valid: [
'new Set(foo || [])',
'new Set(foo && [])',
'new Not_Set(foo ?? [])',
'Set(foo ?? [])',
'new Set(foo ?? [], extraArgument)',
'new Set(...(foo ?? []))',
'new Set(foo ?? [""])',
'new Set(foo ?? "not-empty")',
'new Set(foo ?? 0)',
'new (foo ?? [])(Set)',
// Not checking
'new globalThis.Set(foo ?? [])',
],
invalid: [
'new Set(foo ?? [])',
'new Set(foo ?? "")',
'new Set(foo ?? undefined)',
'new Set(foo ?? null)',
'new WeakSet(foo ?? [])',
'new Map(foo ?? [])',
'new WeakMap(foo ?? [])',
'new Set( ((foo ?? [])) )',
'new Set( (( foo )) ?? [] )',
'new Set( foo ?? (( [] )) )',
'new Set( (await foo) ?? [] )',
'new Set( (0, foo) ?? [] )',
'new Set( (( (0, foo) ?? [] )) )',
// Who cares?
'new Set(document.all ?? [])',
// Both sides are useless
'new Set([] ?? "")',
'new Set( (( (( "" )) ?? (( [] )) )) )',
'new Set(foo ?? bar ?? [])',
],
});
Loading
Loading