fix(eslint-factory): recognize single-assignment alias of spawn error as valid guard#46199
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
… as valid guard Fixes false positive in require-spawnsync-error-check when result.error (or a destructured error binding) is assigned to an intermediate variable that is then tested in a guard condition. Adds isAssignedToGuardedAlias() which detects the pattern: const e = result.error; // or: const e = error; (destructured) if (e) throw e; Updates both the simple-identifier and object-pattern analysis branches to also accept this one-level alias indirection. Updated rule description documents the new alias support and notes that passing result to a helper function remains out of scope. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #46199 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Test Quality Sentinel completed test quality analysis. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
There was a problem hiding this comment.
Pull request overview
Adds one-level guarded aliases for spawnSync error checks.
Changes:
- Adds alias resolution for direct and destructured errors.
- Adds valid and invalid alias tests.
- Changes the generated auto-upgrade schedule.
Show a summary per file
| File | Description |
|---|---|
require-spawnsync-error-check.ts |
Implements guarded alias detection. |
require-spawnsync-error-check.test.ts |
Tests alias handling. |
agentic-auto-upgrade.yml |
Changes the weekly schedule. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Medium
| const variable = findVariableByName(sourceCode, node, idNode.name); | ||
| if (!variable) return false; | ||
|
|
||
| return variable.references.some(ref => ref.identifier.type === AST_NODE_TYPES.Identifier && isGuardingErrorUsage(ref.identifier)); |
There was a problem hiding this comment.
Reverted in the latest commit. The cron schedule is back to "21 3 * * 5" (the value generated from the repository slug). This change should not have been part of this PR.
There was a problem hiding this comment.
The alias-guard fix is correct and well-tested. The isAssignedToGuardedAlias helper properly restricts to single-identifier VariableDeclarator init, preventing false-negatives for deeper alias chains. Both the simple-identifier and object-pattern branches are covered with matching test cases (valid + invalid). No issues found.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 18 AIC · ⌖ 4.9 AIC · ⊞ 5K
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnosing-bugs and /tdd — commenting with minor suggestions, no blocking issues.
📋 Key Themes & Highlights
Key Themes
- Missing symmetric test: the destructured-alias-unguarded path lacks an
invalidtest to mirror the simple-identifier case (see inline comment) - Redundant type guard in
isAssignedToGuardedAlias:ref.identifieris always anIdentifier, so the.type === AST_NODE_TYPES.Identifiercheck is unnecessary noise
Positive Highlights
- ✅ Root cause correctly identified and fixed at the right level
- ✅ One-level-of-indirection scope limit is well-reasoned and documented
- ✅ Both simple-identifier and object-pattern branches updated consistently
- ✅ Rule description updated to document alias support and scope boundary
- ✅ Good test coverage for the new behavior
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 31.7 AIC · ⌖ 4.53 AIC · ⊞ 6.7K
Comment /matt to run again
| }, | ||
| ], | ||
| }); | ||
| }); |
There was a problem hiding this comment.
[/tdd] Missing symmetrical invalid test: the destructured-alias-then-unguarded path has no dedicated test case.
The existing invalid test at line 91 covers the simple-identifier alias that is used non-guardingly (const e = result.error; core.info(String(e))). The object-pattern branch is only tested in the valid direction. Adding the symmetric invalid case strengthens confidence that isAssignedToGuardedAlias doesn't accidentally accept unguarded destructured aliases.
💡 Suggested invalid test
{
// destructured alias assigned but never guarded
code: `const { error } = spawnSync("git", ["status"]); const e = error; core.info(String(e));`,
errors: [{ messageId: "missingErrorCheck" }],
},@copilot please address this.
| if (idNode.type !== AST_NODE_TYPES.Identifier) return false; | ||
|
|
||
| const variable = findVariableByName(sourceCode, node, idNode.name); | ||
| if (!variable) return false; |
There was a problem hiding this comment.
[/diagnosing-bugs] The ref.identifier.type === AST_NODE_TYPES.Identifier guard inside isAssignedToGuardedAlias is redundant — the caller already extracted ref.identifier which is always an Identifier.
Removing the check makes the intent clearer and avoids future confusion about why the type guard is needed.
💡 Suggested simplification
return variable.references.some(ref => isGuardingErrorUsage(ref.identifier));@copilot please address this.
🧪 Test Quality Sentinel Report✅ Test Quality Score: 80/100 — Excellent
📊 Metrics (3 tests)
✅ Test Coverage AnalysisStrengths:
Coverage Summary:
Verdict
Quality assessment: Tests precisely cover the new feature and include a critical negative case that prevents the rule from creating false positives. High-value test additions for the scope. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
There was a problem hiding this comment.
✅ Test Quality Sentinel: 80/100. 0% implementation tests (threshold: 30%). All 3 tests verify behavioral contracts with comprehensive edge-case coverage. The negative case (alias without guarding) is particularly valuable for preventing false positives.
There was a problem hiding this comment.
🔎 Code quality review by PR Code Quality Reviewer · 45.6 AIC · ⌖ 4.42 AIC · ⊞ 5.6K
Comment /review to run again
| const variable = findVariableByName(sourceCode, node, idNode.name); | ||
| if (!variable) return false; | ||
|
|
||
| return variable.references.some(ref => ref.identifier.type === AST_NODE_TYPES.Identifier && isGuardingErrorUsage(ref.identifier)); |
There was a problem hiding this comment.
Write references are iterated needlessly before reaching isAssignedToGuardedAlias, adding subtle noise to the logic.
💡 Detail
variable.references includes both read and write references. The write reference is the binding site itself (e.g. the error identifier in the id position of const { ..., error } = spawnSync(...)). When that write-ref identifier is passed to isGuardingErrorUsage or isAssignedToGuardedAlias:
isGuardingErrorUsagewill walk up through its VariableDeclarator parent and returnfalse— harmless.isAssignedToGuardedAliaschecksparent.init !== node; since the write-ref identifier is theid, not theinit, it returnsfalseimmediately — also harmless.
But the intent is only ever to check read references, and silently skipping the write ref by relying on early-exit logic in two different helpers is fragile. A future change to either helper could accidentally treat a write reference as a valid guard. Filtering explicitly makes the contract clear:
return variable.references.some(
ref =>
!ref.isWrite() &&
ref.identifier.type === AST_NODE_TYPES.Identifier &&
(isGuardingErrorUsage(ref.identifier) || isAssignedToGuardedAlias(sourceCode, ref.identifier))
);Same pattern applies to the identical block in the simple-identifier branch (line ~196).
|
@copilot please run the Unresolved review threads (newest first):
|
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…as; revert unrelated schedule change Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
|
🎉 This pull request is included in a new release. Release: |
require-spawnsync-error-checkemits a false positive whenresult.erroris assigned to an intermediate variable that is then guarded — both in the simple-identifier and object-pattern branches.Root cause
isGuardingErrorUsagehas no handler forVariableDeclarator, soconst e = result.erroralways returnsfalse, and the laterif (e) throw eguard is never connected back toresult.error.Changes
isAssignedToGuardedAlias— whennodeis theinitof a single-identifierVariableDeclarator, resolves the alias variable and checks if any of its references satisfyisGuardingErrorUsage. One level of indirection only.isAssignedToGuardedAlias(sourceCode, parent)on theresult.errorMemberExpression.isAssignedToGuardedAlias(sourceCode, ref.identifier)on the destructurederrorbinding.resultto a helper function remains out of scope.Test coverage added