Array Method Callback Must Return a Value

ID

javascript.array_callback_return

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

reliability, suspicious-callback

Description

Reports inline callbacks passed to Array.prototype methods that require a return value but don’t produce one. The affected methods are those whose semantics depend on each callback invocation returning a value:

  • map, flatMap — collect transformed values.

  • filter — keep only elements where the callback is truthy.

  • reduce, reduceRight — accumulate the next state.

  • find, findIndex, findLast, findLastIndex, some, every — test a predicate.

  • sort — compute an ordering integer.

  • Array.from 2-argument overload — map each value while constructing the array.

Two failure modes are flagged:

  • The callback contains no return statement and never throws. Every call yields undefined, silently turning map into an array of holes, filter into an empty array, reduce into a TypeError, and so on.

  • The callback contains a bare return; (no expression). That path also yields undefined and is almost always a missed early-exit value.

Only inline function literals (arrow functions, function expressions, and their async / generator variants) directly passed as the first argument are inspected. Identifier callbacks (e.g. arr.map(cb)) are out of scope — the rule is intentionally local to avoid resolving foreign function bodies. Methods whose callback is not expected to return (e.g. forEach) are ignored.

Rationale

  • These methods are the workhorses of array transformation in JavaScript. A non-returning callback is silently corrupted data: map produces [undefined, undefined, …], filter filters everything out, reduce throws a confusing TypeError, and sort becomes order-unstable.

  • The bug usually surfaces far away from the broken callback — typically in a downstream .length, JSON serialization, or an Array.includes that returns false for everything. Catching the missing return at the call site avoids those long-range failures.

  • A callback that always throws is allowed because the throw is itself an explicit contract — "this configuration is forbidden". A callback that sometimes returns and sometimes does not is the dangerous case, and the bare return; form is the most common offender.

// Bad
const doubled = numbers.map(n => {
  n * 2;            // expression statement, value discarded
});
doubled;            // [undefined, undefined, …]

// Bad
const positives = numbers.filter(function (n) {
  n > 0;            // forgot to return
});
positives.length;   // 0

// Bad
const total = numbers.reduce((acc, n) => {
  acc + n;          // returns undefined → TypeError next iteration
}, 0);

Remediation

Add an explicit return of the value the callback is meant to produce, or rewrite the callback as a concise arrow function whose body is the expression itself.

// Good — concise arrow
const doubled = numbers.map(n => n * 2);

// Good — explicit return
const positives = numbers.filter(function (n) {
  return n > 0;
});

// Good — every path returns
const total = numbers.reduce((acc, n) => {
  return acc + n;
}, 0);

If the side effect is what you actually want (logging, mutation, etc.), switch to a method that does not require a return — typically forEach or a for…of loop.

References