Tighten require-spawnsync-error-check for destructuring and guard semantics#45419
Conversation
|
Hey A couple of things to address before this is ready for review:
Once the diff is in, here's a prompt you can assign to your coding agent to ensure coverage:
|
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
require-spawnsync-error-check for destructuring and guard semantics
There was a problem hiding this comment.
Pull request overview
Tightens spawnSync linting by recognizing destructured error bindings and requiring guard-aware error usage.
Changes:
- Adds destructured binding and scope analysis.
- Distinguishes guarding error checks from passive reads.
- Adds regression tests and documents unsupported forms.
Show a summary per file
| File | Description |
|---|---|
eslint-factory/src/rules/require-spawnsync-error-check.ts |
Implements guard and destructuring analysis. |
eslint-factory/src/rules/require-spawnsync-error-check.test.ts |
Adds valid and invalid regression cases. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 4
- Review effort level: Medium
| continue; | ||
| } | ||
|
|
||
| break; |
| if (parent.type === AST_NODE_TYPES.LogicalExpression && ((parent.operator === "&&" && (parent.left === current || parent.right === current)) || (parent.operator === "||" && parent.left === current))) { | ||
| return true; |
| if (property.key.type !== AST_NODE_TYPES.Identifier || property.key.name !== "error") { | ||
| continue; |
| if (parent.type === AST_NODE_TYPES.LogicalExpression && ((parent.operator === "&&" && (parent.left === current || parent.right === current)) || (parent.operator === "||" && parent.left === current))) { | ||
| return true; |
|
✅ Test Quality Sentinel completed test quality analysis. No test files detected in pre-fetched data (test-diff.txt empty). Test Quality Sentinel analysis skipped per workflow Step 1. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (default_business_additions=0). |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
|
There was a problem hiding this comment.
Review: require-spawnsync-error-check tightening
The destructuring coverage and guard-aware detection are solid improvements. One blocking correctness bug found.
Blocking: && right-hand operand is not a guarding position (see inline comment on line 33). condition && result.error is a passive read that would incorrectly pass the rule. Both && and || should only treat parent.left === current as guarding.
Non-blocking: No test for renamed destructuring ({ error: err }) which getErrorBindingNames explicitly supports — worth adding a valid-case test to prevent regression.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 32.1 AIC · ⌖ 5.5 AIC · ⊞ 4.8K
| return true; | ||
| } | ||
|
|
||
| if (parent.type === AST_NODE_TYPES.LogicalExpression && ((parent.operator === "&&" && (parent.left === current || parent.right === current)) || (parent.operator === "||" && parent.left === current))) { |
There was a problem hiding this comment.
Bug: && right-hand operand is incorrectly treated as a guarding position.
In condition && result.error, the result.error on the right is a passive read — it only executes when condition is already truthy. It does not guard against the error. Only the left operand of && establishes a guard (e.g. result.error && doSomething()).
This allows the following to pass the rule when it should be flagged:
const result = spawnSync(cmd, args);
if (result.status !== 0 && result.error) log(result.error); // right side — not a guardFix: change (parent.left === current || parent.right === current) to just parent.left === current:
// Both && and || only guard on the left operand
if (parent.type === AST_NODE_TYPES.LogicalExpression && parent.left === current) {
return true;
}@copilot please address this.
There was a problem hiding this comment.
Request changes: four correctness gaps in guard-walk logic that cause false negatives (valid guards rejected) and false positives (non-guards accepted).
The existing comments detail each issue; see inline threads for specifics. Summary:
- Comparison-based guards rejected —
if (result.error !== undefined)is not recognized becauseBinaryExpressionparents are not traversed. ||right operand prematurely rejected —if (status !== 0 || result.error)reports false positives.- String-literal destructuring keys not recognized —
const { "error": spawnError } = spawnSync(...)is reported as missing a check. - Logical-expression operands accepted unconditionally — passive reads like
result.error && result.error.messagesatisfy the rule without any guard context.
All four affect the correctness of the lint rule's intent and need to be fixed before merging.
🔎 Code quality review by PR Code Quality Reviewer · 88.8 AIC · ⌖ 4.33 AIC · ⊞ 5.4K
Comment /review to run again
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnosing-bugs and /tdd — requesting changes on one correctness issue and two clarity gaps.
📋 Key Themes & Highlights
Key Issues
&&right-operand false negative (line 33,isGuardingErrorUsage): treating the right side of&&as a guard meanscond && result.error(standalone expression) would pass the check even thoughresult.erroris never acted on. Only the left side of&&should be a guard terminus; the right side should continue walking up.- Redundant type guard (line 155):
ref.identifier.type === AST_NODE_TYPES.Identifieris alwaystruefor scope references — remove it for clarity. - Test coverage gap (test line 27): the case of logging before a guard is not in the valid suite, leaving an implicit gap in the spec.
Positive Highlights
- ✅ Guard-aware detection (
isGuardingErrorUsage) is a solid improvement over the previous "any.errorread" heuristic. - ✅ Destructuring path (
getErrorBindingNames) handles both{ error }and{ error = default }cleanly. - ✅ Scope is explicitly documented in the rule description — great for future contributors.
- ✅ New regression tests directly mirror the PR description examples.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 46.8 AIC · ⌖ 4.83 AIC · ⊞ 6.6K
Comment /matt to run again
| return true; | ||
| } | ||
|
|
||
| if (parent.type === AST_NODE_TYPES.LogicalExpression && ((parent.operator === "&&" && (parent.left === current || parent.right === current)) || (parent.operator === "||" && parent.left === current))) { |
There was a problem hiding this comment.
[/diagnosing-bugs] The && right-operand branch is a false negative: someCondition && result.error would satisfy the guard check even though result.error is only read when someCondition is truthy — no throw/return occurs.
💡 Details and suggested fix
The current check:
parent.operator === "&&" && (parent.left === current || parent.right === current)treats the right side of && as a guard. For handleError() && result.error (standalone expression), isGuardingErrorUsage returns true even though nothing acts on the error.
Restricting && to the left operand (like || does) or continuing to walk up to the enclosing conditional removes this false negative:
parent.operator === "&&" && parent.left === current
// or
parent.operator === "||" && parent.left === currentThe right side of && is already guarded by the enclosing if/while parent via the loop — no early return is needed for it.
@copilot please address this.
| return true; | ||
| } | ||
|
|
||
| if (parent.type === AST_NODE_TYPES.LogicalExpression && ((parent.operator === "&&" && (parent.left === current || parent.right === current)) || (parent.operator === "||" && parent.left === current))) { |
There was a problem hiding this comment.
[/tdd] Missing test: ?? (nullish coalescing) is not covered — result.error ?? fallback would not satisfy isGuardingErrorUsage since NullishCoalescing is absent from the operator checks, but there is no test case documenting this as intentionally out-of-scope.
💡 Suggested test case or comment
Either add an invalid test case:
{ code: `const result = spawnSync("git", ["status"]); const e = result.error ?? null;`, errors: [{ messageId: "missingErrorCheck" }] }Or add a comment in isGuardingErrorUsage explicitly noting ?? is not treated as a guard (the operator does not imply control flow diversion).
@copilot please address this.
| const hasGuardingErrorCheck = errorBindingNames.some(varName => { | ||
| const variable = findVariableByName(sourceCode, node, varName); | ||
| 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.
[/diagnosing-bugs] ref.identifier.type === AST_NODE_TYPES.Identifier is always true for an ESLint scope reference — Reference.identifier is always an Identifier node. This guard is a no-op and only adds noise.
💡 Simplification
return variable.references.some(ref => isGuardingErrorUsage(ref.identifier));@copilot please address this.
🧪 Test Quality Sentinel Report✅ Test Quality Score: 100/100 — Excellent
📊 Metrics (1 test)
Verdict
Analysis:
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.
|
|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot please run the
|
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed the in-scope rule feedback in |
require-spawnsync-error-checkpreviously missedspawnSyncdestructuring patterns and treated any.errorread as compliant, which allowed non-handling usages (e.g. logging only) to bypass the rule. This update expands coverage for destructured bindings and enforces that.errorparticipates in actual guard/handling control flow.Rule coverage: destructured
spawnSyncbindingsspawnSync(...).erroris not bound (e.g.{ status }).erroris used in a guarding/handling position.Rule soundness: require guarding
.errorusage.errorread” logic with guard-aware detection..errorparticipates in control flow (condition tests, logical guards, throw/return paths), not passive reads.Scope explicitly documented
AssignmentExpression(result = spawnSync(...)) and inline chains (spawnSync(...).status) as out of scope.Regression cases captured in rule tests
.errorread (invalid),error(invalid),error(valid),.errorplus additional logging (valid).