Skip to content

Commit 9fdb4cf

Browse files
committed
feat: add 'jsx-shorthand-*' rules
1 parent 8fdbadd commit 9fdb4cf

13 files changed

+739
-0
lines changed
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+
- [`jsx-shorthand-boolean`](./jsx-shorthand-boolean)\
57+
Enforces the use of shorthand syntax for boolean attributes.
58+
- [`jsx-shorthand-fragment`](./jsx-shorthand-fragment)\
59+
Enforces the use of shorthand syntax for fragments.
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+
});
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+
}
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+
- [`jsx-shorthand-boolean`](./jsx-shorthand-boolean)\
65+
Enforces the use of shorthand syntax for boolean attributes.
66+
- [`jsx-shorthand-fragment`](./jsx-shorthand-fragment)\
67+
Enforces the use of shorthand syntax for fragments.
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+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
2+
import type { CamelCase } from "string-ts";
3+
import { JsxConfig, type RuleContext, type RuleFeature } from "@eslint-react/kit";
4+
5+
import { createRule } from "../utils";
6+
7+
export const RULE_NAME = "avoid-shorthand-fragment";
8+
9+
export const RULE_FEATURES = [] as const satisfies RuleFeature[];
10+
11+
export type MessageID = CamelCase<typeof RULE_NAME>;
12+
13+
export default createRule<[], MessageID>({
14+
meta: {
15+
type: "problem",
16+
docs: {
17+
description: "Enforces explicit `<Fragment>` components instead of the shorthand `<>` or `</>` syntax.",
18+
[Symbol.for("rule_features")]: RULE_FEATURES,
19+
},
20+
messages: {
21+
avoidShorthandFragment: "Avoid using shorthand fragment syntax. Use '{{jsxFragmentFactory}}' component instead.",
22+
},
23+
schema: [],
24+
},
25+
name: RULE_NAME,
26+
create,
27+
defaultOptions: [],
28+
});
29+
30+
export function create(context: RuleContext<MessageID, []>): RuleListener {
31+
const jsxConfigFromContext = JsxConfig.getFromContext(context);
32+
const jsxConfigFromAnnotation = JsxConfig.getFromAnnotation(context);
33+
const jsxConfig = {
34+
...jsxConfigFromContext,
35+
...jsxConfigFromAnnotation,
36+
};
37+
38+
return {
39+
JSXFragment(node) {
40+
context.report({
41+
messageId: "avoidShorthandFragment",
42+
node,
43+
data: {
44+
jsxFragmentFactory: jsxConfig.jsxFragmentFactory,
45+
},
46+
});
47+
},
48+
};
49+
}

0 commit comments

Comments
 (0)