[eslint-miner] fix(eslint): extend require-fetch-try-catch to detect member-chained fetch calls#47967
Conversation
…fetch calls Previously, isAwaitFetchCall only matched bare `await fetch(url)` expressions. Any method chain on the result (`await fetch(url).then(...)`, `.catch(...)`, `.json()`) had a MemberExpression callee and silently escaped detection. This change: - Adds getRootFetchCall() to walk call/member chains and identify fetch at the root - Adds chainHasRejectionHandler() to recognize .catch(handler) and two-arg .then() as satisfying the error-handling requirement (no flag emitted for those) - Updates the AwaitExpression visitor to use these new helpers - Adds test cases covering chained-fetch valid/invalid patterns Closes #47950, relates to #47932 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
✅ Test Quality Sentinel completed test quality analysis. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ 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 (0 additions in default business logic directories). |
|
✅ PR Code Quality Reviewer completed the code quality review. |
There was a problem hiding this comment.
Pull request overview
Extends the ESLint rule to detect unprotected chained fetch() calls.
Changes:
- Traverses member-call chains rooted at
fetch(). - Recognizes
.catch()and two-argument.then()handlers. - Adds chained-fetch tests.
Show a summary per file
| File | Description |
|---|---|
require-fetch-try-catch.ts |
Adds chained-fetch detection and rejection-handler checks. |
require-fetch-try-catch.test.ts |
Tests protected and unprotected fetch chains. |
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/require-fetch-try-catch.ts:49
- Arity does not prove that a rejection handler exists. Promise methods ignore non-callable handlers, so
await fetch(url).catch(undefined)andawait fetch(url).then(ok, null)still propagate the fetch rejection but are exempted here. Inspect the relevant argument rather than only its presence (at minimum rejectingundefined,null, and other statically non-callable literals), and add invalid tests for these cases.
if (name === "catch" && call.arguments.length >= 1) return true;
if (name === "then" && call.arguments.length >= 2) return true;
eslint-factory/src/rules/require-fetch-try-catch.ts:64
- Optional member chains are wrapped in a
ChainExpression, soawait fetch(url)?.then(ok)returns here without detection even though the fetch rejection remains unhandled. UnwrapChainExpressionbefore checking for a call, and use that same unwrapped call asouterCallin the visitor.
const argument = node.argument;
if (argument.type !== AST_NODE_TYPES.CallExpression) return false;
return getRootFetchCall(argument) !== null;
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Medium
| if (callee.type !== AST_NODE_TYPES.MemberExpression) break; | ||
| const member = callee as TSESTree.MemberExpression; | ||
| const prop = member.property; | ||
| const name = prop.type === AST_NODE_TYPES.Identifier ? (prop as TSESTree.Identifier).name : null; |
| * Returns true when the node is an `await` expression whose argument is a call chain | ||
| * rooted in the global `fetch` identifier (e.g. `await fetch(url)` or | ||
| * `await fetch(url).then(...)`). |
| valid: [ | ||
| `async function f() { await fetch(url).catch(handler); }`, | ||
| `async function f() { const res = await fetch(url).catch(err => { throw err; }); }`, | ||
| `async function f() { await fetch(url).then(r => r.json(), err => { throw err; }); }`, |
🧪 Test Quality Sentinel Report✅ Test Quality Score: 90/100 — Excellent
📊 Metrics (2 tests)
Test BreakdownNew Tests Added:
Verdict
|
There was a problem hiding this comment.
The implementation correctly extends require-fetch-try-catch to detect member-chained fetch patterns. The two helper functions are clean, the logic is sound, and test coverage addresses the key cases. No blocking issues found.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 19.6 AIC · ⌖ 5.13 AIC · ⊞ 5K
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnosing-bugs and /tdd — solid fix overall, a few small improvements suggested.
📋 Key Themes & Highlights
Key Themes
- Loop sentinel fragility (
chainHasRejectionHandler): thecurrent !== fetchCallidentity check is correct today but relies on an implicit invariant (same node reference) that is not documented and could silently break if the AST helpers change. - Test coverage gap: multi-hop chain (
fetch().then(...).catch(handler)) is not exercised, leaving the loop body untested past its first iteration. - Test naming:
"valid: ..."/"invalid: ..."prefixes describe outcome rather than behaviour — spec-style names would make failures self-documenting.
Positive Highlights
- ✅ Root-cause fix:
getRootFetchCallcorrectly unifies bare and chained fetch detection. - ✅
chainHasRejectionHandlercorrectly handles both.catch()and two-argument.then(). - ✅ Existing
validate_secrets.cjspatterns are explicitly validated in the PR description. - ✅ 11/11 tests pass; no regression to pre-existing behavior.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 37.1 AIC · ⌖ 4.88 AIC · ⊞ 6.7K
Comment /matt to run again
| cjsRuleTester.run("require-fetch-try-catch", requireFetchTryCatchRule, { | ||
| valid: [], | ||
| invalid: [ | ||
| { |
There was a problem hiding this comment.
[/tdd] Multi-hop chain fetch().then(...).catch(handler) is not tested — add a valid case to prevent regressions.
💡 Suggested test case
chainHasRejectionHandler walks each link; a 3-hop chain exercises the loop past its first iteration. A future refactor breaking multi-hop traversal would silently pass all current tests.
// add to valid: block in "await fetch chain with .catch() rejection handler"
`async function f() { const r = await fetch(url).then(x => x).catch(handler); }`,@copilot please address this.
| * rejection handler: `.catch(handler)` or a two-argument `.then(onFulfilled, onRejected)`. | ||
| * Such chains already handle network errors and should not be flagged. | ||
| */ | ||
| function chainHasRejectionHandler(fetchCall: TSESTree.CallExpression, outerCall: TSESTree.CallExpression): boolean { |
There was a problem hiding this comment.
[/diagnosing-bugs] chainHasRejectionHandler uses object identity (current !== fetchCall) as the loop sentinel, which is fragile — if getRootFetchCall ever returns a different node object for the same AST node, the loop will over-traverse and silently skip the handler check.
💡 Safer alternative
Consider stopping the loop by position/type rather than identity. A simple guard is to break when current is the same CallExpression as fetchCall by checking current === fetchCall before the member-walk, or by tracking depth from getRootFetchCall. Adding a comment explaining the identity invariant would also help future maintainers.
// Add a safety comment at the sentinel check
while (current !== fetchCall) { // relies on the same node reference returned by getRootFetchCall@copilot please address this.
| invalid: [], | ||
| }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
[/tdd] Test description says "valid" but does not encode why — consider renaming to describe the expected behaviour as a specification, not just a status label.
💡 Example rename
// before
it("valid: await fetch chain with .catch() rejection handler", ...)
// after — reads as a spec
it("does not flag await fetch chains that have a .catch() rejection handler", ...)
it("does not flag await fetch chains that pass an error handler to .then()", ...)Spec-style names make test output self-documenting and reduce the need to read the test body to understand intent.
@copilot please address this.
There was a problem hiding this comment.
REQUEST_CHANGES — two correctness bugs must be fixed before merge
The chain-detection improvement is solid and the approach is correct, but two false-negative bugs in chainHasRejectionHandler and one false positive need to be addressed.
Blocking issues (3)
.catch(null)is accepted as a valid handler (line 88) —arguments.length >= 1is satisfied bynull, which is a no-op per spec. Fix: check that the argument is not a null literal..then(fn, null)is accepted as a valid rejection handler (line 89) — same root cause; the second argument beingnullleaves the rejection unhandled.fetch(url)['catch'](handler)produces a false positive (line 87) — bracket-notation method calls use aLiteralproperty node, not anIdentifier, sonameisnulland the chain is never recognized as safe. Fix: also extract the name from string-literal computed properties.
Additionally: the double invocation of getRootFetchCall (once inside isAwaitFetchChain, once via the ! assertion on line 117) is a latent maintenance hazard — consolidating to one call would be cleaner.
Test gaps: no coverage for .catch(null), .then(fn, null), or multi-hop chains like .then(r=>r.json()).catch(handler).
🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 45.5 AIC · ⌖ 5.12 AIC · ⊞ 5.7K
Comment /review to run again
Comments that could not be inline-anchored
eslint-factory/src/rules/require-fetch-try-catch.ts:88
False negative: .catch(null) counts as a valid rejection handler — call.arguments.length >= 1 is true for .catch(null), but null is a no-op per the Promises spec and the rejection will still propagate unhandled.
<details>
<summary>💡 Suggested fix</summary>
Add a null/undefined literal guard before accepting the argument as a real handler:
const isNullish = (n: TSESTree.Node) =>
n.type === AST_NODE_TYPES.Literal && (n as TSESTree.Literal).value === null;
if (name === "ca…
</details>
<details><summary>eslint-factory/src/rules/require-fetch-try-catch.ts:117</summary>
**Double-call redundancy creates a maintenance trap** — `getRootFetchCall(outerCall)!` is called after `isAwaitFetchChain` already invoked it internally. If one function is modified without the other, the `!` assertion will hide a null-dereference at runtime.
<details>
<summary>💡 Suggested fix</summary>
Consolidate to a single call: return the `CallExpression | null` directly from the check instead of throwing it away and recalling:
```ts
AwaitExpression(node) {
if (node.type !== AST_NODE…
</details>
<details><summary>eslint-factory/src/rules/require-fetch-try-catch.ts:87</summary>
**False positive: `fetch(url)['catch'](handler)` is flagged as missing a rejection handler** — bracket-notation method calls (`computed: true` MemberExpression) are not recognized because the property is a `Literal`, not an `Identifier`, so `name` is `null`.
<details>
<summary>💡 Suggested fix</summary>
Extract the method name from both identifier and string-literal computed properties:
```ts
const prop = member.property;
const name =
prop.type === AST_NODE_TYPES.Identifier
? (prop as …
</details>
<details><summary>eslint-factory/src/rules/require-fetch-try-catch.test.ts:17</summary>
**Missing test cases for `null` handler arguments** — the test suite has no coverage for `.catch(null)` or `.then(fn, null)`, which are currently false negatives (the rule incorrectly suppresses them).
<details>
<summary>💡 Suggested additions to the invalid group</summary>
```ts
{
code: `async function f() { await fetch(url).catch(null); }`,
errors: [{ messageId: "requireTryCatch" }],
},
{
code: `async function f() { await fetch(url).then(r => r.json(), null); }`,
errors: [{ messageI…
</details>|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
All three review items addressed in commit
All 13 tests pass locally. |
|
@copilot resolve the merge conflicts in this pull request |
|
@copilot this branch has merge conflicts. Please run There is also a blocking code-quality review requesting follow-up on nullish rejection handlers and bracket-notation Run: https://github.com/github/gh-aw/actions/runs/30155206984
|
|
@copilot merge main and recompile |
…-catch-chain-detection-71057aa0a9e18722 # Conflicts: # .github/workflows/ab-testing-advisor.lock.yml # .github/workflows/ace-editor.lock.yml # .github/workflows/agent-performance-analyzer.lock.yml # .github/workflows/agent-persona-explorer.lock.yml # .github/workflows/agentic-token-audit.lock.yml # .github/workflows/agentic-token-optimizer.lock.yml # .github/workflows/agentic-token-trend-audit.lock.yml # .github/workflows/ai-moderator.lock.yml # .github/workflows/api-consumption-report.lock.yml # .github/workflows/approach-validator.lock.yml # .github/workflows/archie.lock.yml # .github/workflows/architecture-guardian.lock.yml # .github/workflows/artifacts-summary.lock.yml # .github/workflows/audit-workflows.lock.yml # .github/workflows/auto-triage-issues.lock.yml # .github/workflows/avenger.lock.yml # .github/workflows/aw-failure-investigator.lock.yml # .github/workflows/blog-auditor.lock.yml # .github/workflows/bot-detection.lock.yml # .github/workflows/brave.lock.yml # .github/workflows/breaking-change-checker.lock.yml # .github/workflows/changeset.lock.yml # .github/workflows/chaos-pr-bundle-fuzzer.lock.yml # .github/workflows/ci-coach.lock.yml # .github/workflows/ci-doctor.lock.yml # .github/workflows/claude-code-user-docs-review.lock.yml # .github/workflows/cli-consistency-checker.lock.yml # .github/workflows/cli-version-checker.lock.yml # .github/workflows/cloclo.lock.yml # .github/workflows/code-scanning-fixer.lock.yml # .github/workflows/code-simplifier.lock.yml # .github/workflows/codex-github-remote-mcp-test.lock.yml # .github/workflows/commit-changes-analyzer.lock.yml # .github/workflows/constraint-solving-potd.lock.yml # .github/workflows/contribution-check.lock.yml # .github/workflows/copilot-agent-analysis.lock.yml # .github/workflows/copilot-centralization-drilldown.lock.yml # .github/workflows/copilot-centralization-optimizer.lock.yml # .github/workflows/copilot-cli-deep-research.lock.yml # .github/workflows/copilot-opt.lock.yml # .github/workflows/copilot-pr-merged-report.lock.yml # .github/workflows/copilot-pr-nlp-analysis.lock.yml # .github/workflows/copilot-pr-prompt-analysis.lock.yml # .github/workflows/copilot-session-insights.lock.yml # .github/workflows/craft.lock.yml # .github/workflows/daily-agent-of-the-day-blog-writer.lock.yml # .github/workflows/daily-agentrx-trace-optimizer.lock.yml # .github/workflows/daily-ambient-context-optimizer.lock.yml # .github/workflows/daily-architecture-diagram.lock.yml # .github/workflows/daily-assign-issue-to-user.lock.yml # .github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml # .github/workflows/daily-aw-cross-repo-compile-check.lock.yml # .github/workflows/daily-awf-spec-compiler-surfacing.lock.yml # .github/workflows/daily-byok-ollama-test.lock.yml # .github/workflows/daily-cache-strategy-analyzer.lock.yml # .github/workflows/daily-caveman-optimizer.lock.yml # .github/workflows/daily-choice-test.lock.yml # .github/workflows/daily-cli-performance.lock.yml # .github/workflows/daily-cli-tools-tester.lock.yml # .github/workflows/daily-code-metrics.lock.yml # .github/workflows/daily-community-attribution.lock.yml # .github/workflows/daily-compiler-quality.lock.yml # .github/workflows/daily-compiler-threat-spec-optimizer.lock.yml # .github/workflows/daily-credit-limit-test.lock.yml # .github/workflows/daily-doc-healer.lock.yml # .github/workflows/daily-doc-updater.lock.yml # .github/workflows/daily-elixir-credo-snippet-audit.lock.yml # .github/workflows/daily-evals-report.lock.yml # .github/workflows/daily-experiment-report.lock.yml # .github/workflows/daily-fact.lock.yml # .github/workflows/daily-file-diet.lock.yml # .github/workflows/daily-firewall-report.lock.yml # .github/workflows/daily-formal-spec-verifier.lock.yml # .github/workflows/daily-function-namer.lock.yml # .github/workflows/daily-geo-optimizer.lock.yml # .github/workflows/daily-hippo-learn.lock.yml # .github/workflows/daily-issues-report.lock.yml # .github/workflows/daily-malicious-code-scan.lock.yml # .github/workflows/daily-max-ai-credits-test.lock.yml # .github/workflows/daily-mcp-concurrency-analysis.lock.yml # .github/workflows/daily-model-inventory.lock.yml # .github/workflows/daily-model-resolution.lock.yml # .github/workflows/daily-multi-device-docs-tester.lock.yml # .github/workflows/daily-news.lock.yml # .github/workflows/daily-observability-report.lock.yml # .github/workflows/daily-performance-summary.lock.yml # .github/workflows/daily-regulatory.lock.yml # .github/workflows/daily-reliability-review.lock.yml # .github/workflows/daily-rendering-scripts-verifier.lock.yml # .github/workflows/daily-repo-chronicle.lock.yml # .github/workflows/daily-safe-output-integrator.lock.yml # .github/workflows/daily-safe-output-optimizer.lock.yml # .github/workflows/daily-safe-outputs-conformance.lock.yml # .github/workflows/daily-safeoutputs-git-simulator.lock.yml # .github/workflows/daily-secrets-analysis.lock.yml # .github/workflows/daily-security-observability.lock.yml # .github/workflows/daily-security-red-team.lock.yml # .github/workflows/daily-semgrep-scan.lock.yml # .github/workflows/daily-sentrux-report.lock.yml # .github/workflows/daily-skill-optimizer.lock.yml # .github/workflows/daily-spdd-spec-planner.lock.yml # .github/workflows/daily-squid-image-scan.lock.yml # .github/workflows/daily-syntax-error-quality.lock.yml # .github/workflows/daily-team-evolution-insights.lock.yml # .github/workflows/daily-team-status.lock.yml # .github/workflows/daily-testify-uber-super-expert.lock.yml # .github/workflows/daily-token-consumption-report.lock.yml # .github/workflows/daily-vulnhunter-scan.lock.yml # .github/workflows/daily-windows-terminal-integration-builder.lock.yml # .github/workflows/daily-workflow-updater.lock.yml # .github/workflows/daily-yamllint-fixer.lock.yml # .github/workflows/dataflow-pr-discussion-dataset.lock.yml # .github/workflows/dead-code-remover.lock.yml # .github/workflows/deep-report.lock.yml # .github/workflows/deepsec-security-scan.lock.yml # .github/workflows/delight.lock.yml # .github/workflows/dependabot-burner.lock.yml # .github/workflows/dependabot-go-checker.lock.yml # .github/workflows/deployment-incident-monitor.lock.yml # .github/workflows/design-decision-gate.lock.yml # .github/workflows/designer-drift-audit.lock.yml # .github/workflows/detection-analysis-report.lock.yml # .github/workflows/dev-hawk.lock.yml # .github/workflows/dev.lock.yml # .github/workflows/developer-docs-consolidator.lock.yml # .github/workflows/dictation-prompt.lock.yml # .github/workflows/discussion-task-miner.lock.yml # .github/workflows/docs-noob-tester.lock.yml # .github/workflows/draft-pr-cleanup.lock.yml # .github/workflows/duplicate-code-detector.lock.yml # .github/workflows/eslint-miner.lock.yml # .github/workflows/eslint-monster.lock.yml # .github/workflows/eslint-refiner.lock.yml # .github/workflows/example-failure-category-filter.lock.yml # .github/workflows/example-permissions-warning.lock.yml # .github/workflows/example-workflow-analyzer.lock.yml # .github/workflows/firewall-escape.lock.yml # .github/workflows/firewall.lock.yml # .github/workflows/functional-pragmatist.lock.yml # .github/workflows/github-mcp-structural-analysis.lock.yml # .github/workflows/github-mcp-tools-report.lock.yml # .github/workflows/github-remote-mcp-auth-test.lock.yml # .github/workflows/glossary-maintainer.lock.yml # .github/workflows/go-fan.lock.yml # .github/workflows/go-logger.lock.yml # .github/workflows/go-pattern-detector.lock.yml # .github/workflows/gpclean.lock.yml # .github/workflows/grumpy-reviewer.lock.yml # .github/workflows/hippo-embed.lock.yml # .github/workflows/hourly-ci-cleaner.lock.yml # .github/workflows/impeccable-skills-reviewer.lock.yml # .github/workflows/instructions-janitor.lock.yml # .github/workflows/issue-arborist.lock.yml # .github/workflows/issue-monster.lock.yml # .github/workflows/issue-triage-agent.lock.yml # .github/workflows/jsweep.lock.yml # .github/workflows/layout-spec-maintainer.lock.yml # .github/workflows/lint-monster.lock.yml # .github/workflows/linter-miner.lock.yml # .github/workflows/lockfile-stats.lock.yml # .github/workflows/mattpocock-skills-reviewer.lock.yml # .github/workflows/mcp-inspector.lock.yml # .github/workflows/mergefest.lock.yml # .github/workflows/metrics-collector.lock.yml # .github/workflows/necromancer.lock.yml # .github/workflows/notion-issue-summary.lock.yml # .github/workflows/objective-impact-report.lock.yml # .github/workflows/org-health-report.lock.yml # .github/workflows/outcome-collector.lock.yml # .github/workflows/pdf-summary.lock.yml # .github/workflows/plan.lock.yml # .github/workflows/poem-bot.lock.yml # .github/workflows/portfolio-analyst.lock.yml # .github/workflows/pr-code-quality-reviewer.lock.yml # .github/workflows/pr-description-caveman.lock.yml # .github/workflows/pr-nitpick-reviewer.lock.yml # .github/workflows/pr-sous-chef.lock.yml # .github/workflows/pr-triage-agent.lock.yml # .github/workflows/prompt-clustering-analysis.lock.yml # .github/workflows/python-data-charts.lock.yml # .github/workflows/q.lock.yml # .github/workflows/refactoring-cadence.lock.yml # .github/workflows/refiner.lock.yml # .github/workflows/release.lock.yml # .github/workflows/repo-audit-analyzer.lock.yml # .github/workflows/repo-tree-map.lock.yml # .github/workflows/repository-quality-improver.lock.yml # .github/workflows/research.lock.yml # .github/workflows/ruflo-backed-task.lock.yml # .github/workflows/safe-output-health.lock.yml # .github/workflows/schema-consistency-checker.lock.yml # .github/workflows/schema-feature-coverage.lock.yml # .github/workflows/scout.lock.yml # .github/workflows/security-compliance.lock.yml # .github/workflows/security-review.lock.yml # .github/workflows/semantic-function-refactor.lock.yml # .github/workflows/sergo.lock.yml # .github/workflows/sighthound-security-scan.lock.yml # .github/workflows/skillet.lock.yml # .github/workflows/slide-deck-maintainer.lock.yml # .github/workflows/smoke-agent-all-merged.lock.yml # .github/workflows/smoke-agent-all-none.lock.yml # .github/workflows/smoke-agent-public-approved.lock.yml # .github/workflows/smoke-agent-public-none.lock.yml # .github/workflows/smoke-agent-scoped-approved.lock.yml # .github/workflows/smoke-antigravity.lock.yml # .github/workflows/smoke-call-workflow.lock.yml # .github/workflows/smoke-ci.lock.yml # .github/workflows/smoke-claude-on-copilot.lock.yml # .github/workflows/smoke-claude.lock.yml # .github/workflows/smoke-codex.lock.yml # .github/workflows/smoke-copilot-aoai-apikey.lock.yml # .github/workflows/smoke-copilot-aoai-entra.lock.yml # .github/workflows/smoke-copilot-arm.lock.yml # .github/workflows/smoke-copilot-mai.lock.yml # .github/workflows/smoke-copilot-sdk.lock.yml # .github/workflows/smoke-copilot-small.lock.yml # .github/workflows/smoke-copilot-sub-agents.lock.yml # .github/workflows/smoke-copilot.lock.yml # .github/workflows/smoke-create-cross-repo-pr.lock.yml # .github/workflows/smoke-gemini.lock.yml # .github/workflows/smoke-github-claude.lock.yml # .github/workflows/smoke-multi-pr.lock.yml # .github/workflows/smoke-opencode.lock.yml # .github/workflows/smoke-otel-backends.lock.yml # .github/workflows/smoke-pi.lock.yml # .github/workflows/smoke-project.lock.yml # .github/workflows/smoke-service-ports.lock.yml # .github/workflows/smoke-temporary-id.lock.yml # .github/workflows/smoke-test-tools.lock.yml # .github/workflows/smoke-update-cross-repo-pr.lock.yml # .github/workflows/smoke-workflow-call-with-inputs.lock.yml # .github/workflows/smoke-workflow-call.lock.yml # .github/workflows/spec-enforcer.lock.yml # .github/workflows/spec-extractor.lock.yml # .github/workflows/spec-librarian.lock.yml # .github/workflows/stale-pr-cleanup.lock.yml # .github/workflows/stale-repo-identifier.lock.yml # .github/workflows/static-analysis-report.lock.yml # .github/workflows/step-name-alignment.lock.yml # .github/workflows/sub-issue-closer.lock.yml # .github/workflows/super-linter.lock.yml # .github/workflows/technical-doc-writer.lock.yml # .github/workflows/terminal-stylist.lock.yml # .github/workflows/test-quality-sentinel.lock.yml # .github/workflows/tidy.lock.yml # .github/workflows/typist.lock.yml # .github/workflows/ubuntu-image-analyzer.lock.yml # .github/workflows/uk-ai-operational-resilience.lock.yml # .github/workflows/unbloat-docs.lock.yml # .github/workflows/update-astro.lock.yml # .github/workflows/video-analyzer.lock.yml # .github/workflows/visual-regression-checker.lock.yml # .github/workflows/weekly-blog-post-writer.lock.yml # .github/workflows/weekly-editors-health-check.lock.yml # .github/workflows/weekly-issue-summary.lock.yml # .github/workflows/weekly-safe-outputs-spec-review.lock.yml # .github/workflows/workflow-generator.lock.yml # .github/workflows/workflow-health-manager.lock.yml # .github/workflows/workflow-normalizer.lock.yml # .github/workflows/workflow-skill-extractor.lock.yml # eslint-factory/README.md # eslint-factory/src/rules/require-fetch-try-catch.test.ts # eslint-factory/src/rules/require-fetch-try-catch.ts Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Merge conflicts resolved. Main had landed two related commits (#47963, #47969) that extended the same rule with a more comprehensive implementation ( |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done. Main was already merged in the previous commit. Ran |
|
🎉 This pull request is included in a new release. Release: |
Summary
Fixes a false negative in the
require-fetch-try-catchESLint rule whereawait fetch(url).then(...),await fetch(url).json(), and similar member-chained patterns silently escaped detection.Problem
The original
isAwaitFetchCallfunction only matched anAwaitExpressionwhose argument was aCallExpressionwith a barefetchIdentifier callee:Any method chain moved the callee to a
MemberExpression, bypassing the rule entirely. For example:await fetch(url).then(r => r.json())— not detected (should be flagged)await fetch(url).json()— not detected (should be flagged)await fetch(url).catch(handler)— not detected (already safe, should NOT be flagged)This was documented in issues #47950 and #47932.
Solution
Two new helpers:
getRootFetchCall()— walks down a call/member chain and returns the rootfetch(...)CallExpressioniffetchis at the root, otherwise returns null.chainHasRejectionHandler()— checks whether the chain includes a.catch(handler)or a two-argument.then(onFulfilled, onRejected), which satisfy the error-handling requirement.The
AwaitExpressionvisitor now:await fetch(url).method(...)chains as fetch callsEvidence from codebase
validate_secrets.cjs:65,85useawait fetch(...).catch(rethrowAbortError)— this shape was silently ignored before. With this fix it remains not flagged (has a rejection handler).Tests
Added 3 new test groups:
valid:.catch()and two-arg.then()chains are not flaggedinvalid: one-arg.then()and response method chains (.json()) are flaggedvalid: chained fetch inside try block is not flaggedAll 11
require-fetch-try-catchtests pass. Pre-existingrequire-fs-io-try-catchfailures are unrelated.Validation
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
proxy.golang.orgSee Network Configuration for more information.