Skip to content

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

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 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
ac351e8
Merge branch 'main' into no-misused-spread-ii-electric-boogaloo
JoshuaKGoldberg Dec 26, 2024
e8a657e
First round of PR feedback
JoshuaKGoldberg Dec 26, 2024
20a3ae0
lil docs nit
JoshuaKGoldberg Dec 26, 2024
c85797a
More intentional docs reworking
JoshuaKGoldberg Dec 26, 2024
714dde6
fix NotFound/Content/index.tsx violation
JoshuaKGoldberg Dec 26, 2024
462f811
yarn lint --fix
JoshuaKGoldberg Dec 26, 2024
39daadf
Handle static class-likes
JoshuaKGoldberg Dec 26, 2024
500d1c1
test -u and cleanup and typo fix
JoshuaKGoldberg Dec 26, 2024
d8e8ead
Clean up a few redundant tests
JoshuaKGoldberg Dec 26, 2024
a04df3b
Also test : unknown
JoshuaKGoldberg Dec 26, 2024
b78c27b
Merge branch 'main' into no-misused-spread-ii-electric-boogaloo
JoshuaKGoldberg Dec 26, 2024
f49b88f
empty
JoshuaKGoldberg Dec 26, 2024
e1e9c2c
Fix .mdx docs test
JoshuaKGoldberg Dec 26, 2024
b0c3919
Separate TSConfig for DOM
JoshuaKGoldberg Dec 26, 2024
ca44d93
Handle function call spreads too
JoshuaKGoldberg Dec 29, 2024
b0d94e8
Test constrained type params too
JoshuaKGoldberg Dec 29, 2024
f3dbde3
Spring
JoshuaKGoldberg Dec 29, 2024
6031579
Union type tests
JoshuaKGoldberg Dec 29, 2024
5e217c9
Update docs snapshot as well
JoshuaKGoldberg Dec 29, 2024
b8a15a1
Merge branch 'main' into no-misused-spread-ii-electric-boogaloo
JoshuaKGoldberg Jan 3, 2025
b52123d
HTMLElementLike
JoshuaKGoldberg Jan 3, 2025
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
132 changes: 132 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,132 @@
---
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.

[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (`...`) is a JavaScript feature for creating an object with the joined properties of one or more other objects.
TypeScript allows spreading objects whose properties are not typically meant to be enumerated, such as arrays and class instances.

This rule disallows using the spread syntax on values whose types indicate doing so may cause unexpected behavior.
That includes the following cases:

- Spreading a `Promise` into an object.
You probably meant to `await` it.
- Spreading a function without properties into an object.
You probably meant to call it.
- Spreading an iterable (`Array`, `Map`, etc.) into an object.
Iterable objects usually do not have meaningful enumerable properties and you probably meant to spread it into an array instead.
- Spreading a string into an array.
String enumeration behaviors in JavaScript around encoded characters are often surprising.
- Spreading a `class` into an object.
This copies all static own properties of the class, but none of the inheritance chain.
- Spreading a class instance into an object.
This does not faithfully copy the instance because only its own properties are copied, but the inheritance chain is lost, including all its methods.

## Examples

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

```ts
declare const promise: Promise<number>;
const spreadPromise = { ...promise };

declare function getObject(): Record<string, strings>;
const getObjectSpread = { ...getObject };

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

declare const userName: string;
const characters = [...userName];
```

```ts
declare class Box {
value: number;
}
const boxSpread = { ...Box };

declare const instance: Box;
const instanceSpread = { ...box };
```

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

```ts
declare const promise: Promise<number>;
const spreadPromise = { ...(await promise) };

declare function getObject(): Record<string, strings>;
const getObjectSpread = { ...getObject() };

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

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

</TabItem>
</Tabs>

## Options

### `allow`

{/* insert option description */}

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

Examples of a configuration for this option in a `file.ts` file:

```json
"@typescript-eslint/no-misused-spread": [
"error",
{
"allow": [
{ "from": "file", "name": "BrandedString", "path": "file.ts" },
]
}
]
```

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

```ts option='{"allow":[{ "from": "file", "name": "BrandedString" }]}'
declare const unbrandedString: string;

const spreadUnbrandedString = [...unbrandedString];
```

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

```ts option='{"allow":[{ "from": "file", "name": "BrandedString" }]}'
type BrandedString = string & { __brand: 'safe' };

declare const brandedString: BrandedString;

const spreadBrandedString = [...brandedString];
```

</TabItem>
</Tabs>

## When Not To Use It

If your application intentionally works with raw data in unusual ways, such as directly manipulating class prototype chains, you might not want this rule.

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.

## Further Reading

- [Strings Shouldn't Be Iterable By Default](https://www.xanthir.com/b4wJ1)
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 @@ -184,6 +185,7 @@ const rules = {
'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