From a2510be7a0dca5dd8761845e9e38267759e4efce Mon Sep 17 00:00:00 2001
From: Ken-HH24 <62000888+Ken-HH24@users.noreply.github.com>
Date: Sun, 26 Nov 2023 19:06:08 +0800
Subject: [PATCH] feat(linter): no-is-mounted for eslint-plugin-react (#1550)
Try to implement `no-is-mounted` for #1022
---
crates/oxc_linter/src/rules.rs | 2 +
.../src/rules/react/no_is_mounted.rs | 155 ++++++++++++++++++
.../src/snapshots/no_is_mounted.snap | 32 ++++
3 files changed, 189 insertions(+)
create mode 100644 crates/oxc_linter/src/rules/react/no_is_mounted.rs
create mode 100644 crates/oxc_linter/src/snapshots/no_is_mounted.snap
diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index 873f2a7ad96d1..ca965e8d436ca 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -140,6 +140,7 @@ mod react {
pub mod no_children_prop;
pub mod no_dangerously_set_inner_html;
pub mod no_find_dom_node;
+ pub mod no_is_mounted;
pub mod no_render_return_value;
pub mod no_string_refs;
pub mod no_unescaped_entities;
@@ -387,6 +388,7 @@ oxc_macros::declare_all_lint_rules! {
react::no_render_return_value,
react::no_string_refs,
react::no_unescaped_entities,
+ react::no_is_mounted,
import::default,
import::named,
import::no_cycle,
diff --git a/crates/oxc_linter/src/rules/react/no_is_mounted.rs b/crates/oxc_linter/src/rules/react/no_is_mounted.rs
new file mode 100644
index 0000000000000..d36ab2263a766
--- /dev/null
+++ b/crates/oxc_linter/src/rules/react/no_is_mounted.rs
@@ -0,0 +1,155 @@
+use oxc_ast::{
+ ast::{CallExpression, Expression},
+ 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(no-is-mounted): Do not use isMounted")]
+#[diagnostic(severity(warning), help("isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself."))]
+struct NoIsMountedDiagnostic(#[label] pub Span);
+
+#[derive(Debug, Default, Clone)]
+pub struct NoIsMounted;
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ /// This rule prevents using isMounted in ES6 classes
+ ///
+ /// ### Why is this bad?
+ ///
+ /// isMounted is an anti-pattern, is not available when using ES6 classes,
+ /// and it is on its way to being officially deprecated.///
+ ///
+ /// ### Example
+ /// ```javascript
+ /// class Hello extends React.Component {
+ /// someMethod() {
+ /// if (!this.isMounted()) {
+ /// return;
+ /// }
+ /// }
+ /// render() {
+ /// return
Hello
;
+ /// }
+ /// };
+ /// ```
+ NoIsMounted,
+ correctness
+);
+
+impl Rule for NoIsMounted {
+ fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
+ let AstKind::CallExpression(CallExpression {
+ callee: Expression::MemberExpression(member_expr),
+ span,
+ ..
+ }) = node.kind()
+ else {
+ return;
+ };
+
+ if !matches!(member_expr.object(), Expression::ThisExpression(_))
+ || !member_expr.static_property_name().is_some_and(|str| str == "isMounted")
+ {
+ return;
+ }
+
+ for ancestor in ctx.nodes().ancestors(node.id()).skip(1) {
+ if matches!(
+ ctx.nodes().kind(ancestor),
+ AstKind::ObjectProperty(_) | AstKind::MethodDefinition(_)
+ ) {
+ ctx.diagnostic(NoIsMountedDiagnostic(*span));
+ break;
+ }
+ }
+ }
+}
+
+#[test]
+fn test() {
+ use crate::tester::Tester;
+
+ let pass = vec![
+ (
+ "
+ var Hello = function() {
+ };
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ componentDidUpdate: function() {
+ someNonMemberFunction(arg);
+ this.someFunc = this.isMounted;
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ ",
+ None,
+ ),
+ ];
+
+ let fail = vec![
+ (
+ "
+ var Hello = createReactClass({
+ componentDidUpdate: function() {
+ if (!this.isMounted()) {
+ return;
+ }
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ someMethod: function() {
+ if (!this.isMounted()) {
+ return;
+ }
+ },
+ render: function() {
+ return Hello
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ class Hello extends React.Component {
+ someMethod() {
+ if (!this.isMounted()) {
+ return;
+ }
+ }
+ render() {
+ return Hello
;
+ }
+ };
+ ",
+ None,
+ ),
+ ];
+
+ Tester::new(NoIsMounted::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_is_mounted.snap b/crates/oxc_linter/src/snapshots/no_is_mounted.snap
new file mode 100644
index 0000000000000..a72c3ef0412ff
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_is_mounted.snap
@@ -0,0 +1,32 @@
+---
+source: crates/oxc_linter/src/tester.rs
+expression: no_is_mounted
+---
+ ⚠ eslint(no-is-mounted): Do not use isMounted
+ ╭─[no_is_mounted.tsx:3:1]
+ 3 │ componentDidUpdate: function() {
+ 4 │ if (!this.isMounted()) {
+ · ────────────────
+ 5 │ return;
+ ╰────
+ help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
+
+ ⚠ eslint(no-is-mounted): Do not use isMounted
+ ╭─[no_is_mounted.tsx:3:1]
+ 3 │ someMethod: function() {
+ 4 │ if (!this.isMounted()) {
+ · ────────────────
+ 5 │ return;
+ ╰────
+ help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
+
+ ⚠ eslint(no-is-mounted): Do not use isMounted
+ ╭─[no_is_mounted.tsx:3:1]
+ 3 │ someMethod() {
+ 4 │ if (!this.isMounted()) {
+ · ────────────────
+ 5 │ return;
+ ╰────
+ help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
+
+