Template Placeholder In Regular String
ID |
javascript.template_placeholder_in_regular_string |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
reliability, string, suspicious-literal |
Description
Reports a single- or double-quoted string that carries a ${…} placeholder. Interpolation only happens inside backticks. In a quoted string the placeholder is ordinary text, so the value the author meant to insert never appears and the literal braces reach the user instead.
const greeting = "Hello ${name}"; // FLAW — the output is literally: Hello ${name}
log('retry in ${delay}ms'); // FLAW
A placeholder needs a non-empty body and a closing brace to count, so an isolated "${" — a fragment being concatenated, or a documented syntax being described — is not reported. An escaped "\${name}" is deliberate and stays quiet.
${…} is also the placeholder syntax of a dozen other languages that keep their templates inside JavaScript strings — Knockout bindings, Magento UI components, AWS flow-log formats, GitHub Actions expressions, shell parameter expansion, SQL templates. None of those was ever meant to interpolate here, so a placeholder is reported only when it could actually have worked as one: the body has to parse, whole, as a JavaScript expression, it has to name something, and every name it reads has to resolve in a scope the string can see.
That is what tells the foreign syntaxes apart, one by one. ${{ … }} is not an expression at all. ${VAR:-false} is not one either — the whole body has to be consumed, and stopping after VAR does not count. ${account-id} does parse, as a subtraction, but neither account nor id is declared anywhere near. ${1} names nothing. What survives is ${name}, ${user.id} and ${a + b} — the expressions a developer reaching for a template literal actually writes.
A placeholder rooted at $ is left out whether or not the name resolves. $ is the sigil of the interpolation syntax itself, and every engine that borrows ${…} uses it, or a $-prefixed name, for its own context object rather than for anything in the host file: Knockout’s $data and $parent, Magento’s '${ $.provider }:data', Vue’s $props. Resolution alone cannot separate those, because in a file that also loads jQuery $ is a declared name — and what a backtick would then have interpolated is a library handle, [object Object], never the value the template meant.
Rationale
-
Nothing fails. There is no exception, no
undefined, no type error — the wrong text simply ships, which is exactly why the defect survives review and reaches production. -
The mistake is one character wide: a quote where a backtick belonged. It arrives when a template literal is pasted into code that already used quotes, or when a concatenation is rewritten into an interpolation and one string in the group is missed.
-
Placeholders show up in user-visible text far more often than anywhere else — greetings, error messages, log lines — so the failure is visible to exactly the audience that should never see it.