Skip to content

no-github-request-interpolated-route: rule never scope-resolves the route argument — identifier-bound dynamic routes bypass dete [Content truncated due to length] #49913

Description

@github-actions

Rule

eslint-factory/src/rules/no-github-request-interpolated-route.ts

Design gap

getInterpolatedRouteKind() is called directly on firstArg of <client>.request(firstArg, ...). It only recognizes an inline TemplateLiteral/BinaryExpression — it never resolves an Identifier first argument back to its initializer. Contrast with the sibling command rules in the same factory, no-exec-interpolated-command and no-child-process-interpolated-command, which both call resolveWriteOnceInitializerChain() (src/rules/command-initializer-utils.ts) specifically so that const cmd = \git ${branch}`; exec.exec(cmd)is still caught.no-github-request-interpolated-route` has no equivalent step, so:

const route = `GET /repos/${owner}/${repo}${suffix}`;
github.request(route, {});

... is silently unflagged today, even though the inline form github.request(\GET /repos/${owner}/${repo}${suffix}`, {})is correctly flagged asinterpolatedRoute`.

Why this matters / grounding

The "extract a route into a local variable" idiom is already established in this exact codebase — create_project.cjs:230 and update_project.cjs:919 both do:

const route = projectInfo.scope === "orgs" ? "POST /orgs/{org}/projectsV2/{project_number}/views" : "POST /users/{user_id}/projectsV2/{project_number}/views";
...
const response = await github.request(route, params);

This particular instance is fully static (both ternary branches are literals) so it's a correct true-negative today. But it proves the const route = ...; client.request(route, ...) shape is a normal, encouraged pattern here — and the rule's own opaqueWholeRoute message text ("pass a typed route string from the caller when the entire route is dynamic") actively steers developers toward exactly the identifier-bound shape the rule cannot see through. The very first time someone builds that caller-provided route dynamically (e.g. via template literal or concatenation) instead of a static literal, the rule goes blind.

Ask

Give no-github-request-interpolated-route the same write-once scope resolution the exec/child_process rules already have, so an Identifier first argument is resolved to its initializer (reusing or adapting resolveWriteOnceInitializerChain, ideally factored into a rule-agnostic shared util since three rules now need it) before classifying static vs. interpolated vs. concatenated.

Acceptance criteria

  • When the first argument to <client>.request() is a plain Identifier, the rule walks its write-once initializer chain (function-scoped, single-definition, no reassignment — matching the existing resolveWriteOnceInitializerChain semantics) before running getInterpolatedRouteKind/isOpaqueWholeRouteInterpolation.
  • New passing test: const route = \GET /repos/${owner}/${repo}`; github.request(route, {})→ reportsinterpolatedRoute`.
  • New passing test: const route = cond ? "GET /a" : "GET /b"; github.request(route, {}) → NOT flagged (fully static ternary stays a true negative, matching the live create_project.cjs/update_project.cjs idiom).
  • New passing test: identifier reassigned after declaration (let route = ...; route = otherExpr;) → not resolved through (write-once semantics preserved, matches resolveInitializer's existing reassignment guard).
  • Existing valid/invalid test suite for this rule remains green; behavior for inline (non-Identifier) route arguments is unchanged.

Scope

eslint-factory/src/rules/no-github-request-interpolated-route.ts (+ test file). No production code in actions/setup/js/** needs to change — this is a rule-only false-negative fix; the only live call sites found (add_reaction.cjs, add_reaction_and_edit_comment.cjs, add_workflow_run_comment.cjs, assign_agent_helpers.cjs, create_project.cjs, update_project.cjs, route_slash_command.cjs, update_activation_comment.cjs, notify_comment_error.cjs, autofix_code_scanning_alert.cjs, close_issue.cjs, create_agent_session.cjs) already use static or safely-typed routes.

Generated by 🤖 ESLint Refiner · agent · 224.3 AIC · ⌖ 28.6 AIC · ⊞ 4.9K ·

  • expires on Aug 9, 2026, 10:29 PM UTC-08:00

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions