Skip to content

Commit a2510be

Browse files
authored
feat(linter): no-is-mounted for eslint-plugin-react (#1550)
Try to implement `no-is-mounted` for #1022
1 parent 023e6ea commit a2510be

File tree

3 files changed

+189
-0
lines changed

3 files changed

+189
-0
lines changed

crates/oxc_linter/src/rules.rs

+2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ mod react {
140140
pub mod no_children_prop;
141141
pub mod no_dangerously_set_inner_html;
142142
pub mod no_find_dom_node;
143+
pub mod no_is_mounted;
143144
pub mod no_render_return_value;
144145
pub mod no_string_refs;
145146
pub mod no_unescaped_entities;
@@ -387,6 +388,7 @@ oxc_macros::declare_all_lint_rules! {
387388
react::no_render_return_value,
388389
react::no_string_refs,
389390
react::no_unescaped_entities,
391+
react::no_is_mounted,
390392
import::default,
391393
import::named,
392394
import::no_cycle,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use oxc_ast::{
2+
ast::{CallExpression, Expression},
3+
AstKind,
4+
};
5+
use oxc_diagnostics::{
6+
miette::{self, Diagnostic},
7+
thiserror::Error,
8+
};
9+
use oxc_macros::declare_oxc_lint;
10+
use oxc_span::Span;
11+
12+
use crate::{context::LintContext, rule::Rule, AstNode};
13+
14+
#[derive(Debug, Error, Diagnostic)]
15+
#[error("eslint(no-is-mounted): Do not use isMounted")]
16+
#[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."))]
17+
struct NoIsMountedDiagnostic(#[label] pub Span);
18+
19+
#[derive(Debug, Default, Clone)]
20+
pub struct NoIsMounted;
21+
22+
declare_oxc_lint!(
23+
/// ### What it does
24+
///
25+
/// This rule prevents using isMounted in ES6 classes
26+
///
27+
/// ### Why is this bad?
28+
///
29+
/// isMounted is an anti-pattern, is not available when using ES6 classes,
30+
/// and it is on its way to being officially deprecated.///
31+
///
32+
/// ### Example
33+
/// ```javascript
34+
/// class Hello extends React.Component {
35+
/// someMethod() {
36+
/// if (!this.isMounted()) {
37+
/// return;
38+
/// }
39+
/// }
40+
/// render() {
41+
/// return <div onClick={this.someMethod.bind(this)}>Hello</div>;
42+
/// }
43+
/// };
44+
/// ```
45+
NoIsMounted,
46+
correctness
47+
);
48+
49+
impl Rule for NoIsMounted {
50+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
51+
let AstKind::CallExpression(CallExpression {
52+
callee: Expression::MemberExpression(member_expr),
53+
span,
54+
..
55+
}) = node.kind()
56+
else {
57+
return;
58+
};
59+
60+
if !matches!(member_expr.object(), Expression::ThisExpression(_))
61+
|| !member_expr.static_property_name().is_some_and(|str| str == "isMounted")
62+
{
63+
return;
64+
}
65+
66+
for ancestor in ctx.nodes().ancestors(node.id()).skip(1) {
67+
if matches!(
68+
ctx.nodes().kind(ancestor),
69+
AstKind::ObjectProperty(_) | AstKind::MethodDefinition(_)
70+
) {
71+
ctx.diagnostic(NoIsMountedDiagnostic(*span));
72+
break;
73+
}
74+
}
75+
}
76+
}
77+
78+
#[test]
79+
fn test() {
80+
use crate::tester::Tester;
81+
82+
let pass = vec![
83+
(
84+
"
85+
var Hello = function() {
86+
};
87+
",
88+
None,
89+
),
90+
(
91+
"
92+
var Hello = createReactClass({
93+
componentDidUpdate: function() {
94+
someNonMemberFunction(arg);
95+
this.someFunc = this.isMounted;
96+
},
97+
render: function() {
98+
return <div>Hello</div>;
99+
}
100+
});
101+
",
102+
None,
103+
),
104+
];
105+
106+
let fail = vec![
107+
(
108+
"
109+
var Hello = createReactClass({
110+
componentDidUpdate: function() {
111+
if (!this.isMounted()) {
112+
return;
113+
}
114+
},
115+
render: function() {
116+
return <div>Hello</div>;
117+
}
118+
});
119+
",
120+
None,
121+
),
122+
(
123+
"
124+
var Hello = createReactClass({
125+
someMethod: function() {
126+
if (!this.isMounted()) {
127+
return;
128+
}
129+
},
130+
render: function() {
131+
return <div onClick={this.someMethod.bind(this)}>Hello</div>;
132+
}
133+
});
134+
",
135+
None,
136+
),
137+
(
138+
"
139+
class Hello extends React.Component {
140+
someMethod() {
141+
if (!this.isMounted()) {
142+
return;
143+
}
144+
}
145+
render() {
146+
return <div onClick={this.someMethod.bind(this)}>Hello</div>;
147+
}
148+
};
149+
",
150+
None,
151+
),
152+
];
153+
154+
Tester::new(NoIsMounted::NAME, pass, fail).test_and_snapshot();
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
expression: no_is_mounted
4+
---
5+
eslint(no-is-mounted): Do not use isMounted
6+
╭─[no_is_mounted.tsx:3:1]
7+
3componentDidUpdate: function() {
8+
4if (!this.isMounted()) {
9+
· ────────────────
10+
5return;
11+
╰────
12+
help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
13+
14+
eslint(no-is-mounted): Do not use isMounted
15+
╭─[no_is_mounted.tsx:3:1]
16+
3someMethod: function() {
17+
4if (!this.isMounted()) {
18+
· ────────────────
19+
5return;
20+
╰────
21+
help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
22+
23+
eslint(no-is-mounted): Do not use isMounted
24+
╭─[no_is_mounted.tsx:3:1]
25+
3someMethod() {
26+
4if (!this.isMounted()) {
27+
· ────────────────
28+
5return;
29+
╰────
30+
help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.
31+
32+

0 commit comments

Comments
 (0)