From 0873733ae72bdf2d4434568814a582d1f635fb88 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 21 Jun 2023 10:25:40 +0800 Subject: [PATCH] feat(linter): implement `bad_remove_event_listener` from deepscan --- crates/oxc_linter/src/rules.rs | 1 + .../deepscan/bad_remove_event_listener.rs | 66 +++++++++++++++++++ .../snapshots/bad_remove_event_listener.snap | 19 ++++++ 3 files changed, 86 insertions(+) create mode 100644 crates/oxc_linter/src/rules/deepscan/bad_remove_event_listener.rs create mode 100644 crates/oxc_linter/src/snapshots/bad_remove_event_listener.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index ac7589ea8..a018bebe6 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -33,6 +33,7 @@ oxc_macros::declare_all_lint_rules! { deepscan::bad_array_method_on_arguments, deepscan::missing_throw, deepscan::bad_min_max_func, + deepscan::bad_remove_event_listener, use_isnan, valid_typeof, typescript::isolated_declaration diff --git a/crates/oxc_linter/src/rules/deepscan/bad_remove_event_listener.rs b/crates/oxc_linter/src/rules/deepscan/bad_remove_event_listener.rs new file mode 100644 index 000000000..6f3aafb4f --- /dev/null +++ b/crates/oxc_linter/src/rules/deepscan/bad_remove_event_listener.rs @@ -0,0 +1,66 @@ +use oxc_ast::{ast::Argument, 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("removeEventListener() should be called with a correct listener")] +#[diagnostic( + severity(warning), + help( + "This 'removeEventListener()' call does nothing because a newly created function is passed. Consider using the exact function instance that was added at the 'addEventListener()' call" + ) +)] +struct BadRemoveEventListenerDiagnostic(#[label] pub Span); + +/// `https://deepscan.io/docs/rules/bad-remove-event-listener` +#[derive(Debug, Default, Clone)] +pub struct BadRemoveEventListener; + +declare_oxc_lint!( + /// ### What it does + /// + /// Checks whether a newly created function is passed to `removeEventListener`. + /// + /// ### Example + /// ```javascript + /// document.removeEventListener('keydown', function () {}) + /// ``` + BadRemoveEventListener, + correctness +); + +impl Rule for BadRemoveEventListener { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::CallExpression(call_expr) = node.kind() + && let Some(member) = call_expr.callee.get_member_expr() + && let Some(name) = member.static_property_name() + && name == "removeEventListener" + && let Some(Argument::Expression(expr)) = call_expr.arguments.get(1) + && expr.is_function() { + ctx.diagnostic(BadRemoveEventListenerDiagnostic(call_expr.span)); + }; + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("document.removeEventListener('keydown', keydownHandler)", None), + ("document.removeEventListener('keydown', this.keydownHandler)", None), + ]; + + let fail = vec![ + ("document.removeEventListener('keydown', () => foo())", None), + ("document.removeEventListener('keydown', function () {})", None), + ]; + + Tester::new(BadRemoveEventListener::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/bad_remove_event_listener.snap b/crates/oxc_linter/src/snapshots/bad_remove_event_listener.snap new file mode 100644 index 000000000..7aea36329 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/bad_remove_event_listener.snap @@ -0,0 +1,19 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: bad_remove_event_listener +--- + ⚠ removeEventListener() should be called with a correct listener + ╭─[bad_remove_event_listener.tsx:1:1] + 1 │ document.removeEventListener('keydown', () => foo()) + · ──────────────────────────────────────────────────── + ╰──── + help: This 'removeEventListener()' call does nothing because a newly created function is passed. Consider using the exact function instance that was added at the 'addEventListener()' call + + ⚠ removeEventListener() should be called with a correct listener + ╭─[bad_remove_event_listener.tsx:1:1] + 1 │ document.removeEventListener('keydown', function () {}) + · ─────────────────────────────────────────────────────── + ╰──── + help: This 'removeEventListener()' call does nothing because a newly created function is passed. Consider using the exact function instance that was added at the 'addEventListener()' call + +