Rule
eslint-factory/src/rules/no-github-request-interpolated-route.ts
Gap
isOctokitSourceExpression() and isIdentifierBoundToOctokitClient() only recognize a const binding whose initializer is directly one of: a known name (github/octokit/githubClient/octokitClient), a getOctokit(...) call, or context.github. The test suite (no-github-request-interpolated-route.test.ts:242-335) only exercises exactly these single-hop direct-alias shapes.
A const/parameter bound via a fallback expression — LogicalExpression (||, ??) or ConditionalExpression (? :) — whose fallback branch is a known Octokit source is not recognized at all, because isOctokitSourceExpression() has no branch for LogicalExpression/ConditionalExpression. The .request() call is then invisible to the rule regardless of how the route argument is built, so an interpolated/concatenated route on such a client silently escapes detection.
Live grounding
This exact idiom — clientVar = <option> || github — already exists twice in the corpus:
actions/setup/js/update_activation_comment.cjs:206-211:
const fallbackClient = options.targetGithubClient || github;
...
const response = await fallbackClient.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { ... });
Today's route argument is a static literal, so there's no live misfire yet — but the rule performs zero analysis of this .request() call site at all (it isn't recognized as an Octokit client site), so a future edit that swaps in a template-literal or concatenated route here would ship with no warning.
actions/setup/js/sub_issue_helpers.cjs:15: return githubClient || github; — the same fallback-client idiom, confirming it's a recurring pattern in this codebase rather than a one-off (this particular call site only reaches .graphql(), out of this rule's scope, but demonstrates the idiom repeats).
Ask
Extend isOctokitSourceExpression() (or the call sites in isIdentifierBoundToOctokitClient()) to recurse into LogicalExpression.right and ConditionalExpression.consequent/.alternate when checking whether an initializer resolves to a known Octokit source — mirroring how isStaticRouteExpression() already recurses into BinaryExpression for the route side. Add test cases for:
const fallbackClient = options.targetGithubClient || github; fallbackClient.request(\GET /repos/${owner}/${repo}`, {...});` → should report (currently silent).
const client = useTarget ? targetClient : github; client.request(...) → should report the same way when the route is dynamic.
Generated by 🤖 ESLint Refiner · claude · agent · 248.1 AIC · ⌖ 6.98 AIC · ⊞ 4.8K · ◷
Rule
eslint-factory/src/rules/no-github-request-interpolated-route.tsGap
isOctokitSourceExpression()andisIdentifierBoundToOctokitClient()only recognize aconstbinding whose initializer is directly one of: a known name (github/octokit/githubClient/octokitClient), agetOctokit(...)call, orcontext.github. The test suite (no-github-request-interpolated-route.test.ts:242-335) only exercises exactly these single-hop direct-alias shapes.A
const/parameter bound via a fallback expression —LogicalExpression(||,??) orConditionalExpression(? :) — whose fallback branch is a known Octokit source is not recognized at all, becauseisOctokitSourceExpression()has no branch forLogicalExpression/ConditionalExpression. The.request()call is then invisible to the rule regardless of how the route argument is built, so an interpolated/concatenated route on such a client silently escapes detection.Live grounding
This exact idiom —
clientVar = <option> || github— already exists twice in the corpus:actions/setup/js/update_activation_comment.cjs:206-211:.request()call site at all (it isn't recognized as an Octokit client site), so a future edit that swaps in a template-literal or concatenated route here would ship with no warning.actions/setup/js/sub_issue_helpers.cjs:15:return githubClient || github;— the same fallback-client idiom, confirming it's a recurring pattern in this codebase rather than a one-off (this particular call site only reaches.graphql(), out of this rule's scope, but demonstrates the idiom repeats).Ask
Extend
isOctokitSourceExpression()(or the call sites inisIdentifierBoundToOctokitClient()) to recurse intoLogicalExpression.rightandConditionalExpression.consequent/.alternatewhen checking whether an initializer resolves to a known Octokit source — mirroring howisStaticRouteExpression()already recurses intoBinaryExpressionfor the route side. Add test cases for:const fallbackClient = options.targetGithubClient || github; fallbackClient.request(\GET /repos/${owner}/${repo}`, {...});` → should report (currently silent).const client = useTarget ? targetClient : github; client.request(...)→ should report the same way when the route is dynamic.