From 8ab86db7bc4e1b76c99f9cf50058594fb9c579ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 07:56:40 +0000 Subject: [PATCH 1/4] Initial plan From b86b39672ef17e627789b8be11504561aa8b8384 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:08:32 +0000 Subject: [PATCH 2/4] Fix catch-alias detection in require-error-cause-in-rethrow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../require-error-cause-in-rethrow.test.ts | 22 +++++ .../rules/require-error-cause-in-rethrow.ts | 82 ++++++++++++++----- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts b/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts index cae5eaee11a..c40a8b0da6b 100644 --- a/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts +++ b/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts @@ -41,6 +41,12 @@ describe("require-error-cause-in-rethrow", () => { `try { doSomething(); } catch (err) { const e = new Error(\`msg: \${getErrorMessage(err)}\`); log(e); }`, // Existing cause property with wrapped expression should not be flagged. `try { doSomething(); } catch (err) { throw new Error("Failed: " + getErrorMessage(err), { cause: new Error(err.message), code: 500 }); }`, + // Alias initialized from unrelated value should not be treated as catch alias. + `try { doSomething(); } catch (deleteError) { const err = getFallbackError(); throw new Error(\`Failed to delete: \${String(err)}\`); }`, + // Reassigned alias is intentionally not tracked (too complex to follow safely). + `try { doSomething(); } catch (deleteError) { let err = deleteError; err = normalizeError(err); throw new Error(\`Failed to delete: \${String(err)}\`); }`, + // Alias + cause should pass. + `try { doSomething(); } catch (deleteError) { const err = deleteError; throw new Error(\`Failed to delete existing remote branch: \${message || String(err)}\`, { cause: err }); }`, ], invalid: [], }); @@ -143,6 +149,22 @@ describe("require-error-cause-in-rethrow", () => { }, ], }, + { + // Aliased catch var + logical expression branch should be detected. + code: `try { doSomething(); } catch (deleteError) { const err = deleteError; throw new Error(\`Failed to delete existing remote branch: \${message || String(err)}\`); }`, + errors: [ + { + messageId: "missingCause", + data: { catchVar: "err" }, + suggestions: [ + { + messageId: "addCause", + output: `try { doSomething(); } catch (deleteError) { const err = deleteError; throw new Error(` + `\`Failed to delete existing remote branch: \${message || String(err)}\`, { cause: err }); }`, + }, + ], + }, + ], + }, ], }); }); diff --git a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts index 7fc55684927..599ea4557ad 100644 --- a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts +++ b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts @@ -4,6 +4,7 @@ const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh interface CatchFrame { varName: string; + aliases: Set; } /** @@ -20,39 +21,74 @@ function getErrorMessageArg(node: TSESTree.CallExpression): string | null { } /** - * Returns true when the expression tree references the given identifier name. - * Used to detect whether the catch variable appears somewhere in the Error - * message (directly or via getErrorMessage(catchVar)). + * Returns the first identifier name from `trackedNames` referenced in `node`, + * or null if none are found. Used to detect whether the Error message + * references the caught variable (or a direct alias of it). */ -function expressionReferencesCatchVar(node: TSESTree.Expression, varName: string): boolean { - if (node.type === AST_NODE_TYPES.Identifier && node.name === varName) return true; +function findTrackedIdentifierReference(node: TSESTree.Expression, trackedNames: ReadonlySet): string | null { + if (node.type === AST_NODE_TYPES.Identifier && trackedNames.has(node.name)) return node.name; if (node.type === AST_NODE_TYPES.CallExpression) { const gem = getErrorMessageArg(node); - if (gem === varName) return true; + if (gem && trackedNames.has(gem)) return gem; // Recurse into all arguments for (const arg of node.arguments) { - if (arg.type !== "SpreadElement" && expressionReferencesCatchVar(arg, varName)) return true; + if (arg.type === "SpreadElement") continue; + const match = findTrackedIdentifierReference(arg, trackedNames); + if (match) return match; } } if (node.type === AST_NODE_TYPES.TemplateLiteral) { for (const expr of node.expressions) { - if (expressionReferencesCatchVar(expr, varName)) return true; + const match = findTrackedIdentifierReference(expr, trackedNames); + if (match) return match; } } if (node.type === AST_NODE_TYPES.BinaryExpression) { const left = node.left; const right = node.right; - const leftResult = left.type !== AST_NODE_TYPES.PrivateIdentifier && expressionReferencesCatchVar(left, varName); - return leftResult || expressionReferencesCatchVar(right, varName); + if (left.type !== AST_NODE_TYPES.PrivateIdentifier) { + const leftMatch = findTrackedIdentifierReference(left, trackedNames); + if (leftMatch) return leftMatch; + } + return findTrackedIdentifierReference(right, trackedNames); + } + if (node.type === AST_NODE_TYPES.LogicalExpression) { + const leftMatch = findTrackedIdentifierReference(node.left, trackedNames); + if (leftMatch) return leftMatch; + return findTrackedIdentifierReference(node.right, trackedNames); + } + if (node.type === AST_NODE_TYPES.ConditionalExpression) { + const testMatch = findTrackedIdentifierReference(node.test, trackedNames); + if (testMatch) return testMatch; + const consequentMatch = findTrackedIdentifierReference(node.consequent, trackedNames); + if (consequentMatch) return consequentMatch; + return findTrackedIdentifierReference(node.alternate, trackedNames); } if (node.type === AST_NODE_TYPES.MemberExpression) { - if (node.object.type !== AST_NODE_TYPES.Super && expressionReferencesCatchVar(node.object, varName)) return true; + if (node.object.type !== AST_NODE_TYPES.Super) { + const objectMatch = findTrackedIdentifierReference(node.object, trackedNames); + if (objectMatch) return objectMatch; + } if (node.computed) { - return expressionReferencesCatchVar(node.property as TSESTree.Expression, varName); + return findTrackedIdentifierReference(node.property as TSESTree.Expression, trackedNames); + } + return null; + } + return null; +} + +function collectCatchAliases(catchBody: TSESTree.BlockStatement, catchVarName: string): Set { + const aliases = new Set(); + for (const statement of catchBody.body) { + if (statement.type !== AST_NODE_TYPES.VariableDeclaration || statement.kind !== "const") continue; + for (const decl of statement.declarations) { + if (decl.id.type !== AST_NODE_TYPES.Identifier) continue; + if (!decl.init || decl.init.type !== AST_NODE_TYPES.Identifier) continue; + if (decl.init.name !== catchVarName) continue; + aliases.add(decl.id.name); } - return false; } - return false; + return aliases; } /** @@ -131,10 +167,10 @@ export const requireErrorCauseInRethrowRule = createRule({ const param = node.param; if (!param || param.type !== AST_NODE_TYPES.Identifier) { // Bare catch {} or destructured — push empty sentinel so CatchClause:exit still pops - catchStack.push({ varName: "" }); + catchStack.push({ varName: "", aliases: new Set() }); return; } - catchStack.push({ varName: param.name }); + catchStack.push({ varName: param.name, aliases: collectCatchAliases(node.body, param.name) }); }, "CatchClause:exit"() { @@ -153,6 +189,7 @@ export const requireErrorCauseInRethrowRule = createRule({ if (!isInsideCatchBody(node)) return; const catchVarName = frame.varName; + const trackedNames = new Set([catchVarName, ...frame.aliases]); const args = node.arguments; // Must have at least a message argument that references the catch variable. @@ -160,7 +197,8 @@ export const requireErrorCauseInRethrowRule = createRule({ const msgArg = args[0]; if (msgArg.type === "SpreadElement") return; - if (!expressionReferencesCatchVar(msgArg, catchVarName)) return; + const causeIdentifier = findTrackedIdentifierReference(msgArg, trackedNames); + if (!causeIdentifier) return; // If a second argument exists, check that it contains { cause: catchVar }. if (args.length >= 2) { @@ -173,11 +211,11 @@ export const requireErrorCauseInRethrowRule = createRule({ context.report({ node, messageId: "missingCause", - data: { catchVar: catchVarName }, + data: { catchVar: causeIdentifier }, suggest: [ { messageId: "addCause" as const, - data: { catchVar: catchVarName }, + data: { catchVar: causeIdentifier }, fix(fixer) { // If there's already a second arg, replace it with an object that adds cause. if (args.length >= 2) { @@ -186,17 +224,17 @@ export const requireErrorCauseInRethrowRule = createRule({ if (secondArg.type === AST_NODE_TYPES.ObjectExpression) { // Add cause property to the existing object if (secondArg.properties.length === 0) { - return fixer.replaceText(secondArg, `{ cause: ${catchVarName} }`); + return fixer.replaceText(secondArg, `{ cause: ${causeIdentifier} }`); } const firstProp = secondArg.properties[0]; - return fixer.insertTextBefore(firstProp, `cause: ${catchVarName}, `); + return fixer.insertTextBefore(firstProp, `cause: ${causeIdentifier}, `); } return null; } // No second argument — append `, { cause: catchVar }` before closing paren const lastArg = args[args.length - 1]; if (!lastArg || lastArg.type === "SpreadElement") return null; - return fixer.insertTextAfter(lastArg, `, { cause: ${catchVarName} }`); + return fixer.insertTextAfter(lastArg, `, { cause: ${causeIdentifier} }`); }, }, ], From cbd30f17595fc1c8f38ec9b3af6d4463c298dd16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:11:39 +0000 Subject: [PATCH 3/4] Harden catch alias collection in rethrow rule Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- eslint-factory/src/rules/require-error-cause-in-rethrow.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts index 599ea4557ad..23ba29e04b7 100644 --- a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts +++ b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts @@ -85,6 +85,7 @@ function collectCatchAliases(catchBody: TSESTree.BlockStatement, catchVarName: s if (decl.id.type !== AST_NODE_TYPES.Identifier) continue; if (!decl.init || decl.init.type !== AST_NODE_TYPES.Identifier) continue; if (decl.init.name !== catchVarName) continue; + if (decl.id.name === catchVarName) continue; aliases.add(decl.id.name); } } From df8761cf68c460e2bde45b9f7189521543b767fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:59:11 +0000 Subject: [PATCH 4/4] fix: split catchVar message placeholder, add scope-safe alias binding verification - Split `{{catchVar}}` into `{{catchParam}}` (catch clause name) and `{{refName}}` (the referenced identifier), so diagnostics like "inside catch (deleteError)" remain accurate when the reference is an alias `err`. - Track alias `VariableDeclarator` nodes (Map instead of Set) and store `catchNode: CatchClause` in each frame so bindings can be verified. - `verifyIdentifier` walks up the scope chain via `sourceCode.getScope()` and confirms the resolved `Variable.defs` points to the recorded catch-clause param or alias declarator, preventing false positives from shadowed names in inner blocks. - Update tests: use `{ catchParam, refName }` data keys; add valid case for shadowed alias in nested block that must not be flagged. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../require-error-cause-in-rethrow.test.ts | 16 ++-- .../rules/require-error-cause-in-rethrow.ts | 87 ++++++++++++------- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts b/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts index c40a8b0da6b..b6a162fcba4 100644 --- a/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts +++ b/eslint-factory/src/rules/require-error-cause-in-rethrow.test.ts @@ -47,6 +47,8 @@ describe("require-error-cause-in-rethrow", () => { `try { doSomething(); } catch (deleteError) { let err = deleteError; err = normalizeError(err); throw new Error(\`Failed to delete: \${String(err)}\`); }`, // Alias + cause should pass. `try { doSomething(); } catch (deleteError) { const err = deleteError; throw new Error(\`Failed to delete existing remote branch: \${message || String(err)}\`, { cause: err }); }`, + // Alias name shadowed in nested block — inner `err` is unrelated, should not flag. + `try { doSomething(); } catch (deleteError) { const err = deleteError; { const err = getFallbackError(); throw new Error(String(err)); } }`, ], invalid: [], }); @@ -61,7 +63,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "err", refName: "err" }, suggestions: [ { messageId: "addCause", @@ -76,7 +78,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "err", refName: "err" }, suggestions: [ { messageId: "addCause", @@ -91,7 +93,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "error" }, + data: { catchParam: "error", refName: "error" }, suggestions: [ { messageId: "addCause", @@ -107,7 +109,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "err", refName: "err" }, suggestions: [ { messageId: "addCause", @@ -123,7 +125,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "err", refName: "err" }, suggestions: [ { messageId: "addCause", @@ -139,7 +141,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "err", refName: "err" }, suggestions: [ { messageId: "addCause", @@ -155,7 +157,7 @@ describe("require-error-cause-in-rethrow", () => { errors: [ { messageId: "missingCause", - data: { catchVar: "err" }, + data: { catchParam: "deleteError", refName: "err" }, suggestions: [ { messageId: "addCause", diff --git a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts index 23ba29e04b7..02d83a01434 100644 --- a/eslint-factory/src/rules/require-error-cause-in-rethrow.ts +++ b/eslint-factory/src/rules/require-error-cause-in-rethrow.ts @@ -1,10 +1,11 @@ -import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils"; const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh-aw/tree/main/eslint-factory#${name}`); interface CatchFrame { varName: string; - aliases: Set; + catchNode: TSESTree.CatchClause | null; + aliases: Map; } /** @@ -24,22 +25,30 @@ function getErrorMessageArg(node: TSESTree.CallExpression): string | null { * Returns the first identifier name from `trackedNames` referenced in `node`, * or null if none are found. Used to detect whether the Error message * references the caught variable (or a direct alias of it). + * `verifyIdentifier` is called on each name-matched identifier node to confirm + * the binding resolves to the expected catch variable or alias (scope safety). */ -function findTrackedIdentifierReference(node: TSESTree.Expression, trackedNames: ReadonlySet): string | null { - if (node.type === AST_NODE_TYPES.Identifier && trackedNames.has(node.name)) return node.name; +function findTrackedIdentifierReference(node: TSESTree.Expression, trackedNames: ReadonlySet, verifyIdentifier: (name: string, identNode: TSESTree.Identifier) => boolean): string | null { + if (node.type === AST_NODE_TYPES.Identifier && trackedNames.has(node.name)) { + return verifyIdentifier(node.name, node) ? node.name : null; + } if (node.type === AST_NODE_TYPES.CallExpression) { const gem = getErrorMessageArg(node); - if (gem && trackedNames.has(gem)) return gem; + if (gem && trackedNames.has(gem)) { + // getErrorMessageArg returns the name; verify via the actual argument identifier node + const firstArg = node.arguments[0]; + if (firstArg && firstArg.type === AST_NODE_TYPES.Identifier && verifyIdentifier(gem, firstArg)) return gem; + } // Recurse into all arguments for (const arg of node.arguments) { if (arg.type === "SpreadElement") continue; - const match = findTrackedIdentifierReference(arg, trackedNames); + const match = findTrackedIdentifierReference(arg, trackedNames, verifyIdentifier); if (match) return match; } } if (node.type === AST_NODE_TYPES.TemplateLiteral) { for (const expr of node.expressions) { - const match = findTrackedIdentifierReference(expr, trackedNames); + const match = findTrackedIdentifierReference(expr, trackedNames, verifyIdentifier); if (match) return match; } } @@ -47,38 +56,38 @@ function findTrackedIdentifierReference(node: TSESTree.Expression, trackedNames: const left = node.left; const right = node.right; if (left.type !== AST_NODE_TYPES.PrivateIdentifier) { - const leftMatch = findTrackedIdentifierReference(left, trackedNames); + const leftMatch = findTrackedIdentifierReference(left, trackedNames, verifyIdentifier); if (leftMatch) return leftMatch; } - return findTrackedIdentifierReference(right, trackedNames); + return findTrackedIdentifierReference(right, trackedNames, verifyIdentifier); } if (node.type === AST_NODE_TYPES.LogicalExpression) { - const leftMatch = findTrackedIdentifierReference(node.left, trackedNames); + const leftMatch = findTrackedIdentifierReference(node.left, trackedNames, verifyIdentifier); if (leftMatch) return leftMatch; - return findTrackedIdentifierReference(node.right, trackedNames); + return findTrackedIdentifierReference(node.right, trackedNames, verifyIdentifier); } if (node.type === AST_NODE_TYPES.ConditionalExpression) { - const testMatch = findTrackedIdentifierReference(node.test, trackedNames); + const testMatch = findTrackedIdentifierReference(node.test, trackedNames, verifyIdentifier); if (testMatch) return testMatch; - const consequentMatch = findTrackedIdentifierReference(node.consequent, trackedNames); + const consequentMatch = findTrackedIdentifierReference(node.consequent, trackedNames, verifyIdentifier); if (consequentMatch) return consequentMatch; - return findTrackedIdentifierReference(node.alternate, trackedNames); + return findTrackedIdentifierReference(node.alternate, trackedNames, verifyIdentifier); } if (node.type === AST_NODE_TYPES.MemberExpression) { if (node.object.type !== AST_NODE_TYPES.Super) { - const objectMatch = findTrackedIdentifierReference(node.object, trackedNames); + const objectMatch = findTrackedIdentifierReference(node.object, trackedNames, verifyIdentifier); if (objectMatch) return objectMatch; } if (node.computed) { - return findTrackedIdentifierReference(node.property as TSESTree.Expression, trackedNames); + return findTrackedIdentifierReference(node.property as TSESTree.Expression, trackedNames, verifyIdentifier); } return null; } return null; } -function collectCatchAliases(catchBody: TSESTree.BlockStatement, catchVarName: string): Set { - const aliases = new Set(); +function collectCatchAliases(catchBody: TSESTree.BlockStatement, catchVarName: string): Map { + const aliases = new Map(); for (const statement of catchBody.body) { if (statement.type !== AST_NODE_TYPES.VariableDeclaration || statement.kind !== "const") continue; for (const decl of statement.declarations) { @@ -86,7 +95,7 @@ function collectCatchAliases(catchBody: TSESTree.BlockStatement, catchVarName: s if (!decl.init || decl.init.type !== AST_NODE_TYPES.Identifier) continue; if (decl.init.name !== catchVarName) continue; if (decl.id.name === catchVarName) continue; - aliases.add(decl.id.name); + aliases.set(decl.id.name, decl); } } return aliases; @@ -123,8 +132,8 @@ export const requireErrorCauseInRethrowRule = createRule({ }, schema: [], messages: { - missingCause: "`new Error(...)` inside catch ({{catchVar}}) references {{catchVar}} but omits `{ cause: {{catchVar}} }` — the original stack trace will be lost. Add `{ cause: {{catchVar}} }` as the second argument.", - addCause: "Add `{ cause: {{catchVar}} }` as the second argument to preserve the original error chain.", + missingCause: "`new Error(...)` inside catch ({{catchParam}}) references {{refName}} but omits `{ cause: {{refName}} }` — the original stack trace will be lost. Add `{ cause: {{refName}} }` as the second argument.", + addCause: "Add `{ cause: {{refName}} }` as the second argument to preserve the original error chain.", }, }, defaultOptions: [], @@ -168,10 +177,10 @@ export const requireErrorCauseInRethrowRule = createRule({ const param = node.param; if (!param || param.type !== AST_NODE_TYPES.Identifier) { // Bare catch {} or destructured — push empty sentinel so CatchClause:exit still pops - catchStack.push({ varName: "", aliases: new Set() }); + catchStack.push({ varName: "", catchNode: null, aliases: new Map() }); return; } - catchStack.push({ varName: param.name, aliases: collectCatchAliases(node.body, param.name) }); + catchStack.push({ varName: param.name, catchNode: node, aliases: collectCatchAliases(node.body, param.name) }); }, "CatchClause:exit"() { @@ -186,19 +195,39 @@ export const requireErrorCauseInRethrowRule = createRule({ if (callee.type !== AST_NODE_TYPES.Identifier || callee.name !== "Error") return; const frame = innermostCatch(); - if (!frame || !frame.varName) return; + if (!frame || !frame.varName || !frame.catchNode) return; if (!isInsideCatchBody(node)) return; const catchVarName = frame.varName; - const trackedNames = new Set([catchVarName, ...frame.aliases]); + const catchNode = frame.catchNode; + const trackedNames = new Set([catchVarName, ...frame.aliases.keys()]); const args = node.arguments; + // Build a scope-aware verifier: confirm the identifier actually resolves to the + // catch parameter or a recorded alias, not a shadowed variable in a nested block. + const verifyIdentifier = (name: string, identNode: TSESTree.Identifier): boolean => { + let currentScope: TSESLint.Scope.Scope | null = sourceCode.getScope(identNode); + while (currentScope !== null) { + const variable = currentScope.set.get(name); + if (variable !== undefined) { + if (name === catchVarName) { + return variable.defs.some(def => def.type === TSESLint.Scope.DefinitionType.CatchClause && def.node === catchNode); + } + const aliasDeclarator = frame.aliases.get(name); + if (aliasDeclarator === undefined) return false; + return variable.defs.some(def => def.type === TSESLint.Scope.DefinitionType.Variable && def.node === aliasDeclarator); + } + currentScope = currentScope.upper; + } + return false; + }; + // Must have at least a message argument that references the catch variable. if (args.length === 0) return; const msgArg = args[0]; if (msgArg.type === "SpreadElement") return; - const causeIdentifier = findTrackedIdentifierReference(msgArg, trackedNames); + const causeIdentifier = findTrackedIdentifierReference(msgArg, trackedNames, verifyIdentifier); if (!causeIdentifier) return; // If a second argument exists, check that it contains { cause: catchVar }. @@ -212,11 +241,11 @@ export const requireErrorCauseInRethrowRule = createRule({ context.report({ node, messageId: "missingCause", - data: { catchVar: causeIdentifier }, + data: { catchParam: catchVarName, refName: causeIdentifier }, suggest: [ { messageId: "addCause" as const, - data: { catchVar: causeIdentifier }, + data: { refName: causeIdentifier }, fix(fixer) { // If there's already a second arg, replace it with an object that adds cause. if (args.length >= 2) { @@ -232,7 +261,7 @@ export const requireErrorCauseInRethrowRule = createRule({ } return null; } - // No second argument — append `, { cause: catchVar }` before closing paren + // No second argument — append `, { cause: causeIdentifier }` before closing paren const lastArg = args[args.length - 1]; if (!lastArg || lastArg.type === "SpreadElement") return null; return fixer.insertTextAfter(lastArg, `, { cause: ${causeIdentifier} }`);