Summary
no-unsafe-promise-catch-error-property produces false positives on a common defensive error-formatting idiom that short-circuits on truthiness and falls back to String(err). This is a live false positive, not hypothetical.
Live example
actions/setup/js/apply_samples.cjs:650-652:
main().catch(err => {
core.setFailed(err && err.stack ? err.stack : String(err));
});
The rule collects err.stack (it appears twice) and, because it recognizes only instanceof Error, getErrorMessage(err), and typeof err === 'object' && err !== null as guards, reports two unsafeProperty diagnostics here.
Why it's a false positive
The access is already safe against non-Error rejections:
err && err.stack proves err is truthy and has a truthy stack before the value is used.
- Any non-Error / falsy-stack rejection (string, number,
null, plain object without stack) short-circuits to String(err).
In every case the expression neither throws nor mis-formats, so the rule's premise — "the rejection value may not be an Error instance" — does not apply. Flagging it pushes contributors toward a churny rewrite of correct code (or scatters eslint-disable comments).
Suggested refinement
Treat a property access as guarded when it is truthiness-gated by the same variable/member, e.g.:
- LogicalExpression
err && err.<prop> (the && left operand proves err truthy), and/or
- a ConditionalExpression whose test proves
err/err.<prop> truthy and whose alternate is a String(err) coercion.
At minimum, recognizing the x && x.<prop> short-circuit (mirroring the existing non-null-guard logic in isNonNullGuardCheck) would clear this class of FP. The sibling rule no-unsafe-catch-error-property shares the same guard model and would benefit from the same recognition.
Acceptance criteria
Filed by ESLint Refiner. Verified against eslint-factory/src/rules/no-unsafe-promise-catch-error-property.ts and actions/setup/js/apply_samples.cjs at time of writing.
Generated by 🤖 ESLint Refiner · 253.6 AIC · ⌖ 10.2 AIC · ⊞ 4.7K · ◷
Summary
no-unsafe-promise-catch-error-propertyproduces false positives on a common defensive error-formatting idiom that short-circuits on truthiness and falls back toString(err). This is a live false positive, not hypothetical.Live example
actions/setup/js/apply_samples.cjs:650-652:The rule collects
err.stack(it appears twice) and, because it recognizes onlyinstanceof Error,getErrorMessage(err), andtypeof err === 'object' && err !== nullas guards, reports twounsafePropertydiagnostics here.Why it's a false positive
The access is already safe against non-Error rejections:
err && err.stackproveserris truthy and has a truthystackbefore the value is used.null, plain object withoutstack) short-circuits toString(err).In every case the expression neither throws nor mis-formats, so the rule's premise — "the rejection value may not be an Error instance" — does not apply. Flagging it pushes contributors toward a churny rewrite of correct code (or scatters
eslint-disablecomments).Suggested refinement
Treat a property access as guarded when it is truthiness-gated by the same variable/member, e.g.:
err && err.<prop>(the&&left operand proveserrtruthy), and/orerr/err.<prop>truthy and whose alternate is aString(err)coercion.At minimum, recognizing the
x && x.<prop>short-circuit (mirroring the existing non-null-guard logic inisNonNullGuardCheck) would clear this class of FP. The sibling ruleno-unsafe-catch-error-propertyshares the same guard model and would benefit from the same recognition.Acceptance criteria
promise.catch(err => core.setFailed(err && err.stack ? err.stack : String(err)))produces no diagnostic.promise.catch(err => err && err.message ? err.message : 'unknown')produces no diagnostic.promise.catch(err => log(err.stack))) is still flagged (no regression).no-unsafe-promise-catch-error-property.test.tscovering the truthiness-gated and String-fallback forms.Filed by ESLint Refiner. Verified against
eslint-factory/src/rules/no-unsafe-promise-catch-error-property.tsandactions/setup/js/apply_samples.cjsat time of writing.