Skip to content

Commit 755b55b

Browse files
authored
feat: add react-x/jsx-no-undef rule, closes #1016 (#1018)
1 parent e10cf58 commit 755b55b

File tree

10 files changed

+215
-6
lines changed

10 files changed

+215
-6
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"prefer-shorthand-boolean",
5454
"prefer-shorthand-fragment",
5555
"jsx-no-duplicate-props",
56+
"jsx-no-undef",
5657
"jsx-uses-vars",
5758
"---DOM Rules---",
5859
"dom-no-dangerously-set-innerhtml",

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

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Linter rules can have false positives, false negatives, and some rules are depen
7676
| [`prefer-shorthand-boolean`](./prefer-shorthand-boolean) | 0️⃣ | `🔧` | Enforces shorthand syntax for boolean attributes | |
7777
| [`prefer-shorthand-fragment`](./prefer-shorthand-fragment) | 0️⃣ | `🔧` | Enforces shorthand syntax for fragments | |
7878
| [`jsx-no-duplicate-props`](./jsx-no-duplicate-props) | 1️⃣ | | Disallow duplicate props in JSX elements | |
79+
| [`jsx-no-undef`](./jsx-no-undef) | 2️⃣ | | Disallow undefined variables in JSX elements | |
7980
| [`jsx-uses-vars`](./jsx-uses-vars) | 1️⃣ | | Marks variables used in JSX as used | |
8081

8182
## DOM Rules

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

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const name = "react-x/recommended-typescript";
77
export const rules = {
88
...recommended.rules,
99
"react-x/jsx-no-duplicate-props": "off",
10+
"react-x/jsx-no-undef": "off",
1011
"react-x/jsx-uses-vars": "off",
1112
} as const satisfies RulePreset;
1213

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { DEFAULT_ESLINT_REACT_SETTINGS } from "@eslint-react/shared";
44
export const name = "react-x/recommended";
55

