Skip to content

Commit d533b0c

Browse files
committed
fix: recover forward compatibility for missing rules
1 parent ad932a1 commit d533b0c

17 files changed

+1715
-3
lines changed

examples/vite-react-dom-app-v1/eslint.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export default tseslint.config(
5555
],
5656
plugins: {
5757
"react-hooks": eslintPluginReactHooks,
58+
},
59+
rules: {
60+
...eslintPluginReactHooks.configs.recommended.rules,
5861

5962
// Place the v1 ruleset here to test the compatibility in the v2 branch
6063
"@eslint-react/avoid-shorthand-boolean": "warn",
@@ -151,8 +154,5 @@ export default tseslint.config(
151154
"@eslint-react/naming-convention/filename-extension": "warn",
152155
"@eslint-react/naming-convention/use-state": "warn",
153156
},
154-
rules: {
155-
...eslintPluginReactHooks.configs.recommended.rules,
156-
},
157157
},
158158
);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import noUnusedClassComponentMembers from "./rules/no-unused-class-component-mem
5454
import noUnusedState from "./rules/no-unused-state";
5555
import noUseContext from "./rules/no-use-context";
5656
import noUselessForwardRef from "./rules/no-useless-forward-ref";
57+
import noUselessFragment from "./rules/no-useless-fragment";
5758
import preferDestructuringAssignment from "./rules/prefer-destructuring-assignment";
5859
import preferNamespaceImport from "./rules/prefer-namespace-import";
5960
import preferReadOnlyProps from "./rules/prefer-read-only-props";
@@ -64,6 +65,9 @@ import avoidShorthandBoolean from "./rules-removed/avoid-shorthand-boolean";
6465
import avoidShorthandFragment from "./rules-removed/avoid-shorthand-fragment";
6566
import preferShorthandBoolean from "./rules-removed/prefer-shorthand-boolean";
6667
import preferShorthandFragment from "./rules-removed/prefer-shorthand-fragment";
68+
import preferReactNamespaceImport from "./rules-removed/prefer-react-namespace-import";
69+
import noCommentTextnodes from "./rules-removed/no-comment-textnodes";
70+
import noComplexConditionalRendering from "./rules-removed/no-complex-conditional-rendering";
6771
/* eslint-enable perfectionist/sort-imports */
6872

