Rule
no-caught-error-interpolation (in eslint-factory/src/rules/no-caught-error-interpolation.ts). First quality review of this rule — no prior refinement issues filed.
Problem
isCaughtErrorVariableDef() only recognizes two binding shapes as "caught error" variables:
- A
try/catch clause with a simple identifier param.
- The parameter of an inline function passed to
.catch(fn) or .then(onFulfilled, onRejected) (via isInlineRejectionHandler, which requires the function's parent to be exactly that CallExpression).
A very common third shape is structurally identical but unrecognized: an inline arrow/function callback passed as the listener to an EventEmitter 'error' event — emitter.on('error', err => ...), .once('error', ...), .addListener('error', ...). Node guarantees the callback receives an Error object, so ${err} there has the exact same footgun the rule exists to prevent (redundant "Error: message" prefix, or "[object Object]" for non-Error emissions) — yet it is invisible to isInlineRejectionHandler, which only matches .catch/.then callees.
Grounded live occurrence
actions/setup/js/mcp_server_core.cjs:1052:
process.stdin.on("error", err => server.debug(`stdin error: ${err}`));
Here err is the parameter of an inline arrow function passed as the listener for the 'error' event on process.stdin (a stream/EventEmitter). This is a bare, unsafe interpolation of a real Error object, but no-caught-error-interpolation does not fire on it today.
Ask
- Extend the recognized "caught error variable" shapes to include the parameter of an inline
ArrowFunctionExpression/FunctionExpression passed as the listener argument to .on(...)/.once(...)/.addListener(...) when the event-name argument is the string literal "error" (mirroring the .catch/.then detection style already used for isInlineRejectionHandler).
- Only the listener parameter itself should be flagged (first param, matching Node's
(err) => ... signature for the 'error' event) — do not widen to other event names.
- Add test cases: valid (non-
'error' event names, e.g. .on('data', ...), are not flagged; named/hoisted listener functions may remain out of scope per the existing "inline" restriction, but should get an explicit valid test case documenting that as intentional) and invalid (emitter.on('error', err => \...${err}...`), .once('error', ...), .addListener('error', ...)`).
- Re-verify
mcp_server_core.cjs:1052 is flagged (and gets the String(err) fallback suggestion, since getErrorMessage is not imported there) once the fix lands.
Acceptance criteria
Generated by 🤖 ESLint Refiner · agent · 215.2 AIC · ⌖ 30.5 AIC · ⊞ 4.9K · ◷
Rule
no-caught-error-interpolation(ineslint-factory/src/rules/no-caught-error-interpolation.ts). First quality review of this rule — no prior refinement issues filed.Problem
isCaughtErrorVariableDef()only recognizes two binding shapes as "caught error" variables:try/catchclause with a simple identifier param..catch(fn)or.then(onFulfilled, onRejected)(viaisInlineRejectionHandler, which requires the function's parent to be exactly thatCallExpression).A very common third shape is structurally identical but unrecognized: an inline arrow/function callback passed as the listener to an EventEmitter
'error'event —emitter.on('error', err => ...),.once('error', ...),.addListener('error', ...). Node guarantees the callback receives anErrorobject, so${err}there has the exact same footgun the rule exists to prevent (redundant"Error: message"prefix, or"[object Object]"for non-Error emissions) — yet it is invisible toisInlineRejectionHandler, which only matches.catch/.thencallees.Grounded live occurrence
actions/setup/js/mcp_server_core.cjs:1052:Here
erris the parameter of an inline arrow function passed as the listener for the'error'event onprocess.stdin(a stream/EventEmitter). This is a bare, unsafe interpolation of a realErrorobject, butno-caught-error-interpolationdoes not fire on it today.Ask
ArrowFunctionExpression/FunctionExpressionpassed as the listener argument to.on(...)/.once(...)/.addListener(...)when the event-name argument is the string literal"error"(mirroring the.catch/.thendetection style already used forisInlineRejectionHandler).(err) => ...signature for the'error'event) — do not widen to other event names.'error'event names, e.g..on('data', ...), are not flagged; named/hoisted listener functions may remain out of scope per the existing "inline" restriction, but should get an explicit valid test case documenting that as intentional) and invalid (emitter.on('error', err => \...${err}...`),.once('error', ...),.addListener('error', ...)`).mcp_server_core.cjs:1052is flagged (and gets theString(err)fallback suggestion, sincegetErrorMessageis not imported there) once the fix lands.Acceptance criteria
isCaughtErrorVariableDef(or an added helper) recognizes the first parameter of an inline callback passed to.on('error', ...)/.once('error', ...)/.addListener('error', ...).'error'-event exclusion.mcp_server_core.cjs:1052is confirmed to be a true positive after the fix (statically, since lint cannot be run live in this environment).