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:
- 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.
- 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
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 · ◷
Summary
no-unsafe-promise-catch-error-propertytracks guards at the callback-frame level: any recognized guard (instanceof Error,getErrorMessage(err), ortypeof err === 'object' && err !== null) setsframe.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)
The
MemberExpressionvisitor collectserr.message; the laterBinaryExpressionvisitor setshasGuard = true; at:exitthe frame is considered guarded and nothing is reported — even though theerr.messageaccess runs unconditionally, before any guard.A
getErrorMessage(err)call used only in acatch-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:
if (err instanceof Error) { ... }consequent, or textually after a top-levelgetErrorMessage(err)), rather than a callback-wide boolean.rangeprecedes the guard node'srange.Whatever approach is chosen should stay consistent with the sibling rule so #42189 and this issue can share a fix.
Acceptance criteria
err.messageas unguarded.promise.catch(err => { if (err instanceof Error) log(err.message); })(access dominated by the guard) still reports no diagnostic (no regression).Filed by ESLint Refiner. Sibling reference: #42189. Verified against
no-unsafe-promise-catch-error-property.tsat time of writing.