Skip to content

Commit 7891670

Browse files
authored
1 parent 8d9894a commit 7891670

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

crates/oxc_linter/src/rules.rs

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ mod react {
154154
pub mod no_unescaped_entities;
155155
pub mod no_unknown_property;
156156
pub mod react_in_jsx_scope;
157+
pub mod require_render_return;
157158
}
158159

159160
mod unicorn {
@@ -488,6 +489,7 @@ oxc_macros::declare_all_lint_rules! {
488489
react::no_unescaped_entities,
489490
react::no_is_mounted,
490491
react::no_unknown_property,
492+
react::require_render_return,
491493
import::default,
492494
import::named,
493495
import::no_cycle,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
use oxc_ast::{
2+
ast::{Argument, ClassElement, Expression, FunctionBody, ObjectPropertyKind},
3+
AstKind,
4+
};
5+
use oxc_diagnostics::{
6+
miette::{self, Diagnostic},
7+
thiserror::Error,
8+
};
9+
use oxc_macros::declare_oxc_lint;
10+
use oxc_span::Span;
11+
12+
use crate::{
13+
context::LintContext,
14+
rule::Rule,
15+
rules::eslint::array_callback_return::return_checker::{
16+
check_statement, StatementReturnStatus,
17+
},
18+
utils::{is_es5_component, is_es6_component},
19+
AstNode,
20+
};
21+
22+
#[derive(Debug, Error, Diagnostic)]
23+
#[error(
24+
"eslint-plugin-react(require-render-return): Your render method should have a return statement"
25+
)]
26+
#[diagnostic(severity(warning), help("When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing."))]
27+
struct RequireRenderReturnDiagnostic(#[label] pub Span);
28+
29+
#[derive(Debug, Default, Clone)]
30+
pub struct RequireRenderReturn;
31+
32+
declare_oxc_lint!(
33+
/// ### What it does
34+
/// Enforce ES5 or ES6 class for returning value in render function
35+
///
36+
/// ### Why is this bad?
37+
/// When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing.
38+
///
39+
/// ### Example
40+
/// ```javascript
41+
/// var Hello = createReactClass({
42+
/// render() {
43+
/// <div>Hello</div>;
44+
/// }
45+
/// });
46+
///
47+
/// class Hello extends React.Component {
48+
/// render() {
49+
/// <div>Hello</div>;
50+
/// }
51+
/// }
52+
/// ```
53+
RequireRenderReturn,
54+
correctness
55+
);
56+
57+
impl Rule for RequireRenderReturn {
58+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
59+
if !is_es5_component(node) && !is_es6_component(node) {
60+
return;
61+
}
62+
63+
match node.kind() {
64+
AstKind::Class(cl) => {
65+
if let Some((fn_body, is_arrow_function_expression)) =
66+
cl.body.body.iter().find_map(|ce| match ce {
67+
ClassElement::MethodDefinition(md)
68+
if md.key.is_specific_static_name("render") =>
69+
{
70+
md.value.body.as_ref().map(|v| (v, false))
71+
}
72+
ClassElement::PropertyDefinition(pd)
73+
if pd.key.is_specific_static_name("render") =>
74+
{
75+
if let Some(Expression::ArrowExpression(ref ae)) = pd.value {
76+
Some((&ae.body, ae.expression))
77+
} else {
78+
None
79+
}
80+
}
81+
_ => None,
82+
})
83+
{
84+
if is_arrow_function_expression || has_return_in_fn_body(fn_body) {
85+
return;
86+
}
87+
88+
ctx.diagnostic(RequireRenderReturnDiagnostic(fn_body.span));
89+
}
90+
}
91+
AstKind::CallExpression(ce) => {
92+
if let Some(Argument::Expression(Expression::ObjectExpression(obj_expr))) =
93+
ce.arguments.first()
94+
{
95+
if let Some(fn_body) = obj_expr
96+
.properties
97+
.iter()
98+
.filter_map(|prop| match prop {
99+
ObjectPropertyKind::ObjectProperty(prop)
100+
if prop.key.is_specific_static_name("render") =>
101+
{
102+
if let Expression::FunctionExpression(ae) = &prop.value {
103+
ae.body.as_ref()
104+
} else {
105+
None
106+
}
107+
}
108+
_ => None,
109+
})
110+
.find(|fn_body| !has_return_in_fn_body(fn_body))
111+
{
112+
ctx.diagnostic(RequireRenderReturnDiagnostic(fn_body.span));
113+
}
114+
}
115+
}
116+
_ => {}
117+
}
118+
}
119+
}
120+
121+
fn has_return_in_fn_body<'a>(fn_body: &oxc_allocator::Box<'a, FunctionBody<'a>>) -> bool {
122+
fn_body.statements.iter().any(|stmt| check_statement(stmt) != StatementReturnStatus::NotReturn)
123+
}
124+
125+
#[test]
126+
fn test() {
127+
use crate::tester::Tester;
128+
129+
let pass = vec![
130+
r"
131+
class Hello extends React.Component {
132+
render() {
133+
return <div>Hello {this.props.name}</div>;
134+
}
135+
}
136+
",
137+
r"
138+
class Hello extends React.Component {
139+
render = () => {
140+
return <div>Hello {this.props.name}</div>;
141+
}
142+
}
143+
",
144+
r"
145+
class Hello extends React.Component {
146+
render = () => (
147+
<div>Hello {this.props.name}</div>
148+
)
149+
}
150+
",
151+
r"
152+
var Hello = createReactClass({
153+
displayName: 'Hello',
154+
render: function() {
155+
return <div></div>
156+
}
157+
});
158+
",
159+
r"
160+
function Hello() {
161+
return <div></div>;
162+
}
163+
",
164+
r"
165+
var Hello = () => (
166+
<div></div>
167+
);
168+
",
169+
r"
170+
var Hello = createReactClass({
171+
render: function() {
172+
switch (this.props.name) {
173+
case 'Foo':
174+
return <div>Hello Foo</div>;
175+
default:
176+
return <div>Hello {this.props.name}</div>;
177+
}
178+
}
179+
});
180+
",
181+
r"
182+
var Hello = createReactClass({
183+
render: function() {
184+
if (this.props.name === 'Foo') {
185+
return <div>Hello Foo</div>;
186+
} else {
187+
return <div>Hello {this.props.name}</div>;
188+
}
189+
}
190+
});
191+
",
192+
r"
193+
class Hello {
194+
render() {}
195+
}
196+
",
197+
r"class Hello extends React.Component {}",
198+
r"var Hello = createReactClass({});",
199+
r"
200+
var render = require('./render');
201+
var Hello = createReactClass({
202+
render
203+
});
204+
",
205+
r"
206+
class Foo extends Component {
207+
render
208+
}
209+
",
210+
];
211+
212+
let fail = vec![
213+
r"
214+
var Hello = createReactClass({
215+
displayName: 'Hello',
216+
render: function() {}
217+
});
218+
",
219+
r"
220+
class Hello extends React.Component {
221+
render() {}
222+
}
223+
",
224+
r"
225+
class Hello extends React.Component {
226+
render() {
227+
const names = this.props.names.map(function(name) {
228+
return <div>{name}</div>
229+
});
230+
}
231+
}
232+
",
233+
r"
234+
class Hello extends React.Component {
235+
render = () => {
236+
<div>Hello {this.props.name}</div>
237+
}
238+
}
239+
",
240+
];
241+
242+
Tester::new_without_config(RequireRenderReturn::NAME, pass, fail).test_and_snapshot();
243+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
expression: require_render_return
4+
---
5+
eslint-plugin-react(require-render-return): Your render method should have a return statement
6+
╭─[require_render_return.tsx:3:1]
7+
3displayName: 'Hello',
8+
4render: function() {}
9+
· ──
10+
5 │ });
11+
╰────
12+
help: When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing.
13+
14+
eslint-plugin-react(require-render-return): Your render method should have a return statement
15+
╭─[require_render_return.tsx:2:1]
16+
2class Hello extends React.Component {
17+
3 │ render() {}
18+
· ──
19+
4 │ }
20+
╰────
21+
help: When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing.
22+
23+
eslint-plugin-react(require-render-return): Your render method should have a return statement
24+
╭─[require_render_return.tsx:2:1]
25+
2class Hello extends React.Component {
26+
3 │ ╭─▶ render() {
27+
4 │ │ const names = this.props.names.map(function(name) {
28+
5 │ │ return <div>{name}</div>
29+
6 │ │ });
30+
7 │ ╰─▶ }
31+
8 │ }
32+
╰────
33+
help: When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing.
34+
35+
eslint-plugin-react(require-render-return): Your render method should have a return statement
36+
╭─[require_render_return.tsx:2:1]
37+
2class Hello extends React.Component {
38+
3 │ ╭─▶ render = () => {
39+
4 │ │ <div>Hello {this.props.name}</div>
40+
5 │ ╰─▶ }
41+
6 │ }
42+
╰────
43+
help: When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing.
44+
45+

0 commit comments

Comments
 (0)