Make no-unsafe-catch-error-property flow-sensitive to branch dominance and source order#47534
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
Great work! 🎉 This PR refines the guard flow sensitivity analysis in the The changes are solid:
The PR looks ready for merge!
|
There was a problem hiding this comment.
Pull request overview
Refines catch-error property validation to evaluate guards per access using branch dominance and source order.
Changes:
- Adds branch-aware guard detection.
- Makes
getErrorMessagesuppression order- and block-sensitive. - Adds sibling-branch and guard-order regression tests.
Show a summary per file
| File | Description |
|---|---|
eslint-factory/src/rules/no-unsafe-catch-error-property.ts |
Implements per-access guard evaluation. |
eslint-factory/src/rules/no-unsafe-catch-error-property.test.ts |
Adds flow-sensitivity regression tests. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (2)
eslint-factory/src/rules/no-unsafe-catch-error-property.ts:111
- Nested
&&guards lose combined narrowing evidence because each ancestor's left operand is evaluated independently. For example,typeof err === "object" && (err !== null && err.status)is safe, but the inner guard has only the non-null check and the outer guard has only thetypeofcheck, so this now reportserr.status. Accumulate all dominating left operands before evaluating the combined guard.
if (ancestor.type === AST_NODE_TYPES.LogicalExpression && ancestor.operator === "&&" && ancestor.right === current) {
if (isTruthyBranchGuarded(sourceCode, ancestor.left, varName, ancestor)) return true;
eslint-factory/src/rules/no-unsafe-catch-error-property.ts:132
- Source order of block entries does not imply that the call executes. In
if (flag) getErrorMessage(err); console.log(err.stack);, the call maps to the earlierIfStatemententry, so this returns true and suppresseserr.stackeven whenflagis false. Require the call's execution path to dominate the access, excluding conditional, loop, short-circuit, and deferred contexts.
if (callEntryIndex < nodeEntryIndex) return true;
if (callEntryIndex === nodeEntryIndex && callNode.range[1] <= node.range[0]) return true;
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Medium
| if (ancestor.type === AST_NODE_TYPES.IfStatement && ancestor.consequent === current) { | ||
| if (isTruthyBranchGuarded(sourceCode, ancestor.test, varName, ancestor)) return true; |
|
@copilot please run the Run: https://github.com/github/gh-aw/actions/runs/29998171888
|
…rns) Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Addressed in commit Added
The new check is wired into |
|
🎉 This pull request is included in a new release. Release: |
no-unsafe-catch-error-propertyused a catch-frame boolean guard, so any recognized guard could suppress all unsafe accesses in the catch block, including sibling branches and earlier accesses. This produced false negatives whenever guard recognition was not control-flow aligned with the reported access.Rule behavior: replace frame-wide suppression with per-access guard evaluation
hasGuard/hasNonNullGuardframe state and evaluate each collected unsafe member access independently atCatchClause:exit.ifconsequent, conditional consequent,&&RHS) that narrows the caught variable.Guard semantics preserved, but scoped correctly
instanceof Errorcontinues to guard accesses inside dominated branches.typeof err === "object"still requires non-null evidence (err,err != null, or priorif (!err) return;in the same block).getErrorMessage(err)is now order/scope-aware: it only suppresses accesses that occur later in source order within the same enclosing block.Regression coverage added