Skip to content

Commit ab1b274

Browse files
authored
feat(plugins/x): add 'no-context-provider' (#873)
1 parent 1187e30 commit ab1b274

File tree

9 files changed

+163
-0
lines changed

9 files changed

+163
-0
lines changed

packages/plugins/eslint-plugin-react-x/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default [
4444
"react-x/no-component-will-mount": "error",
4545
"react-x/no-component-will-receive-props": "error",
4646
"react-x/no-component-will-update": "error",
47+
"react-x/no-context-provider": "warn",
4748
"react-x/no-create-ref": "error",
4849
"react-x/no-default-props": "error",
4950
"react-x/no-direct-mutation-state": "error",
@@ -94,6 +95,7 @@ export default [
9495
| `no-component-will-mount` | Prevents using `componentWillMount`. || | |
9596
| `no-component-will-receive-props` | Prevents using `componentWillReceiveProps`. || | |
9697
| `no-component-will-update` | Prevents using `componentWillUpdate`. || | |
98+
| `no-context-provider` | Prevents using `Context.Provider`. || | |
9799
| `no-create-ref` | Prevents using `createRef`. || | |
98100
| `no-default-props` | Prevents using `defaultProps` property in favor of ES6 default parameters. | ✔️ | | |
99101
| `no-direct-mutation-state` | Prevents direct mutation of `this.state`. | ✔️ | | |

packages/plugins/eslint-plugin-react-x/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import noComplexConditionalRendering from "./rules/no-complex-conditional-render
2222
import noComponentWillMount from "./rules/no-component-will-mount";
2323
import noComponentWillReceiveProps from "./rules/no-component-will-receive-props";
2424
import noComponentWillUpdate from "./rules/no-component-will-update";
25+
import noContextProvider from "./rules/no-context-provider";
2526
import noCreateRef from "./rules/no-create-ref";
2627
import noDefaultProps from "./rules/no-default-props";
2728
import noDirectMutationState from "./rules/no-direct-mutation-state";
@@ -90,6 +91,7 @@ export default {
9091
"no-component-will-mount": noComponentWillMount,
9192
"no-component-will-receive-props": noComponentWillReceiveProps,
9293
"no-component-will-update": noComponentWillUpdate,
94+
"no-context-provider": noContextProvider,
9395
"no-create-ref": noCreateRef,
9496
"no-default-props": noDefaultProps,
9597
"no-direct-mutation-state": noDirectMutationState,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { ruleTester } from "../../../../../test";
2+
import rule, { RULE_NAME } from "./no-context-provider";
3+
4+
ruleTester.run(RULE_NAME, rule, {
5+
invalid: [
6+
{
7+
code: "<Context.Provider />",
8+
errors: [{ messageId: "noContextProvider" }],
9+
output: "<Context />",
10+
settings: {
11+
"react-x": {
12+
version: "19.0.0",
13+
},
14+
},
15+
},
16+
{
17+
code: "<Context.Provider><App /></Context.Provider>",
18+
errors: [{ messageId: "noContextProvider" }],
19+
output: "<Context><App /></Context>",
20+
settings: {
21+
"react-x": {
22+
version: "19.0.0",
23+
},
24+
},
25+
},
26+
{
27+
code: "<Context.Provider>{children}</Context.Provider>",
28+
errors: [{ messageId: "noContextProvider" }],
29+
output: "<Context>{children}</Context>",
30+
settings: {
31+
"react-x": {
32+
version: "19.0.0",
33+
},
34+
},
35+
},
36+
{
37+
code: "<Foo.Bar.Provider>{children}</Foo.Bar.Provider>",
38+
errors: [{ messageId: "noContextProvider" }],
39+
output: "<Foo.Bar>{children}</Foo.Bar>",
40+
settings: {
41+
"react-x": {
42+
version: "19.0.0",
43+
},
44+
},
45+
},
46+
],
47+
valid: [
48+
{
49+
code: "<Context.Provider />",
50+
settings: {
51+
"react-x": {
52+
version: "18.0.0",
53+
},
54+
},
55+
},
56+
],
57+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as JSX from "@eslint-react/jsx";
2+
import { decodeSettings, normalizeSettings } from "@eslint-react/shared";
3+
import { compare } from "compare-versions";
4+
import type { CamelCase } from "string-ts";
5+
6+
import { createRule } from "../utils";
7+
8+
export const RULE_NAME = "no-context-provider";
9+
10+
export type MessageID = CamelCase<typeof RULE_NAME>;
11+
12+
export default createRule<[], MessageID>({
13+
meta: {
14+
type: "problem",
15+
docs: {
16+
description: "disallow the use of '<Context.Provider>'",
17+
},
18+
fixable: "code",
19+
messages: {
20+
noContextProvider: "In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'.",
21+
},
22+
schema: [],
23+
},
24+
name: RULE_NAME,
25+
create(context) {
26+
if (!context.sourceCode.text.includes(".Provider")) return {};
27+
const { version } = normalizeSettings(decodeSettings(context.settings));
28+
if (compare(version, "19.0.0", "<")) return {};
29+
return {
30+
JSXElement(node) {
31+
const elementName = JSX.getElementName(node.openingElement);
32+
if (!elementName.endsWith(".Provider")) return;
33+
context.report({
34+
messageId: "noContextProvider",
35+
node,
36+
fix(fixer) {
37+
const providerName = elementName.replace(/\.Provider$/, "");
38+
const openingElement = node.openingElement;
39+
const closingElement = node.closingElement;
40+
if (!closingElement) return fixer.replaceText(openingElement.name, providerName);
41+
return [
42+
fixer.replaceText(openingElement.name, providerName),
43+
fixer.replaceText(closingElement.name, providerName),
44+
];
45+
},
46+
});
47+
},
48+
};
49+
},
50+
defaultOptions: [],
51+
});

packages/plugins/eslint-plugin/src/configs/all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const rules = {
3030
"@eslint-react/no-component-will-mount": "error",
3131
"@eslint-react/no-component-will-receive-props": "error",
3232
"@eslint-react/no-component-will-update": "error",
33+
"@eslint-react/no-context-provider": "warn",
3334
"@eslint-react/no-create-ref": "error",
3435
"@eslint-react/no-default-props": "error",
3536
"@eslint-react/no-direct-mutation-state": "error",

packages/plugins/eslint-plugin/src/configs/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const rules = {
1919
"@eslint-react/no-component-will-mount": "error",
2020
"@eslint-react/no-component-will-receive-props": "error",
2121
"@eslint-react/no-component-will-update": "error",
22+
"@eslint-react/no-context-provider": "warn",
2223
"@eslint-react/no-create-ref": "error",
2324
"@eslint-react/no-default-props": "error",
2425
"@eslint-react/no-direct-mutation-state": "error",

website/pages/docs/rules/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default {
2626
"no-component-will-mount": "no-component-will-mount",
2727
"no-component-will-receive-props": "no-component-will-receive-props",
2828
"no-component-will-update": "no-component-will-update",
29+
"no-context-provider": "no-context-provider",
2930
"no-create-ref": "no-create-ref",
3031
"no-default-props": "no-default-props",
3132
"no-direct-mutation-state": "no-direct-mutation-state",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# no-context-provider
2+
3+
## Rule category
4+
5+
Restriction.
6+
7+
## What it does
8+
9+
Disallows using `<Context.Provider>`.
10+
11+
## Why is this bad?
12+
13+
In React 19, you can render `<Context>` as a provider instead of `<Context.Provider>`.
14+
15+
## Examples
16+
17+
### Failing
18+
19+
```tsx
20+
const ThemeContext = createContext('');
21+
22+
function App({children}) {
23+
return (
24+
<ThemeContext.Provider value="light">
25+
{children}
26+
</ThemeContext>
27+
);
28+
}
29+
```
30+
31+
### Passing
32+
33+
```tsx
34+
const ThemeContext = createContext('');
35+
36+
function App({children}) {
37+
return (
38+
<ThemeContext value="dark">
39+
{children}
40+
</ThemeContext>
41+
);
42+
}
43+
```
44+
45+
## Further reading
46+
47+
- [React: `<Context>` as a provider](https://react.dev/blog/2024/12/05/react-19#context-as-a-provider)

website/pages/docs/rules/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
| [`no-component-will-mount`](no-component-will-mount) | Prevents using `componentWillMount`. || | |
4343
| [`no-component-will-receive-props`](no-component-will-receive-props) | Prevents using `componentWillReceiveProps`. || | |
4444
| [`no-component-will-update`](no-component-will-update) | Prevents using `componentWillUpdate`. || | |
45+
| [`no-context-provider`](no-context-provider) | Prevents using `<Context.Provider>`. || | |
4546
| [`no-create-ref`](no-create-ref) | Prevents using `createRef`. || | |
4647
| [`no-default-props`](no-default-props) | Prevents using `defaultProps` property in favor of ES6 default parameters. | ✔️ | | |
4748
| [`no-direct-mutation-state`](no-direct-mutation-state) | Prevents direct mutation of `this.state`. | ✔️ | | |

0 commit comments

Comments
 (0)