feat(linter): eslint-plugin-react/no-find-dom-node (#1031)

This PR implements the
[eslint-plugin-react/no-find-dom-node](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md)
rule for oxlint.
This commit is contained in:
Hao Cheng 2023-10-24 03:52:27 +02:00 committed by GitHub
parent 2483e5cbf1
commit a2e40ef623
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 247 additions and 0 deletions

View file

@ -131,6 +131,7 @@ mod react {
pub mod jsx_no_useless_fragment;
pub mod no_children_prop;
pub mod no_dangerously_set_inner_html;
pub mod no_find_dom_node;
}
mod unicorn {
@ -260,6 +261,7 @@ oxc_macros::declare_all_lint_rules! {
react::jsx_no_useless_fragment,
react::no_children_prop,
react::no_dangerously_set_inner_html,
react::no_find_dom_node,
import::named,
import::no_cycle,
import::no_self_import,

View file

@ -0,0 +1,194 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.")]
#[diagnostic(severity(warning), help("Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives."))]
struct NoFindDomNodeDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoFindDomNode;
declare_oxc_lint!(
/// ### What it does
/// This rule disallows the use of `findDOMNode`.
///
/// ### Why is this bad?
/// `findDOMNode` is an escape hatch used to access the underlying DOM node.
/// In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.
/// [It has been deprecated in `StrictMode`.](https://legacy.reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage)
///
/// ### Example
/// ```javascript
/// class MyComponent extends Component {
/// componentDidMount() {
/// findDOMNode(this).scrollIntoView();
/// }
/// render() {
/// return <div />;
/// }
/// }
/// ```
NoFindDomNode,
correctness
);
impl Rule for NoFindDomNode {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::CallExpression(call_expr) = node.kind() else { return };
if let Some(ident) = call_expr.callee.get_identifier_reference() {
if ident.name == "findDOMNode" {
ctx.diagnostic(NoFindDomNodeDiagnostic(ident.span));
}
return;
}
let Some(member_expr) = call_expr.callee.get_member_expr() else { return };
let member = member_expr.object();
if !member.is_specific_id("React")
&& !member.is_specific_id("ReactDOM")
&& !member.is_specific_id("ReactDom")
{
return;
}
let Some((span, "findDOMNode")) = member_expr.static_property_info() else { return };
ctx.diagnostic(NoFindDomNodeDiagnostic(span));
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
("var Hello = function() {};", None),
(
r#"
var Hello = createReactClass({
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
someNonMemberFunction(arg);
this.someFunc = React.findDOMNode;
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
React.someFunc(this);
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
SomeModule.findDOMNode(this).scrollIntoView();
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
];
let fail = vec![
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
React.findDOMNode(this).scrollIntoView();
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
ReactDOM.findDOMNode(this).scrollIntoView();
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
componentDidMount: function() {
ReactDom.findDOMNode(this).scrollIntoView();
},
render: function() {
return <div>Hello</div>;
}
});
"#,
None,
),
(
r#"
class Hello extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView();
}
render() {
return <div>Hello</div>;
}
}
"#,
None,
),
(
r#"
class Hello extends Component {
componentDidMount() {
this.node = findDOMNode(this);
}
render() {
return <div>Hello</div>;
}
}
"#,
None,
),
];
Tester::new(NoFindDomNode::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,51 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 105
expression: no_find_dom_node
---
⚠ eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.
╭─[no_find_dom_node.tsx:3:1]
3 │ componentDidMount: function() {
4 │ React.findDOMNode(this).scrollIntoView();
· ───────────
5 │ },
╰────
help: Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives.
⚠ eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.
╭─[no_find_dom_node.tsx:3:1]
3 │ componentDidMount: function() {
4 │ ReactDOM.findDOMNode(this).scrollIntoView();
· ───────────
5 │ },
╰────
help: Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives.
⚠ eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.
╭─[no_find_dom_node.tsx:3:1]
3 │ componentDidMount: function() {
4 │ ReactDom.findDOMNode(this).scrollIntoView();
· ───────────
5 │ },
╰────
help: Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives.
⚠ eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.
╭─[no_find_dom_node.tsx:3:1]
3 │ componentDidMount() {
4 │ findDOMNode(this).scrollIntoView();
· ───────────
5 │ }
╰────
help: Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives.
⚠ eslint-plugin-react(no-find-dom-node): Unexpected call to `findDOMNode`.
╭─[no_find_dom_node.tsx:3:1]
3 │ componentDidMount() {
4 │ this.node = findDOMNode(this);
· ───────────
5 │ }
╰────
help: Replace `findDOMNode` with one of the alternatives documented at https://react.dev/reference/react-dom/findDOMNode#alternatives.