Skip to content

Parse Object.prototype names as ordinary names in liquid-html-parser - #1331

Open
PhilippeCollin wants to merge 1 commit into
mainfrom
liquid-html-parser-prototype-names
Open

PhilippeCollin wants to merge 1 commit into
mainfrom
liquid-html-parser-prototype-names

Conversation

@PhilippeCollin

@PhilippeCollin PhilippeCollin commented Oct 8, 2026 •

Copy link
Copy Markdown

Why

The parser looks up tag names and keywords in plain JavaScript objects. Plain objects come with built-in properties such as constructor and toString. So when a template uses one of those names, the parser finds the JavaScript built-in instead of treating it as an ordinary name:

{% foo %}           → an unknown tag, as expected
{% constructor %}   → throws "Unexpected object: undefined", even in the tolerant parser
{{ constructor }}   → a Liquid literal whose value is a JavaScript function, not a variable

Inside {% liquid %}, the same names leave the whole block unparsed. This happens in two places: isLiteralKeyword (checking for true, nil and the other literals) and Environment.tagForName (looking up built-in tags).

Templates rarely use these names, but theme-check and the Prettier plugin should treat them like any other unknown name, not crash.

What

  • Literal keywords are recognized with a switch on the six keywords. A new test checks that every key of LiquidLiteralValues is still recognized, so the two can't drift apart silently.
  • Built-in tags are stored in a Map (Environment isn't exported from the package).

{{ constructor }} now parses as a variable, and {% constructor %} parses as an unknown tag, both in documents and in {% liquid %} lines.

The switch is also faster than in on this hot path. Parsing the 429 fixture files (Dawn, Horizon, base theme; M3 Pro, Node 24.15; median of 5 alternating rounds × 30 passes) saves about 2 ms per pass:

toLiquidHtmlAST, all 429 files before after Time saved
On main 95.9 ms 93.8 ms 2%
On top of #1329 42.4 ms 40.3 ms 5%

Other ASTs are unchanged: a differential run against main over about 25,700 inputs (every .liquid file in the repo, plus random and mutated templates) found 0 differences. liquid-html-parser, prettier-plugin-liquid and theme-check-common tests pass.

Related: #1329, #1330. Found while profiling after the tokenizer change.

`isLiteralKeyword` used `s in LiquidLiteralValues`, and `tagForName`
read `this.builtins[name]` from a plain object. Both also see inherited
properties, so `{{ constructor }}` became a LiquidLiteral whose value
was the Object function, and `{% constructor %}` / `{% __proto__ %}`
resolved to a non-tag "definition" and threw.

Recognize literal keywords with a switch (also faster than `in` on this
hot path) and store builtin tags in a Map. A test checks every
LiquidLiteralValues key is still recognized.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant