-
-
Notifications
You must be signed in to change notification settings - Fork 497
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): no-is-mounted for eslint-plugin-react (#1550)
Try to implement `no-is-mounted` for #1022
- Loading branch information
Showing
3 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
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. | ||
|
||
|