Skip to content

Commit

Permalink
prefer-spread: Forbid use of Array#toSpliced() to copy array (#2034)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Feb 1, 2023
1 parent ca5c62a commit 4ada50e
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 3 deletions.
14 changes: 13 additions & 1 deletion docs/rules/prefer-spread.md
@@ -1,4 +1,4 @@
# Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#slice()` and `String#split('')`
# Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')`

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

Expand All @@ -25,6 +25,10 @@ Enforces the use of [the spread operator (`...`)](https://developer.mozilla.org/

Variables named `arrayBuffer`, `blob`, `buffer`, `file`, and `this` are ignored.

- `Array#toSpliced()`

Shallow copy an `Array`.

- `String#split('')`

Split a string into an array of characters.
Expand All @@ -47,6 +51,14 @@ const array = array1.concat(array2);
const copy = array.slice();
```

```js
const copy = array.slice(0);
```

```js
const copy = array.toSpliced();
```

```js
const characters = string.split('');
```
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -145,7 +145,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. || 🔧 | 💡 |
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. || 🔧 | 💡 |
| [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. || 🔧 | |
| [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#slice()` and `String#split('')`. || 🔧 | 💡 |
| [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')`. || 🔧 | 💡 |
| [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | | 🔧 | |
| [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. || 🔧 | |
| [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. || 🔧 | 💡 |
Expand Down
19 changes: 18 additions & 1 deletion rules/prefer-spread.js
Expand Up @@ -16,6 +16,7 @@ const isMethodNamed = require('./utils/is-method-named.js');
const ERROR_ARRAY_FROM = 'array-from';
const ERROR_ARRAY_CONCAT = 'array-concat';
const ERROR_ARRAY_SLICE = 'array-slice';
const ERROR_ARRAY_TO_SPLICED = 'array-to-spliced';
const ERROR_STRING_SPLIT = 'string-split';
const SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE = 'argument-is-spreadable';
const SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE = 'argument-is-not-spreadable';
Expand All @@ -26,6 +27,7 @@ const messages = {
[ERROR_ARRAY_FROM]: 'Prefer the spread operator over `Array.from(…)`.',
[ERROR_ARRAY_CONCAT]: 'Prefer the spread operator over `Array#concat(…)`.',
[ERROR_ARRAY_SLICE]: 'Prefer the spread operator over `Array#slice()`.',
[ERROR_ARRAY_TO_SPLICED]: 'Prefer the spread operator over `Array#toSpliced()`.',
[ERROR_STRING_SPLIT]: 'Prefer the spread operator over `String#split(\'\')`.',
[SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE]: 'First argument is an `array`.',
[SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE]: 'First argument is not an `array`.',
Expand Down Expand Up @@ -56,6 +58,14 @@ const arraySliceCallSelector = [
'[callee.object.type!="ArrayExpression"]',
].join('');

const arrayToSplicedCallSelector = [
methodCallSelector({
method: 'toSpliced',
argumentsLength: 0,
}),
'[callee.object.type!="ArrayExpression"]',
].join('');

const ignoredSliceCallee = [
'arrayBuffer',
'blob',
Expand Down Expand Up @@ -444,6 +454,13 @@ const create = context => {
fix: methodCallToSpread(node, sourceCode),
};
},
[arrayToSplicedCallSelector](node) {
return {
node: node.callee.property,
messageId: ERROR_ARRAY_TO_SPLICED,
fix: methodCallToSpread(node, sourceCode),
};
},
[stringSplitCallSelector](node) {
const [separator] = node.arguments;
if (!isLiteral(separator, '')) {
Expand Down Expand Up @@ -495,7 +512,7 @@ module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#slice()` and `String#split(\'\')`.',
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split(\'\')`.',
},
fixable: 'code',
hasSuggestions: true,
Expand Down
39 changes: 39 additions & 0 deletions test/prefer-spread.mjs
Expand Up @@ -347,6 +347,45 @@ test.snapshot({
],
});

// `Array#toSpliced`
test.snapshot({
valid: [
'new Array.toSpliced()',
'toSpliced()',
'array[toSpliced]()',
'array.toSpliced',
'array.toSpliced(0)',
'array.toSpliced(...[])',
'array.toSpliced(...[0])',
'array.toSpliced(0 + 0)',
'array.toSpliced("")',
'array.toSpliced(null)',
'const ZERO = 0;array.toSpliced(0, ZERO)',
'array.toSpliced(0, array.length)',
'array.toSpliced(0, 0)',
'array.notToSpliced()',
// Why would someone write these
'[...foo].toSpliced()',
'[foo].toSpliced()',
'array.toSpliced(100, 0)',
'array.toSpliced(-1, 0)',
],
invalid: [
'array.toSpliced()',
'array.toSpliced().toSpliced()',
'const copy = array.toSpliced()',
'(( (( (( array )).toSpliced ))() ))',
// Semicolon
outdent`
bar()
foo.toSpliced()
`,
// `{String,TypedArray}#toSpliced` are wrongly detected
'"".toSpliced()',
'new Uint8Array([10, 20, 30, 40, 50]).toSpliced()',
],
});

// `String#slice('')`
test.snapshot({
valid: [
Expand Down
122 changes: 122 additions & 0 deletions test/snapshots/prefer-spread.mjs.md
Expand Up @@ -2268,6 +2268,128 @@ Generated by [AVA](https://avajs.dev).
| ^^^^^ Prefer the spread operator over \`Array#slice()\`.␊
`

## Invalid #1
1 | array.toSpliced()

> Output
`␊
1 | [...array]␊
`

> Error 1/1
`␊
> 1 | array.toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #2
1 | array.toSpliced().toSpliced()

> Output
`␊
1 | [...array].toSpliced()␊
`

> Error 1/2
`␊
> 1 | array.toSpliced().toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

> Error 2/2
`␊
> 1 | array.toSpliced().toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #3
1 | const copy = array.toSpliced()

> Output
`␊
1 | const copy = [...array]␊
`

> Error 1/1
`␊
> 1 | const copy = array.toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #4
1 | (( (( (( array )).toSpliced ))() ))

> Output
`␊
1 | (( [...(( (( array )) ))] ))␊
`

> Error 1/1
`␊
> 1 | (( (( (( array )).toSpliced ))() ))␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #5
1 | bar()
2 | foo.toSpliced()

> Output
`␊
1 | bar()␊
2 | ;[...foo]␊
`

> Error 1/1
`␊
1 | bar()␊
> 2 | foo.toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #6
1 | "".toSpliced()

> Output
`␊
1 | [...""]␊
`

> Error 1/1
`␊
> 1 | "".toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #7
1 | new Uint8Array([10, 20, 30, 40, 50]).toSpliced()

> Output
`␊
1 | [...new Uint8Array([10, 20, 30, 40, 50])]␊
`

> Error 1/1
`␊
> 1 | new Uint8Array([10, 20, 30, 40, 50]).toSpliced()␊
| ^^^^^^^^^ Prefer the spread operator over \`Array#toSpliced()\`.␊
`

## Invalid #1
1 | "string".split("")

Expand Down
Binary file modified test/snapshots/prefer-spread.mjs.snap
Binary file not shown.

0 comments on commit 4ada50e

Please sign in to comment.