Skip to content

eslint-factory: add no-unsafe-catch-error-property rule#42057

Merged
pelikhan merged 2 commits into
mainfrom
copilot/eslint-mineradd-no-unsafe-catch-error-property
Jun 28, 2026
Merged

eslint-factory: add no-unsafe-catch-error-property rule#42057
pelikhan merged 2 commits into
mainfrom
copilot/eslint-mineradd-no-unsafe-catch-error-property

Conversation

Copilot AI commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Adds a new ESLint rule that flags direct access to .message, .stack, or .code on a caught error variable when no safe guard is present — catching the silent-undefined bug that occurs when non-Error values are thrown.

Rule design

  • Guard (suppresses all warnings for the block): getErrorMessage(err) call (preferred) or err instanceof Error check anywhere in the same catch block
  • Suggestion (.message only): auto-fix to getErrorMessage(err) from error_helpers.cjs
  • Bare catch {} and destructuring bindings are ignored; nested catch blocks are evaluated independently
// ❌ flagged — silent undefined if non-Error thrown
} catch (err) {
  core.setFailed(err.message);
}

// ✅ safe — getErrorMessage handles any thrown value
} catch (err) {
  core.setFailed(getErrorMessage(err));
}

Changes

  • eslint-factory/src/rules/no-unsafe-catch-error-property.ts — rule implementation using a CatchClause-stack visitor pattern; collects unsafe nodes during traversal and reports at CatchClause:exit after guard detection is complete
  • eslint-factory/src/rules/no-unsafe-catch-error-property.test.ts — 14 tests covering valid/invalid patterns, both guard types, nested try/catch independence, and suggestion output
  • eslint-factory/src/index.ts — registers the rule in the plugin
  • eslint-factory/eslint.config.cjs — enables at warn level

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add no-unsafe-catch-error-property rule for ESLint eslint-factory: add no-unsafe-catch-error-property rule Jun 28, 2026
Copilot AI requested a review from pelikhan June 28, 2026 10:30
@pelikhan pelikhan marked this pull request as ready for review June 28, 2026 10:36
Copilot AI review requested due to automatic review settings June 28, 2026 10:36
@pelikhan pelikhan merged commit ad1e387 into main Jun 28, 2026
9 checks passed
@pelikhan pelikhan deleted the copilot/eslint-mineradd-no-unsafe-catch-error-property branch June 28, 2026 10:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new custom ESLint rule to the eslint-factory plugin to prevent unsafe direct property access (.message, .stack, .code) on caught exceptions when the thrown value might not be an Error, with a suggestion to replace .message usage with getErrorMessage(err).

Changes:

  • Implements no-unsafe-catch-error-property with a catch-stack approach that defers reporting until CatchClause:exit once guard detection is complete.
  • Adds a dedicated Vitest/RuleTester suite covering valid/invalid patterns, guard handling, nested catch independence, and suggestion output.
  • Registers and enables the rule in the plugin index and the flat ESLint config at warn level.
Show a summary per file
File Description
eslint-factory/src/rules/no-unsafe-catch-error-property.ts New rule implementation for guarded vs unguarded caught-error property access, with suggestions for .message.
eslint-factory/src/rules/no-unsafe-catch-error-property.test.ts Test suite for rule correctness, nested catch behavior, and suggestion outputs.
eslint-factory/src/index.ts Registers the new rule in the exported plugin rules map.
eslint-factory/eslint.config.cjs Enables the new rule for .cjs files at warning level.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 5
  • Review effort level: Low

Comment on lines +83 to +92
// Detect catchVar instanceof Error — also accepted as a safe guard
BinaryExpression(node) {
if (catchStack.length === 0) return;
const top = catchStack[catchStack.length - 1];
if (!top || top.hasGuard || !top.varName) return;

if (node.operator === "instanceof" && node.left.type === AST_NODE_TYPES.Identifier && node.left.name === top.varName) {
top.hasGuard = true;
}
},
Comment on lines +18 to +20
docs: {
description: "Disallow direct access to .message, .stack, or .code on a caught error variable without a getErrorMessage guard",
},
Comment on lines +22 to +25
messages: {
unsafeProperty: "Direct access to .{{prop}} on caught error '{{errorVar}}' is unsafe — the thrown value may not be an Error instance. Use getErrorMessage({{errorVar}}) from error_helpers.cjs instead.",
useGetErrorMessage: "Replace with getErrorMessage({{errorVar}}) from error_helpers.cjs for safe error message extraction.",
},
Comment on lines +38 to +60
it("valid: instanceof Error guard suppresses all warnings in the catch block", () => {
cjsRuleTester.run("no-unsafe-catch-error-property", noUnsafeCatchErrorPropertyRule, {
valid: [`try { f(); } catch (err) { core.setFailed(err instanceof Error ? err.message : String(err)); }`, `try { f(); } catch (err) { if (err instanceof Error) { console.log(err.stack); } }`],
invalid: [],
});
});

it("valid: property access on a different variable is not flagged", () => {
cjsRuleTester.run("no-unsafe-catch-error-property", noUnsafeCatchErrorPropertyRule, {
valid: [`try { f(); } catch (err) { console.log(otherObj.message); }`, `try { f(); } catch (err) { const e = new Error(); console.log(e.message); }`],
invalid: [],
});
});

it("valid: computed property access on catch variable is not flagged", () => {
cjsRuleTester.run("no-unsafe-catch-error-property", noUnsafeCatchErrorPropertyRule, {
valid: [`try { f(); } catch (err) { console.log(err["message"]); }`, `try { f(); } catch (err) { const prop = "message"; console.log(err[prop]); }`],
invalid: [],
});
});

it("invalid: err.message without guard is flagged", () => {
cjsRuleTester.run("no-unsafe-catch-error-property", noUnsafeCatchErrorPropertyRule, {
});
});

it("valid: nested try/catch — inner guard does not affect the outer catch block", () => {
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants