Summary
no-exec-interpolated-command only inspects the syntactic shape of arguments[0]. Assigning the dynamic command to a variable first fully evades detection, so the space-splitting bug the rule targets slips through whenever the command is built one line earlier.
Evidence
- The rule's own test documents this as intentionally out of scope:
eslint-factory/src/rules/no-exec-interpolated-command.test.ts:22-23 — exec.exec(myCommand, [arg1]); is listed under valid with the comment "Command variable (identifier) — not a string literal, out of scope".
- Real code builds command strings in variables before executing, e.g.
actions/setup/js/create_pull_request.test.cjs:3064 — const cmdStr = \${cmd} ${(args || []).join(" ")}`;. A one-line refactor moving any of the many current exec.exec(`git ... ${x}`, [])` sites into a variable would silently disable the check.
Proposal
When arguments[0] is an Identifier, resolve a same-scope, write-once binding to its initializer and apply the existing getDynamicCommandKind check to that initializer.
Acceptance criteria
- Flags
const cmd = \git checkout ${b}`; exec.exec(cmd, []);and the dynamic+`-concatenation equivalent.
- Only resolves bindings that are (a) in scope, (b) have exactly one definition with an initializer, and (c) are never reassigned. Params, imported bindings, multiply-assigned vars, and cross-function bindings are skipped to avoid false positives.
- All current
valid cases stay valid — in particular a variable holding a static string (const cmd = "git"; exec.exec(cmd, [b]);) is not flagged.
- Report anchors on the call's first argument; message/
messageId unchanged.
- Tests: variable-holds-interpolation (flagged), variable-holds-static (valid), reassigned variable (valid/skipped), parameter-as-command (valid/skipped), and the
+-concatenation-in-variable case (flagged).
Non-duplicate
Not covered by any open or closed eslint issue; the direct-argument case already works — this closes the documented indirection gap.
Filed by ESLint Refiner (daily rule-quality refinement).
Generated by 🤖 ESLint Refiner · age00 317.3 AIC · ⌖ 13.1 AIC · ⊞ 4.6K · ◷
Summary
no-exec-interpolated-commandonly inspects the syntactic shape ofarguments[0]. Assigning the dynamic command to a variable first fully evades detection, so the space-splitting bug the rule targets slips through whenever the command is built one line earlier.Evidence
eslint-factory/src/rules/no-exec-interpolated-command.test.ts:22-23—exec.exec(myCommand, [arg1]);is listed undervalidwith the comment "Command variable (identifier) — not a string literal, out of scope".actions/setup/js/create_pull_request.test.cjs:3064—const cmdStr = \${cmd} ${(args || []).join(" ")}`;. A one-line refactor moving any of the many currentexec.exec(`git ... ${x}`, [])` sites into a variable would silently disable the check.Proposal
When
arguments[0]is anIdentifier, resolve a same-scope, write-once binding to its initializer and apply the existinggetDynamicCommandKindcheck to that initializer.Acceptance criteria
const cmd = \git checkout ${b}`; exec.exec(cmd, []);and the dynamic+`-concatenation equivalent.validcases stay valid — in particular a variable holding a static string (const cmd = "git"; exec.exec(cmd, [b]);) is not flagged.messageIdunchanged.+-concatenation-in-variable case (flagged).Non-duplicate
Not covered by any open or closed eslint issue; the direct-argument case already works — this closes the documented indirection gap.
Filed by ESLint Refiner (daily rule-quality refinement).