Skip to content

Commit c93154d

Browse files
committed
fix: recover forward compatibility for shorthand rules
1 parent 34a400b commit c93154d

14 files changed

+734
-3
lines changed

packages/plugins/eslint-plugin-react-hooks-extra/src/plugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { name, version } from "../package.json";
22

3+
import noDirectSetStateInUseEffect from "./rules/no-direct-set-state-in-use-effect";
4+
import noDirectSetStateInUseLayoutEffect from "./rules/no-direct-set-state-in-use-layout-effect";
5+
6+
/* eslint-disable perfectionist/sort-imports */
37
import noUnnecessaryUseCallback from "./rules-removed/no-unnecessary-use-callback";
48
import noUnnecessaryUseMemo from "./rules-removed/no-unnecessary-use-memo";
59
import noUnnecessaryUsePrefix from "./rules-removed/no-unnecessary-use-prefix";
610
import preferUseStateLazyInitialization from "./rules-removed/prefer-use-state-lazy-initialization";
7-
8-
import noDirectSetStateInUseEffect from "./rules/no-direct-set-state-in-use-effect";
9-
import noDirectSetStateInUseLayoutEffect from "./rules/no-direct-set-state-in-use-layout-effect";
11+
/* eslint-enable perfectionist/sort-imports */
1012