66
export const rules = {
7+
"react-x/jsx-no-duplicate-props": "warn",
8+
"react-x/jsx-no-undef": "error",
9+
"react-x/jsx-uses-vars": "warn",
710
"react-x/no-access-state-in-setstate": "error",
811
"react-x/no-array-index-key": "warn",
912
"react-x/no-children-count": "warn",
@@ -20,7 +23,6 @@ export const rules = {
2023
"react-x/no-create-ref": "error",
2124
"react-x/no-default-props": "error",
2225
"react-x/no-direct-mutation-state": "error",
23-
"react-x/jsx-no-duplicate-props": "warn",
2426
"react-x/no-duplicate-key": "warn",
2527
"react-x/no-forward-ref": "warn",
2628
"react-x/no-implicit-key": "warn",
@@ -41,7 +43,6 @@ export const rules = {
4143
"react-x/no-unused-state": "warn",
4244
"react-x/no-use-context": "warn",
4345
"react-x/no-useless-forward-ref": "warn",
44-
"react-x/jsx-uses-vars": "warn",
4546
} as const satisfies RulePreset;
4647

4748
export const settings = {

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

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { name, version } from "../package.json";
22
import avoidShorthandBoolean from "./rules/avoid-shorthand-boolean";
33
import avoidShorthandFragment from "./rules/avoid-shorthand-fragment";
44
import jsxNoDuplicateProps from "./rules/jsx-no-duplicate-props";
5+
import jsxNoUndef from "./rules/jsx-no-undef";
56
import jsxUsesVars from "./rules/jsx-uses-vars";
67
import noAccessStateInSetstate from "./rules/no-access-state-in-setstate";
78
import noArrayIndexKey from "./rules/no-array-index-key";
@@ -111,6 +112,7 @@ export const plugin = {
111112

112113
// Part: JSX only rules
113114
"jsx-no-duplicate-props": jsxNoDuplicateProps,
115+
"jsx-no-undef": jsxNoUndef,
114116
"jsx-uses-vars": jsxUsesVars,
115117

116118
// Part: deprecated rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: jsx-no-undef
3+
---
4+
5+
**Full Name in `eslint-plugin-react-x`**
6+
7+
```plain copy
8+
react-x/jsx-no-undef
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```plain copy
14+
@eslint-react/jsx-no-undef
15+
```
16+
17+
**Presets**
18+
19+
- `core`
20+
- `recommended`
21+
22+
## Description
23+
24+
This rule is used to prevent the use of undefined variables in JSX. It checks for any undefined variables in the JSX code and reports them as errors.
25+
26+
## Examples
27+
28+
### Failing
29+
30+
```jsx
31+
const MyComponent = () => {
32+
return (
33+
<div>
34+
<Foo />
35+
</div>
36+
);
37+
};
38+
```
39+
40+
### Passing
41+
42+
```jsx
43+
import Foo from "./Foo";
44+
45+
const MyComponent = () => {
46+
return (
47+
<div>
48+
<Foo />
49+
</div>
50+
);
51+
};
52+
```
53+
54+
```jsx
55+
const Foo = () => <div>Foo</div>;
56+
57+
const MyComponent = () => {
58+
return (
59+
<div>
60+
<Foo />
61+
</div>
62+
);
63+
};
64+
```
65+
66+
## Implementation
67+
68+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-no-undef.ts)
69+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-no-undef.spec.ts)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import tsx from "dedent";
2+
3+
import { ruleTester } from "../../../../../test";
4+
import rule, { RULE_NAME } from "./jsx-no-undef";
5+
6+
ruleTester.run(RULE_NAME, rule, {
7+
invalid: [
8+
{
9+
code: tsx`
10+
const element = <Foo />;
11+
`,
12+
errors: [
13+
{
14+
messageId: "jsxNoUndef",
15+
data: { name: "Foo" },
16+
},
17+
],
18+
},
19+
{
20+
code: tsx`
21+
const element = <Foo />;
22+
const element = <Bar />;
23+
`,
24+
errors: [
25+
{
26+
messageId: "jsxNoUndef",
27+
data: { name: "Foo" },
28+
},
29+
{
30+
messageId: "jsxNoUndef",
31+
data: { name: "Bar" },
32+
},
33+
],
34+
},
35+
{
36+
code: tsx`
37+
function Foo() {
38+
return <Bar />;
39+
}
40+
`,
41+
errors: [
42+
{
43+
messageId: "jsxNoUndef",
44+
data: { name: "Bar" },
45+
},
46+
],
47+
},
48+
],
49+
valid: [
50+
{
51+
code: tsx`
52+
function Foo() {
53+
return <div />;
54+
}
55+
function Bar() {
56+
return <Foo />;
57+
}
58+
`,
59+
},
60+
{
61+
code: tsx`
62+
import { Foo } from "./Foo";
63+
import { Bar } from "./Bar";
64+
65+
function App() {
66+
return (
67+
<div>
68+
<Foo />
69+
<Bar />
70+
</div>
71+
);
72+
}
73+
`,
74+
},
75+
],
76+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
2+
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
3+
import type { CamelCase } from "string-ts";
4+
import * as VAR from "@eslint-react/var";
5+
6+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
7+
import { createRule } from "../utils";
8+
9+
export const RULE_NAME = "jsx-no-undef";
10+
11+
export const RULE_FEATURES = [] 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: "Disallow undefined variables in JSX.",
20+
[Symbol.for("rule_features")]: RULE_FEATURES,
21+
},
22+
messages: {
23+
jsxNoUndef: "JSX variable {{name}} is not defined.",
24+
},
25+
schema: [],
26+
},
27+
name: RULE_NAME,
28+
create,
29+
defaultOptions: [],
30+
});
31+
32+
export function create(context: RuleContext<MessageID, []>): RuleListener {
33+
return {
34+
JSXIdentifier(node) {
35+
if (node.name === "this") {
36+
return;
37+
}
38+
// Skip JsxIntrinsicElements
39+
if (/^[a-z]/u.test(node.name)) {
40+
return;
41+
}
42+
// Skip JSXMemberExpression property
43+
if (node.parent.type === T.JSXMemberExpression && node.parent.property === node) {
44+
return;
45+
}
46+
const initialScope = context.sourceCode.getScope(node);
47+
if (VAR.findVariable(node.name, initialScope) == null) {
48+
context.report({
49+
messageId: "jsxNoUndef",
50+
node,
51+
data: { name: node.name },
52+
});
53+
}
54+
},
55+
};
56+
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export const name = "@eslint-react/all";
1212
export const rules = {
1313
"@eslint-react/avoid-shorthand-boolean": "off",
1414
"@eslint-react/avoid-shorthand-fragment": "off",
15+
"@eslint-react/jsx-no-duplicate-props": "warn",
16+
"@eslint-react/jsx-no-undef": "error",
17+
"@eslint-react/jsx-uses-vars": "warn",
1518
"@eslint-react/no-access-state-in-setstate": "error",
1619
"@eslint-react/no-array-index-key": "warn",
1720
"@eslint-react/no-children-count": "warn",
@@ -31,7 +34,6 @@ export const rules = {
3134
"@eslint-react/no-create-ref": "error",
3235
"@eslint-react/no-default-props": "error",
3336
"@eslint-react/no-direct-mutation-state": "error",
34-
"@eslint-react/jsx-no-duplicate-props": "warn",
3537
"@eslint-react/no-duplicate-key": "warn",
3638
"@eslint-react/no-forward-ref": "warn",
3739
"@eslint-react/no-implicit-key": "warn",
@@ -58,7 +60,6 @@ export const rules = {
5860
"@eslint-react/prefer-destructuring-assignment": "warn",
5961
"@eslint-react/prefer-shorthand-boolean": "warn",
6062
"@eslint-react/prefer-shorthand-fragment": "warn",
61-
"@eslint-react/jsx-uses-vars": "warn",
6263

6364
"@eslint-react/dom/no-dangerously-set-innerhtml": "warn",
6465
"@eslint-react/dom/no-dangerously-set-innerhtml-with-children": "error",

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import reactx from "eslint-plugin-react-x";
55
export const name = "@eslint-react/core";
66

77
export const rules = {
8+
"@eslint-react/jsx-no-duplicate-props": "warn",
9+
"@eslint-react/jsx-no-undef": "error",
10+
"@eslint-react/jsx-uses-vars": "warn",
811
"@eslint-react/no-access-state-in-setstate": "error",
912
"@eslint-react/no-array-index-key": "warn",
1013
"@eslint-react/no-children-count": "warn",
@@ -21,7 +24,6 @@ export const rules = {
2124
"@eslint-react/no-create-ref": "error",
2225
"@eslint-react/no-default-props": "error",
2326
"@eslint-react/no-direct-mutation-state": "error",
24-
"@eslint-react/jsx-no-duplicate-props": "warn",
2527
"@eslint-react/no-duplicate-key": "warn",
2628
"@eslint-react/no-forward-ref": "warn",
2729
"@eslint-react/no-implicit-key": "warn",
@@ -43,7 +45,6 @@ export const rules = {
4345
"@eslint-react/no-use-context": "warn",
4446
"@eslint-react/no-useless-forward-ref": "warn",
4547
"@eslint-react/no-useless-fragment": "warn",
46-
"@eslint-react/jsx-uses-vars": "warn",
4748
} as const satisfies RulePreset;
4849

4950
export const plugins = {

0 commit comments

Comments
 (0)