Removing $placeholder changes how an unquoted template attribute value, attribute name, or tag name parses (13 languages)
#4110 removed $placeholder: template tokens no longer leave an identifier-like stand-in in the code handed to the host grammar, they simply vanish. Its description records the consequence for three fixtures; this issue is to track it, since nothing else does.
Where a template expression is an entire unquoted value, the host grammar no longer sees a value there and stops parsing the construct around it. Quoted values are unaffected.
Repro
import { createInstance } from './tests/helper/prism-loader.js';
const prism = await createInstance(['php', 'markup', 'javascript', 'clike', 'css']);
console.log(prism.highlight('<span data-x=<?= $x ?>>a</span>', 'php'));v2 [tag [punctuation <][tag span] [attr-name data-x][attr-value [punctuation =][php …]][punctuation >]]
new <span data-x=[php …]>a… ← the whole opening tag is no longer a tagScope, measured
All 13 markup-templating languages — django, twig, handlebars, liquid, ejs, erb, etlua, tt2, smarty, ftl, latte, soy, php — in three positions:
| position | example | severity |
|---|---|---|
| unquoted attribute value | <span data-x={{x}}> |
the whole opening tag stops being a tag |
| tag name | <h{$level}> |
tag survives, name is just h |
| unquoted attribute name | <div {{on "click"}}> |
tag survives, loses the attr-name wrapper |
Quoted values (href="{{url}}") are identical to v2.
Of these, only the first loses a whole construct. latte and soy's fixtures changed in #4110 because their stripped text still parses; handlebars' fixture shows the tag being lost.
Not worth chasing
A template expression between JS regex delimiters — <script>var re = /{{p}}/;</script> — collapses to // and comments out the rest of that line. Same root cause, but nobody writes that: interpolating into a regex literal breaks on the first / or backslash in the value, so real code uses new RegExp('{{p}}'). Checked against a corpus of realistic template-in-script snippets (JSON blob, quoted string, bare number, condition, CSS value) — all byte-identical to v2.
Why it is a reasonable trade
Placeholders caused their own class of bugs, found in #4107: the stand-in being parsed as markdown emphasis, as a JS constant, or split in two by the host grammar. This is recorded for tracking, not to argue #4110 should be reverted.
Source: PrismJS/prism