Skip to content

feat(eslint-plugin): [no-misused-spread] add new rule #8509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
75812fc
feat(eslint-plugin): [no-misused-spread] add new rule
StyleShit Feb 19, 2024
f18acb8
fix docs
StyleShit Feb 19, 2024
22a50e5
add tests
StyleShit Feb 19, 2024
985795a
wip
StyleShit Feb 19, 2024
3ff215f
fix functions
StyleShit Feb 20, 2024
8e760e0
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Feb 20, 2024
5f9fae6
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Feb 20, 2024
990006f
change type name to `Iterable`
StyleShit Feb 21, 2024
49692d2
wip
StyleShit Feb 27, 2024
1f76659
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Feb 27, 2024
c45038f
fix lint
StyleShit Feb 27, 2024
a12c9ff
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Mar 8, 2024
9f07bc1
wip
StyleShit Mar 8, 2024
ab20e73
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Mar 10, 2024
3ef5a45
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Apr 24, 2024
b97a397
wip
StyleShit Apr 24, 2024
e02d7a9
don't flag strings spread in object
StyleShit Apr 24, 2024
26434c3
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit May 19, 2024
0921b80
wip
StyleShit May 19, 2024
b8d7e47
wip
StyleShit May 19, 2024
3f9d545
configs
StyleShit May 19, 2024
b133698
snapshot
StyleShit May 19, 2024
c449dba
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit May 28, 2024
bb3031f
add options
StyleShit May 28, 2024
99c67a0
better message
StyleShit May 28, 2024
ef12015
add map
StyleShit May 28, 2024
c3fa340
messages
StyleShit May 28, 2024
bc3bcf2
array
StyleShit May 28, 2024
137c371
promise + iterable
StyleShit May 28, 2024
db72726
class declaration
StyleShit May 28, 2024
92140f2
class instance
StyleShit May 28, 2024
8a5e2dc
lint
StyleShit May 28, 2024
d2fe874
wip
StyleShit May 29, 2024
0b0ac03
docs
StyleShit May 29, 2024
8a1a309
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit May 29, 2024
dc1411f
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Aug 20, 2024
02d7fd3
wip
StyleShit Aug 20, 2024
e90385a
wip
StyleShit Aug 20, 2024
fc57f13
wip
StyleShit Aug 20, 2024
a95cd5f
wip?
StyleShit Aug 20, 2024
094ae02
wip
StyleShit Aug 20, 2024
f9a1d18
FML
StyleShit Aug 20, 2024
e20e49b
wip
StyleShit Aug 21, 2024
acd146f
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Aug 21, 2024
af82cbb
update snapshot
StyleShit Aug 21, 2024
9ff1ba1
docs
StyleShit Aug 22, 2024
f4cf064
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Aug 22, 2024
3dea4cd
Merge branch 'main' into feat/no-misused-spread
bradzacher Aug 25, 2024
235ca44
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Sep 8, 2024
a808081
Merge branch 'feat/no-misused-spread' of https://github.com/StyleShit…
StyleShit Sep 8, 2024
9405682
wip
StyleShit Sep 10, 2024
2d60b29
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Sep 10, 2024
a678af7
Merge branch 'main' into feat/no-misused-spread
JoshuaKGoldberg Sep 13, 2024
283bbb7
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Sep 24, 2024
804fab4
Merge branch 'feat/no-misused-spread' of https://github.com/StyleShit…
StyleShit Sep 24, 2024
fc0e114
fix tests
StyleShit Sep 24, 2024
07b69b8
Merge remote-tracking branch 'typescript-eslint/main' into feat/no-mi…
StyleShit Sep 24, 2024
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
141 changes: 141 additions & 0 deletions packages/eslint-plugin/docs/rules/no-misused-spread.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
description: 'Disallow using the spread operator when it might cause unexpected behavior.'
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/no-misused-spread** for documentation.

