From 9f2412a84e820d39f03832b5a2e575131771eaa0 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 16 Oct 2023 21:21:19 +0100 Subject: [PATCH] feat(linter): eslint-plugin-unicorn(throw-new-error) --- crates/oxc_linter/src/rules.rs | 4 +- .../src/rules/unicorn/throw_new_error.rs | 139 +++++++++++++++ .../src/snapshots/throw_new_error.snap | 159 ++++++++++++++++++ 3 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 crates/oxc_linter/src/rules/unicorn/throw_new_error.rs create mode 100644 crates/oxc_linter/src/snapshots/throw_new_error.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 66ccf231613f5..6e87f01d3f60f 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -132,6 +132,7 @@ mod unicorn { pub mod no_thenable; pub mod no_unnecessary_await; pub mod prefer_array_flat_map; + pub mod throw_new_error; } oxc_macros::declare_all_lint_rules! { @@ -235,11 +236,12 @@ oxc_macros::declare_all_lint_rules! { jest::valid_title, unicorn::catch_error_name, unicorn::error_message, + unicorn::filename_case, unicorn::no_console_spaces, unicorn::no_instanceof_array, unicorn::no_unnecessary_await, unicorn::no_thenable, - unicorn::filename_case, + unicorn::throw_new_error, unicorn::prefer_array_flat_map, import::named, import::no_cycle, diff --git a/crates/oxc_linter/src/rules/unicorn/throw_new_error.rs b/crates/oxc_linter/src/rules/unicorn/throw_new_error.rs new file mode 100644 index 0000000000000..686b8ed4f0193 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/throw_new_error.rs @@ -0,0 +1,139 @@ +use lazy_static::lazy_static; +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; + +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use regex::Regex; + +use crate::{ast_util::outermost_paren, context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error.")] +#[diagnostic(severity(warning), help("While it's possible to create a new error without using the `new` keyword, it's better to be explicit."))] +struct ThrowNewErrorDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct ThrowNewError; + +declare_oxc_lint!( + /// ### What it does + /// + /// Require `new` when throwing an error.` + /// + /// ### Why is this bad? + /// + /// While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + /// + /// ### Example + /// ```javascript + /// // Fail + /// throw Error('๐Ÿฆ„'); + /// throw TypeError('unicorn'); + /// throw lib.TypeError('unicorn'); + /// + /// // Pass + /// throw new Error('๐Ÿฆ„'); + /// throw new TypeError('unicorn'); + /// throw new lib.TypeError('unicorn'); + /// + /// ``` + ThrowNewError, + style +); + +impl Rule for ThrowNewError { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { return }; + + let Some(outermost_paren_node) = ctx.nodes().parent_node(outermost_paren(node, ctx).id()) + else { + return; + }; + + let AstKind::ThrowStatement(_) = outermost_paren(outermost_paren_node, ctx).kind() else { + return; + }; + + match &call_expr.callee.without_parenthesized() { + Expression::Identifier(v) => { + if !CUSTOM_ERROR_REGEX_PATTERN.is_match(&v.name) { + return; + } + } + Expression::MemberExpression(v) => { + if v.is_computed() { + return; + } + if let Some(v) = v.static_property_name() { + if !CUSTOM_ERROR_REGEX_PATTERN.is_match(v) { + return; + } + } + } + _ => return, + } + + ctx.diagnostic(ThrowNewErrorDiagnostic(call_expr.span)); + } +} + +lazy_static! { + static ref CUSTOM_ERROR_REGEX_PATTERN: Regex = + Regex::new(r"^(?:[A-Z][\da-z]*)*Error$").unwrap(); +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("throw new Error()", None), + ("new Error()", None), + ("throw new TypeError()", None), + ("throw new EvalError()", None), + ("throw new RangeError()", None), + ("throw new ReferenceError()", None), + ("throw new SyntaxError()", None), + ("throw new URIError()", None), + ("throw new CustomError()", None), + ("throw new FooBarBazError()", None), + ("throw new ABCError()", None), + ("throw getError()", None), + ("throw CustomError", None), + ("throw getErrorConstructor()()", None), + ("throw lib[Error]()", None), + ("throw lib[\"Error\"]()", None), + ("throw lib.getError()", None), + ]; + + let fail = vec![ + ("throw Error()", None), + ("throw (Error)()", None), + ("throw lib.Error()", None), + ("throw lib.mod.Error()", None), + ("throw lib[mod].Error()", None), + ("throw (lib.mod).Error()", None), + ("throw Error('foo')", None), + ("throw CustomError('foo')", None), + ("throw FooBarBazError('foo')", None), + ("throw ABCError('foo')", None), + ("throw Abc3Error('foo')", None), + ("throw TypeError()", None), + ("throw EvalError()", None), + ("throw RangeError()", None), + ("throw ReferenceError()", None), + ("throw SyntaxError()", None), + ("throw URIError()", None), + ("throw (( URIError() ))", None), + ("throw (( URIError ))()", None), + ("throw getGlobalThis().Error()", None), + ("throw utils.getGlobalThis().Error()", None), + ("throw (( getGlobalThis().Error ))()", None), + ]; + + Tester::new(ThrowNewError::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/throw_new_error.snap b/crates/oxc_linter/src/snapshots/throw_new_error.snap new file mode 100644 index 0000000000000..65f68ac6d4a3f --- /dev/null +++ b/crates/oxc_linter/src/snapshots/throw_new_error.snap @@ -0,0 +1,159 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: throw_new_error +--- + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw (Error)() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw lib.Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw lib.mod.Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw lib[mod].Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw (lib.mod).Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw Error('foo') + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw CustomError('foo') + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw FooBarBazError('foo') + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw ABCError('foo') + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw Abc3Error('foo') + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw TypeError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw EvalError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw RangeError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw ReferenceError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw SyntaxError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw URIError() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw (( URIError() )) + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw (( URIError ))() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw getGlobalThis().Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw utils.getGlobalThis().Error() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + + โš  eslint-plugin-unicorn(throw-new-error): Require `new` when throwing an error. + โ•ญโ”€[throw_new_error.tsx:1:1] + 1 โ”‚ throw (( getGlobalThis().Error ))() + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: While it's possible to create a new error without using the `new` keyword, it's better to be explicit. + +