Skip to content

eslint-factory: no-unsafe-promise-catch-error-property guard is whole-callback, causing branch-order false negatives #42915

Description

@github-actions

Summary

no-unsafe-promise-catch-error-property tracks guards at the callback-frame level: any recognized guard (instanceof Error, getErrorMessage(err), or typeof err === 'object' && err !== null) sets frame.hasGuard = true, which suppresses every collected property access in that callback — regardless of whether the access is textually before the guard or in an unrelated branch. This is a false-negative soundness gap and directly parallels the sibling rule's open issue #42189.

Repro (false negative)

promise.catch(err => {
  logger.debug(err.message);      // UNGUARDED use — should be flagged
  if (err instanceof Error) {     // guard appears later, in a different branch
    rethrow(err);
  }
});

The MemberExpression visitor collects err.message; the later BinaryExpression visitor sets hasGuard = true; at :exit the frame is considered guarded and nothing is reported — even though the err.message access runs unconditionally, before any guard.

A getErrorMessage(err) call used only in a catch-of-a-catch branch produces the same masking effect.

Why it matters

The rule's value is catching unguarded error-property access. A flow-insensitive frame flag means a single guarded use anywhere in the callback disables the rule for the whole callback, letting genuinely unsafe accesses through. This is the mirror image of #42189 (same defect in the try/catch sibling no-unsafe-catch-error-property).

Suggested refinement

Options, in rough order of effort:

  1. Only credit a guard for accesses that are lexically dominated by it (e.g. inside the if (err instanceof Error) { ... } consequent, or textually after a top-level getErrorMessage(err)), rather than a callback-wide boolean.
  2. As a lighter first step, restrict the frame guard so it does not suppress accesses whose range precedes the guard node's range.

Whatever approach is chosen should stay consistent with the sibling rule so #42189 and this issue can share a fix.

Acceptance criteria

  • The repro above reports err.message as unguarded.
  • promise.catch(err => { if (err instanceof Error) log(err.message); }) (access dominated by the guard) still reports no diagnostic (no regression).
  • Test cases added covering guard-after-access and guard-in-sibling-branch.

Filed by ESLint Refiner. Sibling reference: #42189. Verified against no-unsafe-promise-catch-error-property.ts at time of writing.

Generated by 🤖 ESLint Refiner · 253.6 AIC · ⌖ 10.2 AIC · ⊞ 4.7K ·

  • expires on Jul 8, 2026, 10:35 PM UTC-08:00

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions