Skip to content

Commit dda295b

Browse files
authored
feat(plugins/dom): add 'no-flush-sync' rule (#942)
1 parent 0ef739b commit dda295b

File tree

7 files changed

+136
-1
lines changed

7 files changed

+136
-1
lines changed

apps/website/content/docs/rules/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"dom-no-dangerously-set-innerhtml",
5959
"dom-no-dangerously-set-innerhtml-with-children",
6060
"dom-no-find-dom-node",
61+
"dom-no-flush-sync",
6162
"dom-no-missing-button-type",
6263
"dom-no-missing-iframe-sandbox",
6364
"dom-no-namespace",

apps/website/content/docs/rules/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ full: true
7979
| [`no-dangerously-set-innerhtml-with-children`](./dom-no-dangerously-set-innerhtml-with-children) | 2️⃣ | `🔍` | Prevents DOM elements using `dangerouslySetInnerHTML` and `children` at the same time. |
8080
| [`no-dangerously-set-innerhtml`](./dom-no-dangerously-set-innerhtml) | 1️⃣ | `🔍` | Prevents DOM elements using `dangerouslySetInnerHTML`. |
8181
| [`no-find-dom-node`](./dom-no-find-dom-node) | 2️⃣ | `🔍` | Prevents using `findDOMNode`. |
82+
| [`no-flush-sync`](./dom-no-flush-sync) | 1️⃣ | `🔍` | Prevents using `flushSync`. |
8283
| [`no-missing-button-type`](./dom-no-missing-button-type) | 1️⃣ | `🔍` | Enforces explicit `type` attribute for `button` elements. |
8384
| [`no-missing-iframe-sandbox`](./dom-no-missing-iframe-sandbox) | 1️⃣ | `🔍` | Enforces explicit `sandbox` attribute for `iframe` elements. |
8485
| [`no-namespace`](./dom-no-namespace) | 2️⃣ | `🔍` | Enforces the absence of a `namespace` in React elements. |

packages/plugins/eslint-plugin-react-dom/src/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { name, version } from "../package.json";
22
import noDangerouslySetInnerHTML from "./rules/no-dangerously-set-innerhtml";
33
import noDangerouslySetInnerHTMLWithChildren from "./rules/no-dangerously-set-innerhtml-with-children";
44
import noFindDomNode from "./rules/no-find-dom-node";
5+
import noFlushSync from "./rules/no-flush-sync";
56
import noMissingButtonType from "./rules/no-missing-button-type";
67
import noMissingIframeSandbox from "./rules/no-missing-iframe-sandbox";
78
import noNamespace from "./rules/no-namespace";
@@ -21,6 +22,7 @@ export const plugin = {
2122
"no-dangerously-set-innerhtml": noDangerouslySetInnerHTML,
2223
"no-dangerously-set-innerhtml-with-children": noDangerouslySetInnerHTMLWithChildren,
2324
"no-find-dom-node": noFindDomNode,
25+
"no-flush-sync": noFlushSync,
2426
"no-missing-button-type": noMissingButtonType,
2527
"no-missing-iframe-sandbox": noMissingIframeSandbox,
2628
"no-namespace": noNamespace,

packages/plugins/eslint-plugin-react-dom/src/rules/no-find-dom-node.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,4 @@ class AutoSelectingInput extends Component {
8080

8181
## Further Reading
8282

83-
- [React: APIs findDOMNode](https://react.dev/reference/react-dom/findDOMNode)
83+
- [React DOM: APIs findDOMNode](https://react.dev/reference/react-dom/findDOMNode)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: no-flush-sync
3+
---
4+
5+
**Full Name in `eslint-plugin-react-dom`**
6+
7+
```plain copy
8+
react-dom/no-flush-sync
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```plain copy
14+
@eslint-react/dom/no-flush-sync
15+
```
16+
17+
**Features**
18+
19+
`🔍`
20+
21+
## What it does
22+
23+
This rule reports usages of `flushSync`.
24+
25+
`flushSync` can significantly hurt performance, and may unexpectedly force pending Suspense boundaries to show their fallback state.
26+
27+
Most of the time, `flushSync` can be avoided, so use `flushSync` as a last resort.
28+
29+
## Examples
30+
31+
### Failing
32+
33+
```tsx
34+
import { flushSync } from "react-dom";
35+
36+
flushSync(() => {
37+
setSomething(123);
38+
});
39+
```
40+
41+
## Implementation
42+
43+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/no-flush-sync.ts)
44+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/no-flush-sync.spec.ts)
45+
46+
## Further Reading
47+
48+
- [React DOM: APIs flushSync](https://react.dev/reference/react-dom/flushSync)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { allValid, ruleTester } from "../../../../../test";
2+
import rule, { RULE_NAME } from "./no-flush-sync";
3+
4+
ruleTester.run(RULE_NAME, rule, {
5+
invalid: [
6+
{
7+
code: /* tsx */ `
8+
import { flushSync } from 'react-dom';
9+
10+
flushSync(() => {
11+
setSomething(123);
12+
});
13+
`,
14+
errors: [
15+
{ messageId: "noFlushSync" },
16+
],
17+
},
18+
{
19+
code: /* tsx */ `
20+
import reactDom from 'react-dom';
21+
22+
reactDom.flushSync(() => {
23+
setSomething(123);
24+
});
25+
`,
26+
errors: [
27+
{ messageId: "noFlushSync" },
28+
],
29+
},
30+
],
31+
valid: [
32+
...allValid,
33+
],
34+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { RuleFeature } from "@eslint-react/shared";
2+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
3+
import type { CamelCase } from "string-ts";
4+
5+
import { createRule } from "../utils";
6+
7+
export const RULE_NAME = "no-flush-sync";
8+
9+
export const RULE_FEATURES = [
10+
"CHK",
11+
] as const satisfies RuleFeature[];
12+
13+
export type MessageID = CamelCase<typeof RULE_NAME>;
14+
15+
export default createRule<[], MessageID>({
16+
meta: {
17+
type: "problem",
18+
docs: {
19+
description: "warns against using `flushSync`",
20+
[Symbol.for("rule_features")]: RULE_FEATURES,
21+
},
22+
messages: {
23+
noFlushSync: "Using 'flushSync' is uncommon and can hurt the performance of your app.",
24+
},
25+
schema: [],
26+
},
27+
name: RULE_NAME,
28+
create(context) {
29+
if (!context.sourceCode.text.includes("flushSync")) return {};
30+
return {
31+
CallExpression(node) {
32+
const { callee } = node;
33+
switch (callee.type) {
34+
case T.Identifier:
35+
if (callee.name === "flushSync") {
36+
context.report({ messageId: "noFlushSync", node });
37+
}
38+
return;
39+
case T.MemberExpression:
40+
if (callee.property.type === T.Identifier && callee.property.name === "flushSync") {
41+
context.report({ messageId: "noFlushSync", node });
42+
}
43+
return;
44+
}
45+
},
46+
};
47+
},
48+
defaultOptions: [],
49+
});

0 commit comments

Comments
 (0)