@codelaw/rules-react 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ ---
2
+ id: react/jsx-no-comment-textnodes
3
+ title: "No comment syntax as text inside JSX"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "whether the text is a comment or content needs the author"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/jsx-no-comment-textnodes]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Comments inside JSX are written as `{/* … */}`; `// …` and `/* … */` in JSX text are not.
13
+
14
+ **Why:** In JSX, `// note` is text that ends up on the page.
15
+
16
+ ```codelaw
17
+ match:
18
+ kind: jsx_text
19
+ regex: ^\s*(//|/\*)
20
+ message: "This is rendered as text, not a comment; write `{/* … */}`"
21
+ ```
22
+
23
+ ## How to fix
24
+
25
+ 1. Wrap the comment in braces: `{/* … */}`.
26
+
27
+ ## Examples
28
+
29
+ ### JavaScript
30
+
31
+ #### Don't
32
+
33
+ ```js
34
+ const a = <div>
35
+ // the title
36
+ <h1>Hi</h1>
37
+ </div>;
38
+ ```
39
+
40
+ #### Do
41
+
42
+ ```js
43
+ const a = <div>
44
+ {/* the title */}
45
+ <h1>Hi</h1>
46
+ </div>;
47
+ ```
48
+
49
+ ### JSX
50
+
51
+ #### Don't
52
+
53
+ ```jsx
54
+ const a = <div>
55
+ // the title
56
+ <h1>Hi</h1>
57
+ </div>;
58
+ ```
59
+
60
+ #### Do
61
+
62
+ ```jsx
63
+ const a = <div>
64
+ {/* the title */}
65
+ <h1>Hi</h1>
66
+ </div>;
67
+ ```
68
+
69
+ ### TSX
70
+
71
+ #### Don't
72
+
73
+ ```tsx
74
+ const a = <div>
75
+ // the title
76
+ <h1>Hi</h1>
77
+ </div>;
78
+ ```
79
+
80
+ #### Do
81
+
82
+ ```tsx
83
+ const a = <div>
84
+ {/* the title */}
85
+ <h1>Hi</h1>
86
+ </div>;
87
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ id: react/jsx-no-duplicate-props
3
+ title: "No prop given twice on one element"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "which value should win needs the author"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/jsx-no-duplicate-props]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A JSX element gives each prop once.
13
+
14
+ **Why:** The last value silently wins, so the first is dead code or a mistake.
15
+
16
+ ```codelaw
17
+ native: react/jsx-no-duplicate-props
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Keep the value that is right and remove the other.
23
+
24
+ ## Examples
25
+
26
+ ### JavaScript
27
+
28
+ #### Don't
29
+
30
+ ```js
31
+ const a = <input type="text" type="email" />;
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```js
37
+ const a = <input type="email" />;
38
+ ```
39
+
40
+ ### JSX
41
+
42
+ #### Don't
43
+
44
+ ```jsx
45
+ const a = <input type="text" type="email" />;
46
+ ```
47
+
48
+ #### Do
49
+
50
+ ```jsx
51
+ const a = <input type="email" />;
52
+ ```
53
+
54
+ ### TSX
55
+
56
+ #### Don't
57
+
58
+ ```tsx
59
+ const a = <input type="text" type="email" />;
60
+ ```
61
+
62
+ #### Do
63
+
64
+ ```tsx
65
+ const a = <input type="email" />;
66
+ ```
@@ -0,0 +1,78 @@
1
+ ---
2
+ id: react/jsx-no-target-blank
3
+ title: "Links that open a new tab set rel=\"noreferrer\""
4
+ languages: [javascript, jsx, tsx]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [eslint-plugin-react/jsx-no-target-blank]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ An `<a target="_blank">` with an `href` also has `rel="noreferrer"`.
12
+
13
+ **Why:** Without it, older browsers give the opened page a handle on this one (`window.opener`), and
14
+ the referrer leaks.
15
+
16
+ ```codelaw
17
+ match:
18
+ kind: jsx_attribute
19
+ regex: ^target=["']_blank["']$
20
+ inside:
21
+ stopBy: neighbor
22
+ any: [{ kind: jsx_opening_element }, { kind: jsx_self_closing_element }]
23
+ all:
24
+ - has: { field: name, regex: ^a$ }
25
+ - has: { stopBy: neighbor, kind: jsx_attribute, regex: ^href= }
26
+ - not: { has: { stopBy: neighbor, kind: jsx_attribute, regex: ^rel= } }
27
+ message: "A link with `target=\"_blank\"` gives the new page access to this one; add `rel=\"noreferrer\"`"
28
+ fix:
29
+ - insert-after: ' rel="noreferrer"'
30
+ ```
31
+
32
+ ## Examples
33
+
34
+ ### JavaScript
35
+
36
+ #### Don't
37
+
38
+ ```js
39
+ const a = <a href={url} target="_blank">Docs</a>;
40
+ ```
41
+
42
+ #### Do
43
+
44
+ ```js
45
+ const a = <a href={url} target="_blank" rel="noreferrer">Docs</a>;
46
+ ```
47
+
48
+ ### JSX
49
+
50
+ #### Don't
51
+
52
+ ```jsx
53
+ const a = <a href={url} target="_blank">Docs</a>;
54
+ ```
55
+
56
+ #### Do
57
+
58
+ ```jsx
59
+ const a = <a href={url} target="_blank" rel="noreferrer">Docs</a>;
60
+ ```
61
+
62
+ ### TSX
63
+
64
+ #### Don't
65
+
66
+ ```tsx
67
+ const a = <a href={url} target="_blank">Docs</a>;
68
+ ```
69
+
70
+ #### Do
71
+
72
+ ```tsx
73
+ const a = <a href={url} target="_blank" rel="noreferrer">Docs</a>;
74
+ ```
75
+
76
+ ## Differences from the source
77
+
78
+ A link that already has a `rel` is not checked.
@@ -0,0 +1,78 @@
1
+ ---
2
+ id: react/jsx-no-undef
3
+ title: "Components used in JSX are declared"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "the right import is the author's to choose"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/jsx-no-undef]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Every component used as a JSX tag is imported or declared.
13
+
14
+ **Why:** An undeclared component throws a ReferenceError when it renders.
15
+
16
+ ```codelaw
17
+ match:
18
+ kind: identifier
19
+ regex: ^[A-Z]
20
+ inside:
21
+ stopBy: neighbor
22
+ any: [{ kind: jsx_opening_element }, { kind: jsx_self_closing_element }]
23
+ field: name
24
+ where:
25
+ $$MATCH: undeclared
26
+ message: "`$$MATCH` is not defined; import or declare the component"
27
+ ```
28
+
29
+ ## How to fix
30
+
31
+ 1. Import the component, or declare it, or correct the name.
32
+
33
+ ## Examples
34
+
35
+ ### JavaScript
36
+
37
+ #### Don't
38
+
39
+ ```js
40
+ export const App = () => <Header />;
41
+ ```
42
+
43
+ #### Do
44
+
45
+ ```js
46
+ import { Header } from "./header";
47
+ export const App = () => <Header />;
48
+ ```
49
+
50
+ ### JSX
51
+
52
+ #### Don't
53
+
54
+ ```jsx
55
+ export const App = () => <Header />;
56
+ ```
57
+
58
+ #### Do
59
+
60
+ ```jsx
61
+ import { Header } from "./header";
62
+ export const App = () => <Header />;
63
+ ```
64
+
65
+ ### TSX
66
+
67
+ #### Don't
68
+
69
+ ```tsx
70
+ export const App = () => <Header />;
71
+ ```
72
+
73
+ #### Do
74
+
75
+ ```tsx
76
+ import { Header } from "./header";
77
+ export const App = () => <Header />;
78
+ ```
@@ -0,0 +1,80 @@
1
+ ---
2
+ id: react/no-children-prop
3
+ title: "Children are passed as children, not as a prop"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "moving the children changes the element's shape by hand"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/no-children-prop]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Children go between an element's tags (or as the third argument of `createElement`), not in a
13
+ `children` prop.
14
+
15
+ **Why:** Passing `children` as a prop works, but hides the element's content where readers don't
16
+ look for it.
17
+
18
+ ```codelaw
19
+ match:
20
+ any:
21
+ - kind: jsx_attribute
22
+ regex: ^children\b
23
+ - pattern: 'createElement($T, { children: $C, $$$REST })'
24
+ - pattern: 'React.createElement($T, { children: $C, $$$REST })'
25
+ message: "Pass children between the tags, not as the `children` prop"
26
+ ```
27
+
28
+ ## How to fix
29
+
30
+ 1. Move the value between the opening and closing tags.
31
+
32
+ ## Examples
33
+
34
+ ### JavaScript
35
+
36
+ #### Don't
37
+
38
+ ```js
39
+ const a = <Panel children={<p>Hi</p>} />;
40
+ ```
41
+
42
+ #### Do
43
+
44
+ ```js
45
+ const a = <Panel>
46
+ <p>Hi</p>
47
+ </Panel>;
48
+ ```
49
+
50
+ ### JSX
51
+
52
+ #### Don't
53
+
54
+ ```jsx
55
+ const a = <Panel children={<p>Hi</p>} />;
56
+ ```
57
+
58
+ #### Do
59
+
60
+ ```jsx
61
+ const a = <Panel>
62
+ <p>Hi</p>
63
+ </Panel>;
64
+ ```
65
+
66
+ ### TSX
67
+
68
+ #### Don't
69
+
70
+ ```tsx
71
+ const a = <Panel children={<p>Hi</p>} />;
72
+ ```
73
+
74
+ #### Do
75
+
76
+ ```tsx
77
+ const a = <Panel>
78
+ <p>Hi</p>
79
+ </Panel>;
80
+ ```
@@ -0,0 +1,83 @@
1
+ ---
2
+ id: react/no-danger-with-children
3
+ title: "No dangerouslySetInnerHTML together with children"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "which content the element should have needs the author"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/no-danger-with-children]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ An element with `dangerouslySetInnerHTML` has no children.
13
+
14
+ **Why:** React throws when an element has both, since it can't know which content wins.
15
+
16
+ ```codelaw
17
+ match:
18
+ any:
19
+ - kind: jsx_element
20
+ all:
21
+ - has: { field: open_tag, has: { stopBy: neighbor, kind: jsx_attribute, regex: ^dangerouslySetInnerHTML } }
22
+ - has:
23
+ stopBy: neighbor
24
+ any:
25
+ - kind: jsx_element
26
+ - kind: jsx_self_closing_element
27
+ - kind: jsx_expression
28
+ - kind: jsx_text
29
+ regex: \S
30
+ - kind: jsx_self_closing_element
31
+ all:
32
+ - has: { stopBy: neighbor, kind: jsx_attribute, regex: ^dangerouslySetInnerHTML }
33
+ - has: { stopBy: neighbor, kind: jsx_attribute, regex: ^children\b }
34
+ message: "An element can't have both `dangerouslySetInnerHTML` and children"
35
+ ```
36
+
37
+ ## How to fix
38
+
39
+ 1. Keep either the HTML or the children, not both.
40
+
41
+ ## Examples
42
+
43
+ ### JavaScript
44
+
45
+ #### Don't
46
+
47
+ ```js
48
+ const a = <div dangerouslySetInnerHTML={{ __html: html }}>Fallback</div>;
49
+ ```
50
+
51
+ #### Do
52
+
53
+ ```js
54
+ const a = <div dangerouslySetInnerHTML={{ __html: html }} />;
55
+ ```
56
+
57
+ ### JSX
58
+
59
+ #### Don't
60
+
61
+ ```jsx
62
+ const a = <div dangerouslySetInnerHTML={{ __html: html }}>Fallback</div>;
63
+ ```
64
+
65
+ #### Do
66
+
67
+ ```jsx
68
+ const a = <div dangerouslySetInnerHTML={{ __html: html }} />;
69
+ ```
70
+
71
+ ### TSX
72
+
73
+ #### Don't
74
+
75
+ ```tsx
76
+ const a = <div dangerouslySetInnerHTML={{ __html: html }}>Fallback</div>;
77
+ ```
78
+
79
+ #### Do
80
+
81
+ ```tsx
82
+ const a = <div dangerouslySetInnerHTML={{ __html: html }} />;
83
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ id: react/no-deprecated
3
+ title: "No deprecated React APIs"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "each deprecated API has its own replacement"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/no-deprecated]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Code doesn't use React APIs that are deprecated: the legacy `componentWill…` lifecycles,
13
+ `React.createClass`, `React.PropTypes`, `ReactDOM.render`, `hydrate` and `unmountComponentAtNode`.
14
+
15
+ **Why:** Deprecated APIs are removed in later versions, and the legacy lifecycles misbehave with
16
+ concurrent rendering.
17
+
18
+ ```codelaw
19
+ match:
20
+ any:
21
+ - kind: method_definition
22
+ has: { field: name, regex: ^(componentWillMount|componentWillReceiveProps|componentWillUpdate)$ }
23
+ - pattern: React.createClass($$$A)
24
+ - pattern: React.PropTypes
25
+ - pattern: ReactDOM.render($$$A)
26
+ - pattern: ReactDOM.hydrate($$$A)
27
+ - pattern: ReactDOM.unmountComponentAtNode($$$A)
28
+ - pattern: ReactDOMServer.renderToNodeStream($$$A)
29
+ message: "This React API is deprecated and is removed in a later version"
30
+ ```
31
+
32
+ ## How to fix
33
+
34
+ 1. Replace `ReactDOM.render` with `createRoot(el).render(…)` and `hydrate` with `hydrateRoot`.
35
+ 2. Rename legacy lifecycles to their `UNSAFE_` names, or move their work to `componentDidMount`/`componentDidUpdate`.
36
+ 3. Use the `prop-types` package instead of `React.PropTypes`.
37
+
38
+ ## Examples
39
+
40
+ ### JavaScript
41
+
42
+ #### Don't
43
+
44
+ ```js
45
+ ReactDOM.render(<App />, root);
46
+ ```
47
+
48
+ #### Do
49
+
50
+ ```js
51
+ createRoot(root).render(<App />);
52
+ ```
53
+
54
+ ### JSX
55
+
56
+ #### Don't
57
+
58
+ ```jsx
59
+ ReactDOM.render(<App />, root);
60
+ ```
61
+
62
+ #### Do
63
+
64
+ ```jsx
65
+ createRoot(root).render(<App />);
66
+ ```
67
+
68
+ ### TSX
69
+
70
+ #### Don't
71
+
72
+ ```tsx
73
+ ReactDOM.render(<App />, root);
74
+ ```
75
+
76
+ #### Do
77
+
78
+ ```tsx
79
+ createRoot(root).render(<App />);
80
+ ```
81
+
82
+ ## Differences from the source
83
+
84
+ The React version setting is not read: every listed API is reported.
@@ -0,0 +1,102 @@
1
+ ---
2
+ id: react/no-direct-mutation-state
3
+ title: "No assigning to this.state outside the constructor"
4
+ languages: [javascript, jsx, tsx]
5
+ fix: manual
6
+ fix_reason: "the update has to be rewritten as a `setState` call"
7
+ stability: preview
8
+ ported_from: [eslint-plugin-react/no-direct-mutation-state]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A class component changes its state with `this.setState`, never by assigning to `this.state` outside
13
+ the constructor.
14
+
15
+ **Why:** A direct assignment doesn't trigger a re-render, and a later `setState` may overwrite it.
16
+
17
+ ```codelaw
18
+ match:
19
+ kind: assignment_expression
20
+ has:
21
+ field: left
22
+ regex: ^this\.state\b
23
+ not:
24
+ inside:
25
+ kind: method_definition
26
+ has: { field: name, regex: ^constructor$ }
27
+ stopBy: end
28
+ inside: { kind: class_body, stopBy: end }
29
+ message: "Assigning to `this.state` doesn't re-render; call `this.setState`"
30
+ ```
31
+
32
+ ## How to fix
33
+
34
+ 1. Replace the assignment with `this.setState(…)`; use the updater form when the new state depends on the old.
35
+
36
+ ## Examples
37
+
38
+ ### JavaScript
39
+
40
+ #### Don't
41
+
42
+ ```js
43
+ class Counter extends React.Component {
44
+ inc() {
45
+ this.state.count = this.state.count + 1;
46
+ }
47
+ }
48
+ ```
49
+
50
+ #### Do
51
+
52
+ ```js
53
+ class Counter extends React.Component {
54
+ inc() {
55
+ this.setState((s) => ({ count: s.count + 1 }));
56
+ }
57
+ }
58
+ ```
59
+
60
+ ### JSX
61
+
62
+ #### Don't
63
+
64
+ ```jsx
65
+ class Counter extends React.Component {
66
+ inc() {
67
+ this.state.count = this.state.count + 1;
68
+ }
69
+ }
70
+ ```
71
+
72
+ #### Do
73
+
74
+ ```jsx
75
+ class Counter extends React.Component {
76
+ inc() {
77
+ this.setState((s) => ({ count: s.count + 1 }));
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### TSX
83
+
84
+ #### Don't
85
+
86
+ ```tsx
87
+ class Counter extends React.Component {
88
+ inc() {
89
+ this.state.count = this.state.count + 1;
90
+ }
91
+ }
92
+ ```
93
+
94
+ #### Do
95
+
96
+ ```tsx
97
+ class Counter extends React.Component {
98
+ inc() {
99
+ this.setState((s) => ({ count: s.count + 1 }));
100
+ }
101
+ }
102
+ ```