feat(linter): support react/no-render-return-value (#1042)

refer to
https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md
This commit is contained in:
Dunqing 2023-10-24 04:34:20 -05:00 committed by GitHub
parent af1a76bafa
commit d8f07ca71d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 0 deletions

View file

@ -132,6 +132,7 @@ mod react {
pub mod no_children_prop;
pub mod no_dangerously_set_inner_html;
pub mod no_find_dom_node;
pub mod no_render_return_value;
pub mod no_unescaped_entities;
}
@ -264,6 +265,7 @@ oxc_macros::declare_all_lint_rules! {
react::no_children_prop,
react::no_dangerously_set_inner_html,
react::no_find_dom_node,
react::no_render_return_value,
import::named,
import::no_cycle,
import::no_self_import,

View file

@ -0,0 +1,135 @@
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ScopeFlags;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.")]
#[diagnostic(severity(warning))]
struct NoRenderReturnValueDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoRenderReturnValue;
declare_oxc_lint!(
/// ### What it does
///
/// This rule will warn you if you try to use the ReactDOM.render() return value.
///
/// ### Example
/// ```javascript
/// // Bad
/// vaa inst =ReactDOM.render(<App />, document.body);
/// function render() {
/// return ReactDOM.render(<App />, document.body);
/// }
///
/// // Good
/// ReactDOM.render(<App />, document.body);
/// ```
NoRenderReturnValue,
correctness
);
impl Rule for NoRenderReturnValue {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::CallExpression(call_expr) = node.kind() else { return };
let Expression::MemberExpression(member_expr) = &call_expr.callee else { return };
let Expression::Identifier(ident) = member_expr.object() else { return };
if ident.name == "ReactDOM" {
if let Some((property_span, property_name)) = member_expr.static_property_info() {
if property_name == "render" {
if let Some(parent_node) = ctx.nodes().parent_node(node.id()) {
if matches!(
parent_node.kind(),
AstKind::VariableDeclarator(_)
| AstKind::ObjectProperty(_)
| AstKind::ReturnStatement(_)
| AstKind::AssignmentExpression(_)
) {
ctx.diagnostic(NoRenderReturnValueDiagnostic(
ident.span.merge(&property_span),
));
}
let is_arrow_function = ctx
.scopes()
.get_flags(parent_node.scope_id())
.contains(ScopeFlags::Arrow);
if is_arrow_function {
ctx.nodes().ancestors(parent_node.id()).skip(1).find(|node_id| {
let parent_node = ctx.nodes().get_node(*node_id);
matches!(parent_node.kind(), AstKind::ArrowExpression(_))
.then(|| {
ctx.diagnostic(NoRenderReturnValueDiagnostic(
ident.span.merge(&property_span),
));
})
.is_some()
});
}
}
}
}
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
("ReactDOM.render(<div />, document.body);", None),
(
"
let node;
ReactDOM.render(<div ref={ref => node = ref}/>, document.body);
",
None,
),
("ReactDOM.render(<div ref={ref => this.node = ref}/>, document.body);", None),
("React.render(<div ref={ref => this.node = ref}/>, document.body);", None),
("React.render(<div ref={ref => this.node = ref}/>, document.body);", None),
("var foo = React.render(<div />, root);", None),
("var foo = render(<div />, root)", None),
("var foo = ReactDom.renderder(<div />, root)", None),
];
let fail = vec![
("var Hello = ReactDOM.render(<div />, document.body);", None),
(
"
var o = {
inst: ReactDOM.render(<div />, document.body)
};
",
None,
),
(
"
function render () {
return ReactDOM.render(<div />, document.body)
}
",
None,
),
("var render = (a, b) => ReactDOM.render(a, b)", None),
("this.o = ReactDOM.render(<div />, document.body);", None),
("var v; v = ReactDOM.render(<div />, document.body);", None),
("var inst = ReactDOM.render(<div />, document.body);", None),
// This rule is only supported for react versions >= 15.0.0, so the following are not supported.
// See https://github.com/web-infra-dev/oxc/pull/1042#discussion_r1369762147
// ("var inst = React.render(<div />, document.body);", None),
// ("var inst = React.render(<div />, document.body);", None),
];
Tester::new(NoRenderReturnValue::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,51 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_render_return_value
---
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:1:1]
1 │ var Hello = ReactDOM.render(<div />, document.body);
· ───────────────
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:2:1]
2 │ var o = {
3 │ inst: ReactDOM.render(<div />, document.body)
· ───────────────
4 │ };
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:2:1]
2 │ function render () {
3 │ return ReactDOM.render(<div />, document.body)
· ───────────────
4 │ }
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:1:1]
1 │ var render = (a, b) => ReactDOM.render(a, b)
· ───────────────
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:1:1]
1 │ this.o = ReactDOM.render(<div />, document.body);
· ───────────────
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:1:1]
1 │ var v; v = ReactDOM.render(<div />, document.body);
· ───────────────
╰────
⚠ eslint-plugin-react(no-render-return-value): Do not depend on the return value from ReactDOM.render.
╭─[no_render_return_value.tsx:1:1]
1 │ var inst = ReactDOM.render(<div />, document.body);
· ───────────────
╰────

View file

@ -27,6 +27,11 @@ impl Span {
self.end - self.start
}
#[must_use]
pub fn merge(&self, other: &Self) -> Self {
Self::new(self.start.min(other.start), self.end.max(other.end))
}
pub fn source_text<'a>(&self, source_text: &'a str) -> &'a str {
&source_text[self.start as usize..self.end as usize]
}