1113
export const plugin = {
1214
meta: {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { name, version } from "../package.json";
2+
23
import jsxKeyBeforeSpread from "./rules/jsx-key-before-spread";
34
import jsxNoCommentTextnodes from "./rules/jsx-no-comment-textnodes";
45
import jsxNoDuplicateProps from "./rules/jsx-no-duplicate-props";
@@ -58,6 +59,13 @@ import preferNamespaceImport from "./rules/prefer-namespace-import";
5859
import preferReadOnlyProps from "./rules/prefer-read-only-props";
5960
import preferUseStateLazyInitialization from "./rules/prefer-use-state-lazy-initialization";
6061

62+
/* eslint-disable perfectionist/sort-imports */
63+
import avoidShorthandBoolean from "./rules-removed/avoid-shorthand-boolean";
64+
import avoidShorthandFragment from "./rules-removed/avoid-shorthand-fragment";
65+
import preferShorthandBoolean from "./rules-removed/prefer-shorthand-boolean";
66+
import preferShorthandFragment from "./rules-removed/prefer-shorthand-fragment";
67+
/* eslint-enable perfectionist/sort-imports */
68+
6169
export const plugin = {
6270
meta: {
6371
name,
@@ -122,5 +130,11 @@ export const plugin = {
122130
"prefer-namespace-import": preferNamespaceImport,
123131
"prefer-read-only-props": preferReadOnlyProps,
124132
"prefer-use-state-lazy-initialization": preferUseStateLazyInitialization,
133+
134+
// Removed rules
135+
"avoid-shorthand-boolean": avoidShorthandBoolean,
136+
"avoid-shorthand-fragment": avoidShorthandFragment,
137+
"prefer-shorthand-boolean": preferShorthandBoolean,
138+
"prefer-shorthand-fragment": preferShorthandFragment,
125139
},
126140
} as const;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: avoid-shorthand-boolean
3+
---
4+
5+
**Full Name in `eslint-plugin-react-x`**
6+
7+
```sh copy
8+
react-x/avoid-shorthand-boolean
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```sh copy
14+
@eslint-react/avoid-shorthand-boolean
15+
```
16+
17+
**Features**
18+
19+
`🔧`
20+
21+
## Description
22+
23+
Enforces explicit boolean values for boolean attributes.
24+
25+
## Examples
26+
27+
### Failing
28+
29+
```tsx
30+
const Input = <input type="checkbox" checked />;
31+
// ^^^^^^^
32+
// - Expected `checked={true}` instead of `checked`
33+
const button = <button disabled />;
34+
// ^^^^^^^^
35+
// - Expected `disabled={true}` instead of `disabled`
36+
```
37+
38+
### Passing
39+
40+
```tsx
41+
const Input = <input type="checkbox" checked={true} />;
42+
const button = <button disabled={true} />;
43+
```
44+
45+
## Implementation
46+
47+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/avoid-shorthand-boolean.ts)
48+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/avoid-shorthand-boolean.spec.ts)
49+
50+
---
51+
52+
## See Also
53+
54+
- [`avoid-shorthand-fragment`](./avoid-shorthand-fragment)\
55+
Enforces the use of explicit `<Fragment>` or `<React.Fragment>` components instead of the shorthand `<>` or `</>` syntax.
56+
- [`prefer-shorthand-boolean`](./prefer-shorthand-boolean)\
57+
Enforces the use of shorthand syntax for boolean attributes.
58+
- [`prefer-shorthand-fragment`](./prefer-shorthand-fragment)\
59+
Enforces the use of shorthand syntax for fragments.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import tsx from "dedent";
2+
3+
import { allValid, ruleTester } from "../../../../../test";
4+
import rule, { RULE_NAME } from "./avoid-shorthand-boolean";
5+
6+
ruleTester.run(RULE_NAME, rule, {
7+
invalid: [
8+
{
9+
code: tsx`<input disabled />`,
10+
errors: [{
11+
messageId: "avoidShorthandBoolean",
12+
data: { propName: "disabled" },
13+
}],
14+
output: tsx`<input disabled={true} />`,
15+
},
16+
{
17+
code: tsx`<App foo />`,
18+
errors: [{
19+
messageId: "avoidShorthandBoolean",
20+
data: { propName: "foo" },
21+
}],
22+
output: tsx`<App foo={true} />`,
23+
},
24+
{
25+
code: tsx`<App foo bar />`,
26+
errors: [
27+
{
28+
messageId: "avoidShorthandBoolean",
29+
data: { propName: "foo" },
30+
},
31+
{
32+
messageId: "avoidShorthandBoolean",
33+
data: { propName: "bar" },
34+
},
35+
],
36+
output: tsx`<App foo={true} bar={true} />`,
37+
},
38+
],
39+
valid: [
40+
...allValid,
41+
tsx`<input disabled={true} />`,
42+
tsx`<App foo={true} />`,
43+
tsx`<App foo={true} bar={true} />`,
44+
tsx`<App foo={false} bar={false} />`,
45+
tsx`<App foo={false} bar={false} baz={false} />`,
46+
],
47+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { RuleFeature } from "@eslint-react/kit";
2+
import type { RuleContext, RuleListener } from "@typescript-eslint/utils/ts-eslint";
3+
import type { CamelCase } from "string-ts";
4+
import * as ER from "@eslint-react/core";
5+
6+
import { createRule } from "../utils";
7+
8+
export const RULE_NAME = "avoid-shorthand-boolean";
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: "Enforces explicit boolean values for boolean attributes.",
19+
[Symbol.for("rule_features")]: RULE_FEATURES,
20+
},
21+
fixable: "code",
22+
messages: {
23+
avoidShorthandBoolean:
24+
"Avoid using shorthand boolean attribute '{{propName}}'. Use '{{propName}}={true}' instead.",
25+
},
26+
schema: [],
27+
},
28+
name: RULE_NAME,
29+
create,
30+
defaultOptions: [],
31+
});
32+
33+
export function create(context: RuleContext<MessageID, []>): RuleListener {
34+
return {
35+
JSXAttribute(node) {
36+
if (node.value == null) {
37+
context.report({
38+
messageId: "avoidShorthandBoolean",
39+
node,
40+
data: {
41+
propName: ER.getAttributeName(context, node),
42+
},
43+
fix: (fixer) => fixer.insertTextAfter(node.name, `={true}`),
44+
});
45+
}
46+
},
47+
};
48+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: avoid-shorthand-fragment
3+
---
4+
5+
**Full Name in `eslint-plugin-react-x`**
6+
7+
```sh copy
8+
react-x/avoid-shorthand-fragment
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```sh copy
14+
@eslint-react/avoid-shorthand-fragment
15+
```
16+
17+
## Description
18+
19+
Enforces explicit `<Fragment>` components instead of the shorthand `<>` or `</>` syntax.
20+
21+
## Examples
22+
23+
### Failing
24+
25+
```tsx
26+
import React from "react";
27+
28+
export function MyComponent() {
29+
return (
30+
<>
31+
<button />
32+
<button />
33+
</>
34+
);
35+
}
36+
```
37+
38+
### Passing
39+
40+
```tsx
41+
import React, { Fragment } from "react";
42+
43+
export function MyComponent() {
44+
return (
45+
<Fragment>
46+
<button />
47+
<button />
48+
</Fragment>
49+
);
50+
}
51+
```
52+
53+
## Implementation
54+
55+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/avoid-shorthand-fragment.ts)
56+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/avoid-shorthand-fragment.spec.ts)
57+
58+
---
59+
60+
## See Also
61+
62+
- [`avoid-shorthand-boolean`](./avoid-shorthand-boolean)\
63+
Enforces the use of explicit boolean values for boolean attributes.
64+
- [`prefer-shorthand-boolean`](./prefer-shorthand-boolean)\
65+
Enforces the use of shorthand syntax for boolean attributes.
66+
- [`prefer-shorthand-fragment`](./prefer-shorthand-fragment)\
67+
Enforces the use of shorthand syntax for fragments.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import tsx from "dedent";
2+
3+
import { allValid, ruleTester } from "../../../../../test";
4+
import rule, { RULE_NAME } from "./avoid-shorthand-fragment";
5+
6+
ruleTester.run(RULE_NAME, rule, {
7+
invalid: [
8+
{
9+
code: tsx`<><div /></>`,
10+
errors: [
11+
{
12+
messageId: "avoidShorthandFragment",
13+
},
14+
],
15+
},
16+
{
17+
code: tsx`<><div /><div /></>`,
18+
errors: [
19+
{
20+
messageId: "avoidShorthandFragment",
21+
},
22+
],
23+
},
24+
{
25+
code: tsx`
26+
/** @jsx createElement */
27+
/** @jsxFrag Fragment */
28+
29+
const element = <><div /></>;
30+
`,
31+
errors: [
32+
{
33+
messageId: "avoidShorthandFragment",
34+
data: {
35+
jsxFragmentFactory: "Fragment",
36+
},
37+
},
38+
],
39+
},
40+
{
41+
code: tsx`
42+
/** @jsx React.createElement */
43+
/** @jsxFrag React.Fragment */
44+
45+
const element = <><div /></>;
46+
`,
47+
errors: [
48+
{
49+
messageId: "avoidShorthandFragment",
50+
data: {
51+
jsxFragmentFactory: "React.Fragment",
52+
},
53+
},
54+
],
55+
},
56+
{
57+
code: tsx`
58+
/** @jsx h */
59+
/** @jsxFrag Fragment */
60+
61+
const element = <><div /></>;
62+
`,
63+
errors: [
64+
{
65+
messageId: "avoidShorthandFragment",
66+
data: {
67+
jsxFragmentFactory: "Fragment",
68+
},
69+
},
70+
],
71+
},
72+
{
73+
code: tsx`
74+
/** @jsx Preact.h */
75+
/** @jsxFrag Preact.Fragment */
76+
77+
const element = <><div /></>;
78+
`,
79+
errors: [
80+
{
81+
messageId: "avoidShorthandFragment",
82+
data: {
83+
jsxFragmentFactory: "Preact.Fragment",
84+
},
85+
},
86+
],
87+
},
88+
],
89+
valid: [
90+
...allValid,
91+
"<React.Fragment><Foo /><Bar /></React.Fragment>",
92+
"<Fragment>foo<div /></Fragment>",
93+
],
94+
});

0 commit comments

Comments
 (0)