Skip to content

Commit ac08de8

Browse files
committed
fix(linter/react_perf): allow new objects, array, fns, etc in top scope (#4395)
Consider the following code: ```tsx import { FC } from 'react' import { SvgIcon } from '@mui/material' const StyledIcon = <SvgIcon sx={{ padding: 1, color: '#ff0000' }} /> // reported violation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ export const MyComponent: FC = () => { return ( <div> {StyledIcon} {/* ... */} </div> ) } ``` This should not be a violation since the JSX is pre-computed and re-used, which does not break React's `Object.is()` checks.
1 parent 00c7b7d commit ac08de8

9 files changed

+147
-104
lines changed

crates/oxc_linter/src/rules/react_perf/jsx_no_jsx_as_prop.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ declare_oxc_lint!(
3838

3939
impl Rule for JsxNoJsxAsProp {
4040
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
41+
if node.scope_id() == ctx.scopes().root_scope_id() {
42+
return;
43+
}
4144
if let AstKind::JSXElement(jsx_elem) = node.kind() {
4245
check_jsx_element(jsx_elem, ctx);
4346
}
@@ -81,14 +84,21 @@ fn check_expression(expr: &Expression) -> Option<Span> {
8184
fn test() {
8285
use crate::tester::Tester;
8386

84-
let pass = vec![r"<Item callback={this.props.jsx} />"];
85-
86-
let fail = vec![
87+
let pass = vec![
88+
r"<Item callback={this.props.jsx} />",
89+
r"const Foo = () => <Item callback={this.props.jsx} />",
8790
r"<Item jsx={<SubItem />} />",
8891
r"<Item jsx={this.props.jsx || <SubItem />} />",
8992
r"<Item jsx={this.props.jsx ? this.props.jsx : <SubItem />} />",
9093
r"<Item jsx={this.props.jsx || (this.props.component ? this.props.component : <SubItem />)} />",
9194
];
9295

96+
let fail = vec![
97+
r"const Foo = () => (<Item jsx={<SubItem />} />)",
98+
r"const Foo = () => (<Item jsx={this.props.jsx || <SubItem />} />)",
99+
r"const Foo = () => (<Item jsx={this.props.jsx ? this.props.jsx : <SubItem />} />)",
100+
r"const Foo = () => (<Item jsx={this.props.jsx || (this.props.component ? this.props.component : <SubItem />)} />)",
101+
];
102+
93103
Tester::new(JsxNoJsxAsProp::NAME, pass, fail).with_react_perf_plugin(true).test_and_snapshot();
94104
}

crates/oxc_linter/src/rules/react_perf/jsx_no_new_array_as_prop.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ declare_oxc_lint!(
4646

4747
impl Rule for JsxNoNewArrayAsProp {
4848
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
49+
if node.scope_id() == ctx.scopes().root_scope_id() {
50+
return;
51+
}
4952
if let AstKind::JSXElement(jsx_elem) = node.kind() {
5053
check_jsx_element(jsx_elem, ctx);
5154
}
@@ -103,9 +106,9 @@ fn check_expression(expr: &Expression) -> Option<Span> {
103106
fn test() {
104107
use crate::tester::Tester;
105108

106-
let pass = vec![r"<Item list={this.props.list} />"];
107-
108-
let fail = vec![
109+
let pass = vec![
110+
r"<Item list={this.props.list} />",
111+
r"const Foo = () => <Item list={this.props.list} />",
109112
r"<Item list={[]} />",
110113
r"<Item list={new Array()} />",
111114
r"<Item list={Array()} />",
@@ -114,6 +117,15 @@ fn test() {
114117
r"<Item list={this.props.list || (this.props.arr ? this.props.arr : [])} />",
115118
];
116119

120+
let fail = vec![
121+
r"const Foo = () => (<Item list={[]} />)",
122+
r"const Foo = () => (<Item list={new Array()} />)",
123+
r"const Foo = () => (<Item list={Array()} />)",
124+
r"const Foo = () => (<Item list={this.props.list || []} />)",
125+
r"const Foo = () => (<Item list={this.props.list ? this.props.list : []} />)",
126+
r"const Foo = () => (<Item list={this.props.list || (this.props.arr ? this.props.arr : [])} />)",
127+
];
128+
117129
Tester::new(JsxNoNewArrayAsProp::NAME, pass, fail)
118130
.with_react_perf_plugin(true)
119131
.test_and_snapshot();

crates/oxc_linter/src/rules/react_perf/jsx_no_new_function_as_prop.rs

+22-10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ declare_oxc_lint!(
4141

4242
impl Rule for JsxNoNewFunctionAsProp {
4343
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
44+
if node.scope_id() == ctx.scopes().root_scope_id() {
45+
return;
46+
}
4447
if let AstKind::JSXElement(jsx_elem) = node.kind() {
4548
check_jsx_element(jsx_elem, ctx);
4649
}
@@ -106,22 +109,19 @@ fn test() {
106109
use crate::tester::Tester;
107110

108111
let pass = vec![
109-
r"<Item callback={this.props.callback} />",
110-
r"<Item promise={new Promise()} />",
111-
r"<Item onClick={bind(foo)} />",
112-
r"<Item prop={0} />",
113-
r"var a;<Item prop={a} />",
114-
r"var a;a = 1;<Item prop={a} />",
115-
r"var a;<Item prop={a} />",
112+
r"const Foo = () => <Item callback={this.props.callback} />",
113+
r"const Foo = () => (<Item promise={new Promise()} />)",
114+
r"const Foo = () => (<Item onClick={bind(foo)} />)",
115+
r"const Foo = () => (<Item prop={0} />)",
116+
r"const Foo = () => { var a; return <Item prop={a} /> }",
117+
r"const Foo = () => { var a;a = 1; return <Item prop={a} /> }",
118+
r"const Foo = () => { var a;<Item prop={a} /> }",
116119
r"function foo ({prop1 = function(){}, prop2}) {
117120
return <Comp prop={prop2} />
118121
}",
119122
r"function foo ({prop1, prop2 = function(){}}) {
120123
return <Comp prop={prop1} />
121124
}",
122-
];
123-
124-
let fail = vec![
125125
r"<Item prop={function(){return true}} />",
126126
r"<Item prop={() => true} />",
127127
r"<Item prop={new Function('a', 'alert(a)')}/>",
@@ -133,6 +133,18 @@ fn test() {
133133
r"<Item prop={this.props.callback || (this.props.cb ? this.props.cb : function(){})} />",
134134
];
135135

136+
let fail = vec![
137+
r"const Foo = () => (<Item prop={function(){return true}} />)",
138+
r"const Foo = () => (<Item prop={() => true} />)",
139+
r"const Foo = () => (<Item prop={new Function('a', 'alert(a)')}/>)",
140+
r"const Foo = () => (<Item prop={Function()}/>)",
141+
r"const Foo = () => (<Item onClick={this.clickHandler.bind(this)} />)",
142+
r"const Foo = () => (<Item callback={this.props.callback || function() {}} />)",
143+
r"const Foo = () => (<Item callback={this.props.callback ? this.props.callback : function() {}} />)",
144+
r"const Foo = () => (<Item prop={this.props.callback || this.props.callback ? this.props.callback : function(){}} />)",
145+
r"const Foo = () => (<Item prop={this.props.callback || (this.props.cb ? this.props.cb : function(){})} />)",
146+
];
147+
136148
Tester::new(JsxNoNewFunctionAsProp::NAME, pass, fail)
137149
.with_react_perf_plugin(true)
138150
.test_and_snapshot();

crates/oxc_linter/src/rules/react_perf/jsx_no_new_object_as_prop.rs

+15-8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ declare_oxc_lint!(
4545

4646
impl Rule for JsxNoNewObjectAsProp {
4747
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
48+
if node.scope_id() == ctx.scopes().root_scope_id() {
49+
return;
50+
}
4851
if let AstKind::JSXElement(jsx_elem) = node.kind() {
4952
check_jsx_element(jsx_elem, ctx);
5053
}
@@ -102,16 +105,20 @@ fn check_expression(expr: &Expression) -> Option<Span> {
102105
fn test() {
103106
use crate::tester::Tester;
104107

105-
let pass = vec![r"<Item config={staticConfig} />"];
108+
let pass = vec![
109+
r"<Item config={staticConfig} />",
110+
r"<Item config={{}} />",
111+
r"const Foo = () => <Item config={staticConfig} />",
112+
];
106113

107114
let fail = vec![
108-
r"<Item config={{}} />",
109-
r"<Item config={new Object()} />",
110-
r"<Item config={Object()} />",
111-
r"<div style={{display: 'none'}} />",
112-
r"<Item config={this.props.config || {}} />",
113-
r"<Item config={this.props.config ? this.props.config : {}} />",
114-
r"<Item config={this.props.config || (this.props.default ? this.props.default : {})} />",
115+
r"const Foo = () => <Item config={{}} />",
116+
r"const Foo = () => (<Item config={new Object()} />)",
117+
r"const Foo = () => (<Item config={Object()} />)",
118+
r"const Foo = () => (<div style={{display: 'none'}} />)",
119+
r"const Foo = () => (<Item config={this.props.config || {}} />)",
120+
r"const Foo = () => (<Item config={this.props.config ? this.props.config : {}} />)",
121+
r"const Foo = () => (<Item config={this.props.config || (this.props.default ? this.props.default : {})} />)",
115122
];
116123

117124
Tester::new(JsxNoNewObjectAsProp::NAME, pass, fail)

crates/oxc_linter/src/snapshots/jsx_no_jsx_as_prop.snap

+12-12
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
source: crates/oxc_linter/src/tester.rs
33
---
44
eslint-plugin-react-perf(jsx-no-jsx-as-prop): JSX attribute values should not contain other JSX.
5-
╭─[jsx_no_jsx_as_prop.tsx:1:12]
6-
1<Item jsx={<SubItem />} />
7-
· ───────────
5+
╭─[jsx_no_jsx_as_prop.tsx:1:31]
6+
1const Foo = () => (<Item jsx={<SubItem />} />)
7+
· ───────────
88
╰────
99
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
1010

1111
eslint-plugin-react-perf(jsx-no-jsx-as-prop): JSX attribute values should not contain other JSX.
12-
╭─[jsx_no_jsx_as_prop.tsx:1:30]
13-
1<Item jsx={this.props.jsx || <SubItem />} />
14-
· ───────────
12+
╭─[jsx_no_jsx_as_prop.tsx:1:49]
13+
1const Foo = () => (<Item jsx={this.props.jsx || <SubItem />} />)
14+
· ───────────
1515
╰────
1616
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
1717

1818
eslint-plugin-react-perf(jsx-no-jsx-as-prop): JSX attribute values should not contain other JSX.
19-
╭─[jsx_no_jsx_as_prop.tsx:1:46]
20-
1<Item jsx={this.props.jsx ? this.props.jsx : <SubItem />} />
21-
· ───────────
19+
╭─[jsx_no_jsx_as_prop.tsx:1:65]
20+
1const Foo = () => (<Item jsx={this.props.jsx ? this.props.jsx : <SubItem />} />)
21+
· ───────────
2222
╰────
2323
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
2424

2525
eslint-plugin-react-perf(jsx-no-jsx-as-prop): JSX attribute values should not contain other JSX.
26-
╭─[jsx_no_jsx_as_prop.tsx:1:77]
27-
1<Item jsx={this.props.jsx || (this.props.component ? this.props.component : <SubItem />)} />
28-
· ───────────
26+
╭─[jsx_no_jsx_as_prop.tsx:1:96]
27+
1const Foo = () => (<Item jsx={this.props.jsx || (this.props.component ? this.props.component : <SubItem />)} />)
28+
· ───────────
2929
╰────
3030
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).

crates/oxc_linter/src/snapshots/jsx_no_new_array_as_prop.snap

+18-18
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,43 @@
22
source: crates/oxc_linter/src/tester.rs
33
---
44
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
5-
╭─[jsx_no_new_array_as_prop.tsx:1:13]
6-
1<Item list={[]} />
7-
· ──
5+
╭─[jsx_no_new_array_as_prop.tsx:1:32]
6+
1const Foo = () => (<Item list={[]} />)
7+
· ──
88
╰────
99
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
1010

1111
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
12-
╭─[jsx_no_new_array_as_prop.tsx:1:13]
13-
1<Item list={new Array()} />
14-
· ───────────
12+
╭─[jsx_no_new_array_as_prop.tsx:1:32]
13+
1const Foo = () => (<Item list={new Array()} />)
14+
· ───────────
1515
╰────
1616
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
1717

1818
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
19-
╭─[jsx_no_new_array_as_prop.tsx:1:13]
20-
1<Item list={Array()} />
21-
· ───────
19+
╭─[jsx_no_new_array_as_prop.tsx:1:32]
20+
1const Foo = () => (<Item list={Array()} />)
21+
· ───────
2222
╰────
2323
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
2424

2525
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
26-
╭─[jsx_no_new_array_as_prop.tsx:1:32]
27-
1<Item list={this.props.list || []} />
28-
· ──
26+
╭─[jsx_no_new_array_as_prop.tsx:1:51]
27+
1const Foo = () => (<Item list={this.props.list || []} />)
28+
· ──
2929
╰────
3030
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
3131

3232
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
33-
╭─[jsx_no_new_array_as_prop.tsx:1:49]
34-
1<Item list={this.props.list ? this.props.list : []} />
35-
· ──
33+
╭─[jsx_no_new_array_as_prop.tsx:1:68]
34+
1const Foo = () => (<Item list={this.props.list ? this.props.list : []} />)
35+
· ──
3636
╰────
3737
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).
3838

3939
eslint-plugin-react-perf(jsx-no-new-array-as-prop): JSX attribute values should not contain Arrays created in the same scope.
40-
╭─[jsx_no_new_array_as_prop.tsx:1:67]
41-
1<Item list={this.props.list || (this.props.arr ? this.props.arr : [])} />
42-
· ──
40+
╭─[jsx_no_new_array_as_prop.tsx:1:86]
41+
1const Foo = () => (<Item list={this.props.list || (this.props.arr ? this.props.arr : [])} />)
42+
· ──
4343
╰────
4444
help: simplify props or memoize props in the parent component (https://react.dev/reference/react/memo#my-component-rerenders-when-a-prop-is-an-object-or-array).

0 commit comments

Comments
 (0)