diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index b795e250a..69470e820 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -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,
diff --git a/crates/oxc_linter/src/rules/react/no_find_dom_node.rs b/crates/oxc_linter/src/rules/react/no_find_dom_node.rs
new file mode 100644
index 000000000..02de64c45
--- /dev/null
+++ b/crates/oxc_linter/src/rules/react/no_find_dom_node.rs
@@ -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
;
+ /// }
+ /// }
+ /// ```
+ 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 Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ someNonMemberFunction(arg);
+ this.someFunc = React.findDOMNode;
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ React.someFunc(this);
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ SomeModule.findDOMNode(this).scrollIntoView();
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ ];
+
+ let fail = vec![
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ React.findDOMNode(this).scrollIntoView();
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ ReactDOM.findDOMNode(this).scrollIntoView();
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ ReactDom.findDOMNode(this).scrollIntoView();
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ "#,
+ None,
+ ),
+ (
+ r#"
+ class Hello extends Component {
+ componentDidMount() {
+ findDOMNode(this).scrollIntoView();
+ }
+ render() {
+ return Hello
;
+ }
+ }
+ "#,
+ None,
+ ),
+ (
+ r#"
+ class Hello extends Component {
+ componentDidMount() {
+ this.node = findDOMNode(this);
+ }
+ render() {
+ return Hello
;
+ }
+ }
+ "#,
+ None,
+ ),
+ ];
+
+ Tester::new(NoFindDomNode::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_find_dom_node.snap b/crates/oxc_linter/src/snapshots/no_find_dom_node.snap
new file mode 100644
index 000000000..acd94d40d
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_find_dom_node.snap
@@ -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.
+
+