The [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (`...`) is a JavaScript
feature that can be misused in ways not always reported by TypeScript. This rule disallows using the spread operator on types where
spreading can lead to unexpected behavior.

This rule disallows the spread operator in the following cases:

- Spreading a string into an array
- Spreading a `Promise` into an object
- Spreading a function without properties into an object
- Spreading a class instance or declaration into an object
- Spreading an iterable (`Map`, `Array`, etc.) into an object

## Examples

<Tabs>
<TabItem value="❌ Incorrect">

```ts
declare const userName: string;
const chars = [...userName];

declare const arr: number[];
const arrSpread = { ...arr };

declare const set: Set<number>;
const setSpread = { ...set };

declare const map: Map<string, number>;
const mapSpread = { ...map };

declare function getObj(): { a: 1; b: 2 };
const getObjSpread = { ...getObj };
```

</TabItem>
<TabItem value="✅ Correct">

```ts
declare const userName: string;
const chars = userName.split('');

declare const arr: number[];
const arrSpread = [...arr];

declare const set: Set<number>;
const setSpread = [...set];

declare const map: Map<string, number>;
const mapObject = Object.fromEntries(map);

declare function getObj(): { a: 1; b: 2 };
const getObjSpread = { ...getObj() };
```

</TabItem>
</Tabs>

## Options

### `allow`

This option allows marking specific types as "safe" to be spread. It takes an
array of type specifiers to consider safe.

This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier).

Examples of a configuration for this option:

```json
"@typescript-eslint/no-misused-spread": [
"error",
{
"allowForKnownSafeIterables": [
"SafeType",
{ "from": "file", "name": "SafeString", "path": "src/safe-string.ts" },
{ "from": "lib", "name": "BrandedArray" },
{ "from": "package", "name": "ThisIsSafe", "package": "safe-lib" }
]
}
]
```

<Tabs>
<TabItem value="❌ Incorrect">

```ts
type UnsafeIterable = Iterable<number>;

declare const iterable: UnsafeIterable;

const spreadIterable = { ...iterable };

type UnsafeBrandedString = string & { __brand: 'unsafe' };

declare const brandedString: UnsafeBrandedString;

const spreadBrandedString = { ...brandedString };
```

</TabItem>
<TabItem value="✅ Correct">

```ts option='{"allow":["SafeIterable", "BrandedString"]}'
type SafeIterable = Iterable<number>;

declare const iterable: SafeIterable;

const spreadIterable = { ...iterable };

type BrandedString = string & { __brand: 'safe' };

declare const brandedString: BrandedString;

const spreadBrandedString = { ...brandedString };
```

</TabItem>
</Tabs>

## When Not To Use It

If you intentionally want to use the spread operator in those cases, and expect
the specific behavior that comes with it, you might not want this rule.
For example, when you want to spread an array into an object and expect the
result to be an object with the array elements as values and the array indices
as keys.

If your use cases for unusual spreads only involve a few types, you might consider using
[ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1)
and/or the [`allow` option](#allow) instead of completely disabling this rule.
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export = {
'@typescript-eslint/no-meaningless-void-operator': 'error',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-misused-spread': 'error',
'@typescript-eslint/no-mixed-enums': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/disable-type-checked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export = {
'@typescript-eslint/no-implied-eval': 'off',
'@typescript-eslint/no-meaningless-void-operator': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-misused-spread': 'off',
'@typescript-eslint/no-mixed-enums': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export = {
'@typescript-eslint/no-implied-eval': 'error',
'@typescript-eslint/no-meaningless-void-operator': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-misused-spread': 'error',
'@typescript-eslint/no-mixed-enums': 'error',
'@typescript-eslint/no-redundant-type-constituents': 'error',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/strict-type-checked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export = {
'@typescript-eslint/no-meaningless-void-operator': 'error',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-misused-spread': 'error',
'@typescript-eslint/no-mixed-enums': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import noMagicNumbers from './no-magic-numbers';
import noMeaninglessVoidOperator from './no-meaningless-void-operator';
import noMisusedNew from './no-misused-new';
import noMisusedPromises from './no-misused-promises';
import noMisusedSpread from './no-misused-spread';
import noMixedEnums from './no-mixed-enums';
import noNamespace from './no-namespace';
import noNonNullAssertedNullishCoalescing from './no-non-null-asserted-nullish-coalescing';
Expand Down Expand Up @@ -182,6 +183,7 @@ export default {
'no-meaningless-void-operator': noMeaninglessVoidOperator,
'no-misused-new': noMisusedNew,
'no-misused-promises': noMisusedPromises,
'no-misused-spread': noMisusedSpread,
'no-mixed-enums': noMixedEnums,
'no-namespace': noNamespace,
'no-non-null-asserted-nullish-coalescing': noNonNullAssertedNullishCoalescing,
Expand Down
Loading
Loading