Skip to content

Commit

Permalink
feat(linter): no-is-mounted for eslint-plugin-react (#1550)
Browse files Browse the repository at this point in the history
Try to implement `no-is-mounted` for #1022
  • Loading branch information
Ken-HH24 authored Nov 26, 2023
1 parent 023e6ea commit a2510be
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
155 changes: 155 additions & 0 deletions crates/oxc_linter/src/rules/react/no_is_mounted.rs
Original file line number Diff line number Diff line change
@@ -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 <div onClick={this.someMethod.bind(this)}>Hello</div>;
/// }
/// };
/// ```
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 <div>Hello</div>;
}
});
",
None,
),
];

let fail = vec![
(
"
var Hello = createReactClass({
componentDidUpdate: function() {
if (!this.isMounted()) {
return;
}
},
render: function() {
return <div>Hello</div>;
}
});
",
None,
),
(
"
var Hello = createReactClass({
someMethod: function() {
if (!this.isMounted()) {
return;
}
},
render: function() {
return <div onClick={this.someMethod.bind(this)}>Hello</div>;
}
});
",
None,
),
(
"
class Hello extends React.Component {
someMethod() {
if (!this.isMounted()) {
return;
}
}
render() {
return <div onClick={this.someMethod.bind(this)}>Hello</div>;
}
};
",
None,
),
];

Tester::new(NoIsMounted::NAME, pass, fail).test_and_snapshot();
}
32 changes: 32 additions & 0 deletions crates/oxc_linter/src/snapshots/no_is_mounted.snap
Original file line number Diff line number Diff line change
@@ -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]
3componentDidUpdate: function() {
4if (!this.isMounted()) {
· ────────────────
5return;
╰────
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]
3someMethod: function() {
4if (!this.isMounted()) {
· ────────────────
5return;
╰────
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]
3someMethod() {
4if (!this.isMounted()) {
· ────────────────
5return;
╰────
help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.


0 comments on commit a2510be

Please sign in to comment.