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. + +