diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index e2c32a1b1..688b6858c 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -163,6 +163,7 @@ mod unicorn { pub mod prefer_code_point; pub mod prefer_date_now; pub mod prefer_logical_operator_over_ternary; + pub mod prefer_optional_catch_binding; pub mod prefer_query_selector; pub mod prefer_regexp_test; pub mod prefer_string_trim_start_end; @@ -301,6 +302,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::prefer_code_point, unicorn::prefer_date_now, unicorn::prefer_logical_operator_over_ternary, + unicorn::prefer_optional_catch_binding, unicorn::prefer_query_selector, unicorn::prefer_regexp_test, unicorn::prefer_string_trim_start_end, diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_optional_catch_binding.rs b/crates/oxc_linter/src/rules/unicorn/prefer_optional_catch_binding.rs new file mode 100644 index 000000000..22ad3c4bd --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/prefer_optional_catch_binding.rs @@ -0,0 +1,119 @@ +use oxc_ast::{ + ast::{BindingPattern, BindingPatternKind}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused")] +#[diagnostic(severity(warning))] +struct PreferOptionalCatchBindingDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct PreferOptionalCatchBinding; + +declare_oxc_lint!( + /// ### What it does + /// + /// Prefers omitting the catch binding parameter if it is unused + /// + /// ### Why is this bad? + /// + /// It is unnecessary to bind the error to a variable if it is not used. + /// + /// ### Example + /// ```javascript + /// // Bad + /// try { + /// // ... + /// } catch (e) { } + /// + /// // Good + /// try { + /// // ... + /// } catch { } + /// ``` + PreferOptionalCatchBinding, + style +); + +impl Rule for PreferOptionalCatchBinding { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CatchClause(catch_clause) = node.kind() else { return }; + + let Some(catch_param) = &catch_clause.param else { return }; + + let references_count = get_param_references_count(catch_param, ctx); + + if references_count != 0 { + return; + } + + ctx.diagnostic(PreferOptionalCatchBindingDiagnostic(catch_param.span())); + } +} + +fn get_param_references_count(binding_pat: &BindingPattern, ctx: &LintContext) -> usize { + match &binding_pat.kind { + BindingPatternKind::BindingIdentifier(binding_ident) => { + ctx.semantic().symbol_references(binding_ident.symbol_id.get().unwrap()).count() + } + BindingPatternKind::ObjectPattern(object_pat) => { + let mut count = 0; + + for prop in &object_pat.properties { + count += get_param_references_count(&prop.value, ctx); + } + + if let Some(rest) = &object_pat.rest { + count += get_param_references_count(&rest.argument, ctx); + } + + count + } + BindingPatternKind::AssignmentPattern(_) => 1, + BindingPatternKind::ArrayPattern(array_pat) => { + let mut count = 0; + + for element in (&array_pat.elements).into_iter().flatten() { + count += get_param_references_count(element, ctx); + } + + count + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"try {} catch {}"#, + r#"try {} catch ({message}) {alert(message)}"#, + r#"try {} catch ({cause: {message}}) {alert(message)}"#, + r#"try {} catch({nonExistsProperty = thisWillExecute()}) {}"#, + ]; + + let fail = vec![ + r#"try {} catch (_) {}"#, + r#"try {} catch (theRealErrorName) {}"#, + r#"try { } catch (e) + { }"#, + r#"try {} catch(e) {}"#, + r#"try {} catch (e){}"#, + r#"try {} catch ({}) {}"#, + r#"try {} catch ({message}) {}"#, + r#"try {} catch ({message: notUsedMessage}) {}"#, + r#"try {} catch ({cause: {message}}) {}"#, + ]; + + Tester::new_without_config(PreferOptionalCatchBinding::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_optional_catch_binding.snap b/crates/oxc_linter/src/snapshots/prefer_optional_catch_binding.snap new file mode 100644 index 000000000..298ecad37 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_optional_catch_binding.snap @@ -0,0 +1,60 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_optional_catch_binding +--- + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch (_) {} + · ─ + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch (theRealErrorName) {} + · ──────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try { } catch (e) + · ─ + 2 │ { } + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch(e) {} + · ─ + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch (e){} + · ─ + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch ({}) {} + · ── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch ({message}) {} + · ───────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch ({message: notUsedMessage}) {} + · ───────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-optional-catch-binding): Prefer omitting the catch binding parameter if it is unused + ╭─[prefer_optional_catch_binding.tsx:1:1] + 1 │ try {} catch ({cause: {message}}) {} + · ────────────────── + ╰──── + +