-
Notifications
You must be signed in to change notification settings - Fork 482
fix(eslint-factory): extend no-caught-error-interpolation to cover EventEmitter .on('error') handlers #49768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,24 @@ function isInlineRejectionHandler(node: TSESTree.ArrowFunctionExpression | TSEST | |
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true when the function node is an inline listener passed as the | ||
| * second argument to an EventEmitter-style `.on('error', fn)`, | ||
| * `.once('error', fn)`, or `.addListener('error', fn)` call. | ||
| */ | ||
| function isInlineEventErrorHandler(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): boolean { | ||
| const parent = node.parent; | ||
| if (!parent || parent.type !== AST_NODE_TYPES.CallExpression) return false; | ||
| const callee = parent.callee; | ||
| if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed) return false; | ||
| const prop = callee.property; | ||
| if (prop.type !== AST_NODE_TYPES.Identifier) return false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This heuristic matches any object's 💡 Why this matters and how to narrow it
Concretely, this will now force $(elem).on('error', (event) => log(`img failed: ${event}`)); // event is not an ErrorConsider either:
|
||
| if (prop.name !== "on" && prop.name !== "once" && prop.name !== "addListener") return false; | ||
| if (parent.arguments[1] !== node) return false; | ||
| const eventArg = parent.arguments[0]; | ||
| return eventArg?.type === AST_NODE_TYPES.Literal && eventArg.value === "error"; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true when `node` is a bare `Identifier` expression — no member | ||
| * access, no call, no unary/binary operation, no nullish coercion. Used to | ||
|
|
@@ -44,13 +62,20 @@ function isCaughtErrorVariableDef(def: TSESLint.Scope.Definition): boolean { | |
| } | ||
|
|
||
| // Inline rejection handler parameter (.catch(err => ...) / .then(_, err => ...)) | ||
| // or inline EventEmitter 'error' event listener (.on('error', err => ...) etc.) | ||
| // def.node is the function node for Parameter definitions | ||
| if (def.type === "Parameter") { | ||
| const fn = def.node as TSESTree.Node; | ||
| if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression && fn.type !== AST_NODE_TYPES.FunctionExpression) { | ||
| return false; | ||
| } | ||
| return isInlineRejectionHandler(fn as TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression); | ||
| const fnNode = fn as TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression; | ||
| if (isInlineRejectionHandler(fnNode)) return true; | ||
| if (isInlineEventErrorHandler(fnNode)) { | ||
| // Only the first parameter receives the error object from an 'error' event | ||
| return fnNode.params[0] === def.name; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
💡 Why this mattersWhen the first parameter is a destructuring pattern ( Add a test case such as: emitter.on('error', ({message}) => log(`err: ${message}`));to confirm/document whether destructured members should be in scope for this rule, and to guard against regressions. |
||
| } | ||
| return false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The 💡 Suggested testAdd a valid case to confirm that extra params in an error handler are not flagged: it("valid: second param in .on('error', ...) is not flagged", () => {
cjsRuleTester.run("no-caught-error-interpolation", noCaughtErrorInterpolationRule, {
valid: [
`emitter.on('error', (err, context) => { log(\`ctx: \${context}\`); });`,
],
invalid: [],
});
});Without this, a future refactor could silently drop the guard and start over-flagging @copilot please address this. |
||
| } | ||
|
|
||
| return false; | ||
|
|
@@ -67,7 +92,8 @@ export const noCaughtErrorInterpolationRule = createRule({ | |
| "For Error objects this produces the redundant 'Error: message' prefix; for non-Error throws (plain objects, strings, etc.) " + | ||
| "it silently produces '[object Object]' or another useless string. " + | ||
| "Use getErrorMessage(err) for consistent, safe formatting, or String(err) when getErrorMessage is unavailable. " + | ||
| "Detected scopes: try/catch bindings, .catch(fn) inline callbacks, and .then(onFulfilled, onRejected) inline rejection handlers.", | ||
| "Detected scopes: try/catch bindings, .catch(fn) inline callbacks, .then(onFulfilled, onRejected) inline rejection handlers, " + | ||
| "and inline EventEmitter 'error' event listeners (.on('error', fn) / .once('error', fn) / .addListener('error', fn)).", | ||
| }, | ||
| schema: [], | ||
| messages: { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd] The test for the named/hoisted function reference passes only because the rule never sees the inner template literal (it's inside a separate function declaration, not inside the
.oncallback). Consider adding a case where aFunctionExpression(not arrow) is used inline to confirm both callback forms are covered:💡 Suggested test for inline FunctionExpression
The implementation handles
FunctionExpressionvia the union type, but no test exercises it.@copilot please address this.