From 7a2a7d0ab94ee5370c07ec7d877858c2b106726d Mon Sep 17 00:00:00 2001 From: Mikhail Baev Date: Thu, 12 Mar 2026 21:48:48 +0500 Subject: [PATCH] feat(linter): implement `n/handle-callback-err` rule (#19616) this PR implements `n/handle-callback-err` rule, passes all tests from original [rule](https://github.com/eslint-community/eslint-plugin-n/blob/master/tests/lib/rules/handle-callback-err.js) issue #493 --------- Signed-off-by: Mikhail Baev Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Cameron Clark --- .../src/generated/rule_runner_impls.rs | 6 + crates/oxc_linter/src/generated/rules_enum.rs | 26 +- crates/oxc_linter/src/rules.rs | 1 + .../src/rules/node/handle_callback_err.rs | 278 ++++++++++++++++++ .../snapshots/node_handle_callback_err.snap | 143 +++++++++ 5 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 crates/oxc_linter/src/rules/node/handle_callback_err.rs create mode 100644 crates/oxc_linter/src/snapshots/node_handle_callback_err.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index eb7a4b3da7..177cdf1aae 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4310,6 +4310,12 @@ impl RuleRunner for crate::rules::node::global_require::GlobalRequire { const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::node::handle_callback_err::HandleCallbackErr { + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::node::no_exports_assign::NoExportsAssign { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::AssignmentExpression])); diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 1dd33290fd..3119531d64 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -335,6 +335,7 @@ pub use crate::rules::nextjs::no_title_in_document_head::NoTitleInDocumentHead a pub use crate::rules::nextjs::no_typos::NoTypos as NextjsNoTypos; pub use crate::rules::nextjs::no_unwanted_polyfillio::NoUnwantedPolyfillio as NextjsNoUnwantedPolyfillio; pub use crate::rules::node::global_require::GlobalRequire as NodeGlobalRequire; +pub use crate::rules::node::handle_callback_err::HandleCallbackErr as NodeHandleCallbackErr; pub use crate::rules::node::no_exports_assign::NoExportsAssign as NodeNoExportsAssign; pub use crate::rules::node::no_new_require::NoNewRequire as NodeNoNewRequire; pub use crate::rules::node::no_path_concat::NoPathConcat as NodeNoPathConcat; @@ -1396,6 +1397,7 @@ pub enum RuleEnum { ), VitestWarnTodo(VitestWarnTodo), NodeGlobalRequire(NodeGlobalRequire), + NodeHandleCallbackErr(NodeHandleCallbackErr), NodeNoExportsAssign(NodeNoExportsAssign), NodeNoNewRequire(NodeNoNewRequire), NodeNoPathConcat(NodeNoPathConcat), @@ -2178,7 +2180,8 @@ const VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID: usize = const VITEST_WARN_TODO_ID: usize = VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID + 1usize; const NODE_GLOBAL_REQUIRE_ID: usize = VITEST_WARN_TODO_ID + 1usize; -const NODE_NO_EXPORTS_ASSIGN_ID: usize = NODE_GLOBAL_REQUIRE_ID + 1usize; +const NODE_HANDLE_CALLBACK_ERR_ID: usize = NODE_GLOBAL_REQUIRE_ID + 1usize; +const NODE_NO_EXPORTS_ASSIGN_ID: usize = NODE_HANDLE_CALLBACK_ERR_ID + 1usize; const NODE_NO_NEW_REQUIRE_ID: usize = NODE_NO_EXPORTS_ASSIGN_ID + 1usize; const NODE_NO_PATH_CONCAT_ID: usize = NODE_NO_NEW_REQUIRE_ID + 1usize; const NODE_NO_PROCESS_ENV_ID: usize = NODE_NO_PATH_CONCAT_ID + 1usize; @@ -2987,6 +2990,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VITEST_WARN_TODO_ID, Self::NodeGlobalRequire(_) => NODE_GLOBAL_REQUIRE_ID, + Self::NodeHandleCallbackErr(_) => NODE_HANDLE_CALLBACK_ERR_ID, Self::NodeNoExportsAssign(_) => NODE_NO_EXPORTS_ASSIGN_ID, Self::NodeNoNewRequire(_) => NODE_NO_NEW_REQUIRE_ID, Self::NodeNoPathConcat(_) => NODE_NO_PATH_CONCAT_ID, @@ -3785,6 +3789,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::NAME, Self::NodeGlobalRequire(_) => NodeGlobalRequire::NAME, + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::NAME, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::NAME, Self::NodeNoNewRequire(_) => NodeNoNewRequire::NAME, Self::NodeNoPathConcat(_) => NodeNoPathConcat::NAME, @@ -4627,6 +4632,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::CATEGORY, Self::NodeGlobalRequire(_) => NodeGlobalRequire::CATEGORY, + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::CATEGORY, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::CATEGORY, Self::NodeNoNewRequire(_) => NodeNoNewRequire::CATEGORY, Self::NodeNoPathConcat(_) => NodeNoPathConcat::CATEGORY, @@ -5428,6 +5434,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::FIX, Self::NodeGlobalRequire(_) => NodeGlobalRequire::FIX, + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::FIX, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::FIX, Self::NodeNoNewRequire(_) => NodeNoNewRequire::FIX, Self::NodeNoPathConcat(_) => NodeNoPathConcat::FIX, @@ -6423,6 +6430,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::documentation(), Self::NodeGlobalRequire(_) => NodeGlobalRequire::documentation(), + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::documentation(), Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::documentation(), Self::NodeNoNewRequire(_) => NodeNoNewRequire::documentation(), Self::NodeNoPathConcat(_) => NodeNoPathConcat::documentation(), @@ -8372,6 +8380,8 @@ impl RuleEnum { .or_else(|| VitestWarnTodo::schema(generator)), Self::NodeGlobalRequire(_) => NodeGlobalRequire::config_schema(generator) .or_else(|| NodeGlobalRequire::schema(generator)), + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::config_schema(generator) + .or_else(|| NodeHandleCallbackErr::schema(generator)), Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::config_schema(generator) .or_else(|| NodeNoExportsAssign::schema(generator)), Self::NodeNoNewRequire(_) => NodeNoNewRequire::config_schema(generator) @@ -9112,6 +9122,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => "vitest", Self::VitestWarnTodo(_) => "vitest", Self::NodeGlobalRequire(_) => "node", + Self::NodeHandleCallbackErr(_) => "node", Self::NodeNoExportsAssign(_) => "node", Self::NodeNoNewRequire(_) => "node", Self::NodeNoPathConcat(_) => "node", @@ -11315,6 +11326,9 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => { Ok(Self::NodeGlobalRequire(NodeGlobalRequire::from_configuration(value)?)) } + Self::NodeHandleCallbackErr(_) => { + Ok(Self::NodeHandleCallbackErr(NodeHandleCallbackErr::from_configuration(value)?)) + } Self::NodeNoExportsAssign(_) => { Ok(Self::NodeNoExportsAssign(NodeNoExportsAssign::from_configuration(value)?)) } @@ -12063,6 +12077,7 @@ impl RuleEnum { } Self::VitestWarnTodo(rule) => rule.to_configuration(), Self::NodeGlobalRequire(rule) => rule.to_configuration(), + Self::NodeHandleCallbackErr(rule) => rule.to_configuration(), Self::NodeNoExportsAssign(rule) => rule.to_configuration(), Self::NodeNoNewRequire(rule) => rule.to_configuration(), Self::NodeNoPathConcat(rule) => rule.to_configuration(), @@ -12765,6 +12780,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run(node, ctx), Self::VitestWarnTodo(rule) => rule.run(node, ctx), Self::NodeGlobalRequire(rule) => rule.run(node, ctx), + Self::NodeHandleCallbackErr(rule) => rule.run(node, ctx), Self::NodeNoExportsAssign(rule) => rule.run(node, ctx), Self::NodeNoNewRequire(rule) => rule.run(node, ctx), Self::NodeNoPathConcat(rule) => rule.run(node, ctx), @@ -13467,6 +13483,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_once(ctx), Self::VitestWarnTodo(rule) => rule.run_once(ctx), Self::NodeGlobalRequire(rule) => rule.run_once(ctx), + Self::NodeHandleCallbackErr(rule) => rule.run_once(ctx), Self::NodeNoExportsAssign(rule) => rule.run_once(ctx), Self::NodeNoNewRequire(rule) => rule.run_once(ctx), Self::NodeNoPathConcat(rule) => rule.run_once(ctx), @@ -14269,6 +14286,7 @@ impl RuleEnum { } Self::VitestWarnTodo(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeGlobalRequire(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::NodeHandleCallbackErr(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoExportsAssign(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoNewRequire(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoPathConcat(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14971,6 +14989,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.should_run(ctx), Self::VitestWarnTodo(rule) => rule.should_run(ctx), Self::NodeGlobalRequire(rule) => rule.should_run(ctx), + Self::NodeHandleCallbackErr(rule) => rule.should_run(ctx), Self::NodeNoExportsAssign(rule) => rule.should_run(ctx), Self::NodeNoNewRequire(rule) => rule.should_run(ctx), Self::NodeNoPathConcat(rule) => rule.should_run(ctx), @@ -15965,6 +15984,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::IS_TSGOLINT_RULE, Self::NodeGlobalRequire(_) => NodeGlobalRequire::IS_TSGOLINT_RULE, + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::IS_TSGOLINT_RULE, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::IS_TSGOLINT_RULE, Self::NodeNoNewRequire(_) => NodeNoNewRequire::IS_TSGOLINT_RULE, Self::NodeNoPathConcat(_) => NodeNoPathConcat::IS_TSGOLINT_RULE, @@ -16836,6 +16856,7 @@ impl RuleEnum { } Self::VitestWarnTodo(_) => VitestWarnTodo::HAS_CONFIG, Self::NodeGlobalRequire(_) => NodeGlobalRequire::HAS_CONFIG, + Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::HAS_CONFIG, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::HAS_CONFIG, Self::NodeNoNewRequire(_) => NodeNoNewRequire::HAS_CONFIG, Self::NodeNoPathConcat(_) => NodeNoPathConcat::HAS_CONFIG, @@ -17540,6 +17561,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.types_info(), Self::VitestWarnTodo(rule) => rule.types_info(), Self::NodeGlobalRequire(rule) => rule.types_info(), + Self::NodeHandleCallbackErr(rule) => rule.types_info(), Self::NodeNoExportsAssign(rule) => rule.types_info(), Self::NodeNoNewRequire(rule) => rule.types_info(), Self::NodeNoPathConcat(rule) => rule.types_info(), @@ -18242,6 +18264,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_info(), Self::VitestWarnTodo(rule) => rule.run_info(), Self::NodeGlobalRequire(rule) => rule.run_info(), + Self::NodeHandleCallbackErr(rule) => rule.run_info(), Self::NodeNoExportsAssign(rule) => rule.run_info(), Self::NodeNoNewRequire(rule) => rule.run_info(), Self::NodeNoPathConcat(rule) => rule.run_info(), @@ -19062,6 +19085,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( ), RuleEnum::VitestWarnTodo(VitestWarnTodo::default()), RuleEnum::NodeGlobalRequire(NodeGlobalRequire::default()), + RuleEnum::NodeHandleCallbackErr(NodeHandleCallbackErr::default()), RuleEnum::NodeNoExportsAssign(NodeNoExportsAssign::default()), RuleEnum::NodeNoNewRequire(NodeNoNewRequire::default()), RuleEnum::NodeNoPathConcat(NodeNoPathConcat::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 7d61594c08..0299d8631c 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -722,6 +722,7 @@ pub(crate) mod vitest { pub(crate) mod node { pub mod global_require; + pub mod handle_callback_err; pub mod no_exports_assign; pub mod no_new_require; pub mod no_path_concat; diff --git a/crates/oxc_linter/src/rules/node/handle_callback_err.rs b/crates/oxc_linter/src/rules/node/handle_callback_err.rs new file mode 100644 index 0000000000..eab0357744 --- /dev/null +++ b/crates/oxc_linter/src/rules/node/handle_callback_err.rs @@ -0,0 +1,278 @@ +use std::borrow::Cow; + +use lazy_regex::Regex; +use schemars::JsonSchema; +use serde::Deserialize; + +use oxc_ast::{AstKind, ast::FormalParameters}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + AstNode, + context::LintContext, + rule::{DefaultRuleConfig, Rule}, +}; + +fn handle_callback_err_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Expected error to be handled.") + .with_help("Handle the error or rename the parameter if it's not an error.") + .with_label(span) +} + +#[derive(Debug, Clone, JsonSchema)] +enum ErrorPattern { + Plain(String), + Regex(Regex), +} + +impl Default for ErrorPattern { + fn default() -> Self { + Self::Plain("err".to_string()) + } +} + +impl ErrorPattern { + fn matches(&self, name: &str) -> bool { + match self { + Self::Plain(s) => name == s, + Self::Regex(r) => r.is_match(name), + } + } +} + +fn deserialize_error_pattern<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let s = >::deserialize(deserializer)?; + + let pattern = { + if s.starts_with('^') { + Regex::new(&s).map_or_else(|_| ErrorPattern::Plain(s.to_string()), ErrorPattern::Regex) + } else { + ErrorPattern::Plain(s.to_string()) + } + }; + + Ok(Box::new(pattern)) +} + +/// The rule takes a single string option: the name of the error parameter. +/// +/// This can be either: +/// - an exact name (e.g. `"err"`, `"error"`) +/// - a regexp pattern (e.g. `"^(err|error)$"`) +/// +/// If the configured name of the error variable begins with a `^` it is considered to be a regexp pattern. +/// +/// Default: `"err"`. +#[derive(Debug, Clone, Default, JsonSchema, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct HandleCallbackErrConfig(String); + +#[derive(Debug, Default, Clone, JsonSchema, Deserialize)] +pub struct HandleCallbackErr( + #[serde(deserialize_with = "deserialize_error_pattern")] Box, +); + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule expects that when you're using the callback pattern in Node.js you'll handle the error. + /// + /// ### Why is this bad? + /// + /// In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. + /// This pattern expects an `Error` object or `null` as the first argument of the callback. + /// Forgetting to handle these errors can lead to some really strange behavior in your application. + /// + /// ```js + /// function loadData (err, data) { + /// doSomething(); // forgot to handle error + /// } + /// ``` + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule with the default `"err"` parameter name: + /// ```js + /// function loadData (err, data) { + /// doSomething(); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule with the default `"err"` parameter name: + /// ```js + /// function loadData (err, data) { + /// if (err) { + /// console.log(err.stack); + /// } + /// doSomething(); + /// } + /// + /// function generateError (err) { + /// if (err) {} + /// } + /// ``` + /// + /// Examples of correct code for this rule with a sample `"error"` parameter name: + ///```js + /// function loadData (error, data) { + /// if (error) { + /// console.log(error.stack); + /// } + /// doSomething(); + /// } + ///``` + HandleCallbackErr, + node, + restriction, + config = HandleCallbackErrConfig, +); + +impl Rule for HandleCallbackErr { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value).map(DefaultRuleConfig::into_inner) + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::Function(func) => { + self.check_for_error(&func.params, ctx); + } + AstKind::ArrowFunctionExpression(arrow_func) => { + self.check_for_error(&arrow_func.params, ctx); + } + _ => {} + } + } +} + +impl HandleCallbackErr { + fn check_for_error(&self, params: &FormalParameters, ctx: &LintContext) { + if let Some(first_param) = params.items.first() + && let Some(ident) = first_param.pattern.get_binding_identifier() + && self.0.matches(&ident.name) + && ctx.symbol_references(ident.symbol_id()).count() == 0 + { + ctx.diagnostic(handle_callback_err_diagnostic(ident.span)); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("function test(error) {}", None), + ("function test(err) {console.log(err);}", None), + ("function test(err, data) {if(err){ data = 'ERROR';}}", None), + ("var test = function(err) {console.log(err);};", None), + ("var test = function(err) {if(err){/* do nothing */}};", None), + ("var test = function(err) {if(!err){doSomethingHere();}else{};}", None), + ("var test = function(err, data) {if(!err) { good(); } else { bad(); }}", None), + ("try { } catch(err) {}", None), + ( + "getData(function(err, data) {if (err) {}getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});});});", + None, + ), + ("var test = function(err) {if(! err){doSomethingHere();}};", None), + ( + "function test(err, data) {if (data) {doSomething(function(err) {console.error(err);});} else if (err) {console.log(err);}}", + None, + ), + ( + "function handler(err, data) {if (data) {doSomethingWith(data);} else if (err) {console.log(err);}}", + None, + ), + ( + "function handler(err) {logThisAction(function(err) {if (err) {}}); console.log(err);}", + None, + ), + ("function userHandler(err) {process.nextTick(function() {if (err) {}})}", None), + ( + "function help() { function userHandler(err) {function tester() { err; process.nextTick(function() { err; }); } } }", + None, + ), + ("function help(done) { var err = new Error('error'); done(); }", None), + ("var test = err => err;", None), + ("var test = err => !err;", None), + ("var test = err => err.message;", None), + ( + "var test = function(error) {if(error){/* do nothing */}};", + Some(serde_json::json!(["error"])), + ), + ( + "var test = (error) => {if(error){/* do nothing */}};", + Some(serde_json::json!(["error"])), + ), + ( + "var test = function(error) {if(! error){doSomethingHere();}};", + Some(serde_json::json!(["error"])), + ), + ( + "var test = function(err) { console.log(err); };", + Some(serde_json::json!(["^(err|error)$"])), + ), + ( + "var test = function(error) { console.log(error); };", + Some(serde_json::json!(["^(err|error)$"])), + ), + ( + "var test = function(anyError) { console.log(anyError); };", + Some(serde_json::json!(["^.+Error$"])), + ), + ( + "var test = function(any_error) { console.log(anyError); };", + Some(serde_json::json!(["^.+Error$"])), + ), + ( + "var test = function(any_error) { console.log(any_error); };", + Some(serde_json::json!(["^.+(e|E)rror$"])), // spellchecker:disable-line + ), + ]; + + let fail = vec![ + ("function test(err) {}", None), + ("function test(err, data) {}", None), + ("function test(err) {errorLookingWord();}", None), + ("function test(err) {try{} catch(err) {}}", None), + ("function test(err, callback) { foo(function(err, callback) {}); }", None), + ("var test = (err) => {};", None), + ("var test = function(err) {};", None), + ("var test = function test(err, data) {};", None), + ("var test = function test(err) {/* if(err){} */};", None), + ("function test(err) {doSomethingHere(function(err){console.log(err);})}", None), + ("function test(error) {}", Some(serde_json::json!(["error"]))), + ( + "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + None, + ), + ( + "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + None, + ), + ( + "function userHandler(err) {logThisAction(function(err) {if (err) { console.log(err); } })}", + None, + ), + ( + "function help() { function userHandler(err) {function tester(err) { err; process.nextTick(function() { err; }); } } }", + None, + ), + ( + "var test = function(anyError) { console.log(otherError); };", + Some(serde_json::json!(["^.+Error$"])), + ), + ("var test = function(anyError) { };", Some(serde_json::json!(["^.+Error$"]))), + ( + "var test = function(err) { console.log(error); };", + Some(serde_json::json!(["^(err|error)$"])), + ), + ]; + + Tester::new(HandleCallbackErr::NAME, HandleCallbackErr::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/node_handle_callback_err.snap b/crates/oxc_linter/src/snapshots/node_handle_callback_err.snap new file mode 100644 index 0000000000..d7f8e5a286 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/node_handle_callback_err.snap @@ -0,0 +1,143 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err) {} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err, data) {} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err) {errorLookingWord();} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err) {try{} catch(err) {}} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err, callback) { foo(function(err, callback) {}); } + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:45] + 1 │ function test(err, callback) { foo(function(err, callback) {}); } + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:13] + 1 │ var test = (err) => {}; + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:21] + 1 │ var test = function(err) {}; + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:26] + 1 │ var test = function test(err, data) {}; + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:26] + 1 │ var test = function test(err) {/* if(err){} */}; + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(err) {doSomethingHere(function(err){console.log(err);})} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:15] + 1 │ function test(error) {} + · ───── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:18] + 1 │ getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); }); + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:18] + 1 │ getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); }); + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:61] + 1 │ getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); }); + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:22] + 1 │ function userHandler(err) {logThisAction(function(err) {if (err) { console.log(err); } })} + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:40] + 1 │ function help() { function userHandler(err) {function tester(err) { err; process.nextTick(function() { err; }); } } } + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:21] + 1 │ var test = function(anyError) { console.log(otherError); }; + · ──────── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:21] + 1 │ var test = function(anyError) { }; + · ──────── + ╰──── + help: Handle the error or rename the parameter if it's not an error. + + ⚠ eslint-plugin-node(handle-callback-err): Expected error to be handled. + ╭─[handle_callback_err.tsx:1:21] + 1 │ var test = function(err) { console.log(error); }; + · ─── + ╰──── + help: Handle the error or rename the parameter if it's not an error.