Skip to content

Commit ddd7b4c

Browse files
committed
feat(react-x): add 'jsx-uses-react' rule
1 parent 83881ca commit ddd7b4c

File tree

18 files changed

+191
-5
lines changed

18 files changed

+191
-5
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"---Core Rules---",
55
"jsx-no-duplicate-props",
66
"jsx-no-undef",
7+
"jsx-uses-react",
78
"jsx-uses-vars",
89
"no-access-state-in-setstate",
910
"no-array-index-key",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Linter rules can have false positives, false negatives, and some rules are depen
2727
| :----------------------------------------------------------------------------------- | :- | :------: | :---------------------------------------------------------------------------------------------------- | :-----: |
2828
| [`jsx-no-duplicate-props`](./jsx-no-duplicate-props) | 1️⃣ | | Disallow duplicate props in JSX elements | |
2929
| [`jsx-no-undef`](./jsx-no-undef) | 0️⃣ | | Disallow undefined variables in JSX elements | |
30+
| [`jsx-uses-react`](./jsx-uses-react) | 1️⃣ | | Marks React variables as used when JSX is used in the file | |
3031
| [`jsx-uses-vars`](./jsx-uses-vars) | 1️⃣ | | Marks variables used in JSX elements as used | |
3132
| [`no-access-state-in-setstate`](./no-access-state-in-setstate) | 2️⃣ | | Disallow accessing `this.state` inside `setState` calls | |
3233
| [`no-array-index-key`](./no-array-index-key) | 1️⃣ | | Disallow an item's index in the array as its key | |

packages/plugins/eslint-plugin-react-x/src/configs/recommended-typescript.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const rules = {
88
...recommended.rules,
99
"react-x/jsx-no-duplicate-props": "off",
1010
"react-x/jsx-no-undef": "off",
11+
"react-x/jsx-uses-react": "off",
1112
"react-x/jsx-uses-vars": "off",
1213
} as const satisfies RulePreset;
1314

packages/plugins/eslint-plugin-react-x/src/configs/recommended.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const name = "react-x/recommended";
55