6973
export const plugin = {
@@ -126,6 +130,7 @@ export const plugin = {
126130
"no-unused-state": noUnusedState,
127131
"no-use-context": noUseContext,
128132
"no-useless-forward-ref": noUselessForwardRef,
133+
"no-useless-fragment": noUselessFragment,
129134
"prefer-destructuring-assignment": preferDestructuringAssignment,
130135
"prefer-namespace-import": preferNamespaceImport,
131136
"prefer-read-only-props": preferReadOnlyProps,
@@ -134,6 +139,9 @@ export const plugin = {
134139
// Removed rules
135140
"avoid-shorthand-boolean": avoidShorthandBoolean,
136141
"avoid-shorthand-fragment": avoidShorthandFragment,
142+
"no-comment-textnodes": noCommentTextnodes,
143+
"no-complex-conditional-rendering": noComplexConditionalRendering,
144+
"prefer-react-namespace-import": preferReactNamespaceImport,
137145
"prefer-shorthand-boolean": preferShorthandBoolean,
138146
"prefer-shorthand-fragment": preferShorthandFragment,
139147
},

packages/plugins/eslint-plugin-react-x/src/rules-removed/avoid-shorthand-boolean.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type MessageID = CamelCase<typeof RULE_NAME>;
1414
export default createRule<[], MessageID>({
1515
meta: {
1616
type: "problem",
17+
deprecated: true,
1718
docs: {
1819
description: "Enforces explicit boolean values for boolean attributes.",
1920
[Symbol.for("rule_features")]: RULE_FEATURES,
@@ -23,6 +24,9 @@ export default createRule<[], MessageID>({
2324
avoidShorthandBoolean:
2425
"Avoid using shorthand boolean attribute '{{propName}}'. Use '{{propName}}={true}' instead.",
2526
},
27+
replacedBy: [
28+
"react-x/jsx-shorthand-boolean",
29+
],
2630
schema: [],
2731
},
2832
name: RULE_NAME,

packages/plugins/eslint-plugin-react-x/src/rules-removed/avoid-shorthand-fragment.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ export type MessageID = CamelCase<typeof RULE_NAME>;
1313
export default createRule<[], MessageID>({
1414
meta: {
1515
type: "problem",
16+
deprecated: true,
1617
docs: {
1718
description: "Enforces explicit `<Fragment>` components instead of the shorthand `<>` or `</>` syntax.",
1819
[Symbol.for("rule_features")]: RULE_FEATURES,
1920
},
2021
messages: {
2122
avoidShorthandFragment: "Avoid using shorthand fragment syntax. Use '{{jsxFragmentFactory}}' component instead.",
2223
},
24+
replacedBy: [
25+
"react-x/jsx-shorthand-fragment",
26+
],
2327
schema: [],
2428
},
2529
name: RULE_NAME,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
title: no-comment-textnodes
3+
---
4+
5+
**Full Name in `eslint-plugin-react-x`**
6+
7+
```sh copy
8+
react-x/no-comment-textnodes
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```sh copy
14+
@eslint-react/no-comment-textnodes
15+
```
16+
17+
**Presets**
18+
19+
- `x`
20+
- `recommended`
21+
- `recommended-typescript`
22+
- `recommended-type-checked`
23+
24+
## Description
25+
26+
Prevents comment strings (e.g. beginning with `//` or `/*`) from being accidentally inserted into the JSX element's textnodes.
27+
28+
This could be a mistake during code editing or it could be a misunderstanding of how JSX works. Either way, it's probably not what you intended.
29+
30+
## Examples
31+
32+
### Failing
33+
34+
```tsx
35+
import React from "react";
36+
37+
function MyComponent1() {
38+
return <div>// empty div</div>;
39+
// ^^^^^^^^^^^^
40+
// - Possible misused comment in text node. Comments inside children section of tag should be placed inside braces.
41+
}
42+
43+
function MyComponent2() {
44+
return <div>/* empty div */</div>;
45+
// ^^^^^^^^^^^^^^^
46+
// - Possible misused comment in text node. Comments inside children section of tag should be placed inside braces.
47+
}
48+
```
49+
50+
### Passing
51+
52+
```tsx
53+
import React from "react";
54+
55+
function MyComponent() {
56+
return <div>{/* empty div */}</div>;
57+
}
58+
```
59+
60+
### Legitimate uses
61+
62+
It's possible you may want to legitimately output comment start characters (`//` or `/*`) in a JSX text node. In which case, you can do the following:
63+
64+
```tsx
65+
import React from "react";
66+
67+
function MyComponent() {
68+
// 🟢 Good: This is a legitimate use of comment strings in JSX textnodes
69+
return <div>{"/* This will be output as a text node */"}</div>;
70+
}
71+
```
72+
73+
## Implementation
74+
75+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-comment-textnodes.ts)
76+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-comment-textnodes.spec.ts)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import tsx from "dedent";
2+
3+
import { allValid, ruleTester } from "../../../../../test";
4+
import rule, { RULE_NAME } from "./no-comment-textnodes";
5+
6+
ruleTester.run(RULE_NAME, rule, {
7+
invalid: [
8+
{
9+
code: tsx`<div>// invalid</div>`,
10+
errors: [{ messageId: "noCommentTextnodes" }],
11+
},
12+
{
13+
code: tsx`<>// invalid</>`,
14+
errors: [{ messageId: "noCommentTextnodes" }],
15+
},
16+
{
17+
code: tsx`<div>/* invalid */</div>`,
18+
errors: [{ messageId: "noCommentTextnodes" }],
19+
},
20+
{
21+
code: tsx`
22+
<div>
23+
// invalid
24+
</div>
25+
`,
26+
errors: [{ messageId: "noCommentTextnodes" }],
27+
},
28+
{
29+
code: tsx`
30+
<div>
31+
abcdef
32+
/* invalid */
33+
foo
34+
</div>
35+
`,
36+
errors: [{ messageId: "noCommentTextnodes" }],
37+
},
38+
{
39+
code: tsx`
40+
<div>
41+
{'abcdef'}
42+
// invalid
43+
{'foo'}
44+
</div>
45+
`,
46+
errors: [{ messageId: "noCommentTextnodes" }],
47+
},
48+
{
49+
code: "<span>/*</span>",
50+
errors: [{ messageId: "noCommentTextnodes" }],
51+
},
52+
],
53+
valid: [
54+
...allValid,
55+
"<App foo='test'>{/* valid */}</App>",
56+
"<strong>&nbsp;https://www.eslint-react.xyz/attachment/download/1</strong>",
57+
"<App /* valid */ placeholder={'foo'}/>",
58+
"</* valid */></>",
59+
],
60+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
2+
import type { TSESTree } from "@typescript-eslint/types";
3+
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
4+
import type { CamelCase } from "string-ts";
5+
import * as AST from "@eslint-react/ast";
6+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
7+
8+
import { createRule } from "../utils";
9+
10+
export const RULE_NAME = "no-comment-textnodes";
11+
12+
export const RULE_FEATURES = [] as const satisfies RuleFeature[];
13+
14+
export type MessageID = CamelCase<typeof RULE_NAME>;
15+
16+
export default createRule<[], MessageID>({
17+
meta: {
18+
type: "problem",
19+
deprecated: true,
20+
docs: {
21+
description: "Prevents comments from being inserted as text nodes.",
22+
[Symbol.for("rule_features")]: RULE_FEATURES,
23+
},
24+
messages: {
25+
noCommentTextnodes:
26+
"Possible misused comment in text node. Comments inside children section of tag should be placed inside braces.",
27+
},
28+
replacedBy: [
29+
"react-x/jsx-no-comment-textnodes",
30+
],
31+
schema: [],
32+
},
33+
name: RULE_NAME,
34+
create,
35+
defaultOptions: [],
36+
});
37+
38+
export function create(context: RuleContext<MessageID, []>): RuleListener {
39+
function hasCommentLike(node: TSESTree.JSXText | TSESTree.Literal) {
40+
if (AST.isOneOf([T.JSXAttribute, T.JSXExpressionContainer])(node.parent)) {
41+
return false;
42+
}
43+
const rawValue = context.sourceCode.getText(node);
44+
return /^\s*\/(?:\/|\*)/mu.test(rawValue);
45+
}
46+
const visitorFunction = (node: TSESTree.JSXText | TSESTree.Literal): void => {
47+
if (!AST.isOneOf([T.JSXElement, T.JSXFragment])(node.parent)) {
48+
return;
49+
}
50+
if (!hasCommentLike(node)) {
51+
return;
52+
}
53+
if (!node.parent.type.includes("JSX")) {
54+
return;
55+
}
56+
context.report({
57+
messageId: "noCommentTextnodes",
58+
node,
59+
});
60+
};
61+
return {
62+
JSXText: visitorFunction,
63+
Literal: visitorFunction,
64+
};
65+
}

0 commit comments

Comments
 (0)