{"record":{"id":"7bf88a8a15a0bd5a","repo":"oxc-project/oxc","slug":"expected-return-with-your-callback-function","errorCode":null,"errorMessage":"Expected return with your callback function.","messagePattern":"Expected return with your callback function\\.","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/node/callback_return.rs","lineNumber":20,"sourceCode":"use serde::Deserialize;\n\nuse oxc_ast::{\n    AstKind,\n    ast::{CallExpression, Expression, Statement},\n};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::{GetSpan, Span};\nuse oxc_str::CompactStr;\n\nuse crate::{\n    AstNode,\n    context::LintContext,\n    rule::{DefaultRuleConfig, Rule},\n};\n\nfn callback_return_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Expected return with your callback function.\")\n        .with_help(\"Return the callback call or add an explicit return immediately after it.\")\n        .with_label(span)\n}\n\n#[derive(Debug, Clone, JsonSchema, Deserialize)]\n/// The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`.\npub struct CallbackReturn(Box<CallbackNames>);\n\n#[derive(Debug, Clone, JsonSchema, Deserialize)]\n#[serde(transparent)]\nstruct CallbackNames(Vec<CompactStr>);\n\nimpl Default for CallbackReturn {\n    fn default() -> Self {\n        Self(Box::new(CallbackNames(vec![\"callback\".into(), \"cb\".into(), \"next\".into()])))\n    }\n}\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/node/callback_return.rs#L2-L38","documentation":"Diagnostic from oxlint rule node/callback-return (style category, ported from ESLint n/callback-return). In Node callback-style code, after you invoke the completion callback the function must stop: if (err) { callback(err); } followed by more statements runs that extra code and can end up calling the callback twice. The rule detects callbacks purely by callee name — defaults are callback, cb, next, configurable as an array that also accepts dotted paths like obj.method — and demands the call be part of a return statement (or immediately followed by a bare return).","triggerScenarios":"A CallExpression whose callee is a plain identifier or member chain whose source text exactly equals a configured callback name, nested inside a function, where the closest block ancestor is not a ReturnStatement or concise arrow body, and the callback statement is neither the last statement of the function body nor the statement immediately preceding a final 'return'. Canonical trigger: function a(err) { if (err) { callback(err); } horse(); } — the call sits mid-block with code after it.","commonSituations":"Callback-era Express/MongoDB codebases migrated from ESLint's n/callback-return to oxlint; enabling the whole node preset in a shared config; documented false positive: if/else branches that each call the callback once still warn (static analysis cannot prove single-invocation); documented false negatives: setTimeout(callback, 0), IIFE-wrapped or process.nextTick-nested calls are not matched at all.","solutions":["Prefix the call with return: 'return callback(err);' — the canonical fix","If the call must not be the return value, add an explicit 'return;' as the very next statement so nothing after it executes","If the callback genuinely fires exactly once and the warning is a known if/else false positive, rename the parameter so it stops matching callback/cb/next, or disable the rule for the file","Configure the rule with your real callback names: \"node/callback-return\": [\"error\", [\"callback\", \"cb\", \"next\", \"obj.method\"]]","Long term, refactor the function to async/await or Promises so the callback disappears entirely"],"exampleFix":"// before\nfunction done(err) {\n  if (err) {\n    callback(err);\n  }\n  callback(); // runs even after the error callback\n}\n\n// after\nfunction done(err) {\n  if (err) {\n    return callback(err);\n  }\n  callback();\n}","handlingStrategy":"validation","validationCode":"// .oxlintrc.json\n\"rules\": {\n  \"node/callback-return\": [\"error\", [\"callback\", \"cb\", \"next\", \"done\"]]\n}\n\n// CI gate\nnpx oxlint -c .oxlintrc.json --deny-warning .","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Adopt the habit 'call the callback, then return' — every callback invocation is either return cb(...) or followed by an immediate return","Review if/else branches flagged by this rule manually: the rule cannot prove single-invocation, so verify each branch calls the callback exactly once","New code: prefer Promises/async-await so callback flow (and this rule) disappears entirely"],"tags":["node","callback","control-flow","style","oxlint"],"backgroundTag":"missing-return-after-callback","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}