66
export const rules = {
77
"react-x/jsx-no-duplicate-props": "warn",
8+
"react-x/jsx-uses-react": "warn",
89
"react-x/jsx-uses-vars": "warn",
910
"react-x/no-access-state-in-setstate": "error",
1011
"react-x/no-array-index-key": "warn",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import avoidShorthandBoolean from "./rules/avoid-shorthand-boolean";
33
import avoidShorthandFragment from "./rules/avoid-shorthand-fragment";
44
import jsxNoDuplicateProps from "./rules/jsx-no-duplicate-props";
55
import jsxNoUndef from "./rules/jsx-no-undef";
6+
import jsxUsesReact from "./rules/jsx-uses-react";
67
import jsxUsesVars from "./rules/jsx-uses-vars";
78
import noAccessStateInSetstate from "./rules/no-access-state-in-setstate";
89
import noArrayIndexKey from "./rules/no-array-index-key";
@@ -113,6 +114,7 @@ export const plugin = {
113114
// Part: JSX only rules
114115
"jsx-no-duplicate-props": jsxNoDuplicateProps,
115116
"jsx-no-undef": jsxNoUndef,
117+
"jsx-uses-react": jsxUsesReact,
116118
"jsx-uses-vars": jsxUsesVars,
117119

118120
// Part: deprecated rules
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: jsx-uses-react
3+
---
4+
5+
**Full Name in `eslint-plugin-react-x`**
6+
7+
```plain copy
8+
react-x/jsx-uses-react
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```plain copy
14+
@eslint-react/jsx-uses-react
15+
```
16+
17+
**Presets**
18+
19+
- `core`
20+
- `recommended`
21+
22+
## Description
23+
24+
Marks React variables as used when JSX is used in the file.
25+
26+
If you are using the `@jsx` pragma this rule will mark the designated variable and not the React one.
27+
28+
This rule does nothing when using the [New JSX Transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) or if the `no-unused-vars` rule is not enabled.
29+
30+
## Examples
31+
32+
### Failing
33+
34+
```tsx
35+
import React from "react";
36+
// nothing to do with React
37+
```
38+
39+
```tsx
40+
/** @jsx Foo */
41+
import React from "react";
42+
// nothing to do with React
43+
44+
const Hello = <div>Hello</div>;
45+
```
46+
47+
### Passing
48+
49+
```tsx
50+
import React from "react";
51+
52+
const Hello = <div>Hello</div>;
53+
```
54+
55+
```tsx
56+
/** @jsx Foo */
57+
import Foo from "foo";
58+
59+
const Hello = <div>Hello</div>;
60+
```
61+
62+
## Implementation
63+
64+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-uses-react.ts)
65+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-uses-react.spec.ts)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import tsx from "dedent";
2+
3+
import { allValid, ruleTester } from "../../../../../test";
4+
import rule from "./jsx-uses-react";
5+
6+
ruleTester.run("no-unused-vars", rule, {
7+
// TODO: Add invalid test cases
8+
invalid: [],
9+
valid: [
10+
...allValid,
11+
{
12+
code: tsx`
13+
import React from "react";
14+
15+
const Hello = <div>Hello</div>;
16+
`,
17+
},
18+
{
19+
code: tsx`
20+
/** @jsx Foo */
21+
import Foo from "foo";
22+
23+
const Hello = <div>Hello</div>;
24+
`,
25+
},
26+
],
27+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
2+
import type { CamelCase } from "string-ts";
3+
import { JsxRuntime, type RuleContext, type RuleFeature } from "@eslint-react/kit";
4+
5+
import { JsxEmit } from "typescript";
6+
import { createRule } from "../utils";
7+
8+
export const RULE_NAME = "jsx-uses-react";
9+
10+
export const RULE_FEATURES = [] as const satisfies RuleFeature[];
11+
12+
export type MessageID = CamelCase<typeof RULE_NAME>;
13+
14+
export default createRule<[], MessageID>({
15+
meta: {
16+
type: "problem",
17+
docs: {
18+
description: "Marks React variables as used when JSX is used in the file.",
19+
[Symbol.for("rule_features")]: RULE_FEATURES,
20+
},
21+
messages: {
22+
jsxUsesReact: "",
23+
},
24+
schema: [],
25+
},
26+
name: RULE_NAME,
27+
create,
28+
defaultOptions: [],
29+
});
30+
31+
export function create(context: RuleContext<MessageID, []>): RuleListener {
32+
const { jsx, jsxFactory, jsxFragmentFactory, reactNamespace } = JsxRuntime.getJsxRuntimeOptionsFromContext(context);
33+
// If we are using the New JSX Transform, this rule should do nothing.
34+
if (jsx === JsxEmit.ReactJSX || jsx === JsxEmit.ReactJSXDev) return {};
35+
return {
36+
JSXFragment(node) {
37+
context.sourceCode.markVariableAsUsed(jsxFragmentFactory, node);
38+
},
39+
JSXOpeningElement(node) {
40+
context.sourceCode.markVariableAsUsed(reactNamespace, node);
41+
context.sourceCode.markVariableAsUsed(jsxFactory, node);
42+
},
43+
JSXOpeningFragment(node) {
44+
context.sourceCode.markVariableAsUsed(reactNamespace, node);
45+
context.sourceCode.markVariableAsUsed(jsxFactory, node);
46+
},
47+
};
48+
}

packages/plugins/eslint-plugin-react-x/src/rules/jsx-uses-vars.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import tsx from "dedent";
22

33
import { allValid, ruleTester } from "../../../../../test";
4-
import rule, { RULE_NAME } from "./jsx-uses-vars";
4+
import rule from "./jsx-uses-vars";
55

6-
ruleTester.run(RULE_NAME, rule, {
6+
ruleTester.run("no-unused-vars", rule, {
7+
// TODO: Add invalid test cases
78
invalid: [],
89
valid: [
910
...allValid,

packages/plugins/eslint-plugin-react-x/src/rules/jsx-uses-vars.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default createRule<[], MessageID>({
1919
[Symbol.for("rule_features")]: RULE_FEATURES,
2020
},
2121
messages: {
22-
jsxUsesVars: "JSX variables should be marked as used.",
22+
jsxUsesVars: "",
2323
},
2424
schema: [],
2525
},

0 commit comments

Comments
 (0)