From f231e78be5b8edaae62e93fb728fc2d6f505a2bd Mon Sep 17 00:00:00 2001 From: Yarchik Date: Wed, 17 Jun 2026 15:31:25 +0100 Subject: [PATCH] fix: don't treat a non-prefix token before `|` as a namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `namespace()` used the previous token's content as the namespace prefix whenever a `|` was seen. For an empty namespace written as a bare `|` (e.g. `|b`), the previous token is whatever precedes it — a comment, comma, combinator or whitespace — and that was emitted as the prefix. So `/* c */|b` round-tripped to `/* c */\/\*\ c\ \*\/|b`, `.a,|b` to `.a,\,|b`, `.a > |b` to `.a > |b`, and so on. Only use the previous token as a prefix when it can be one: a type/word, the universal `*`, or the nesting `&` (the tokens whose parsing hands off to `namespace()`). Otherwise the namespace is empty. --- src/__tests__/namespaces.mjs | 15 +++++++++++++++ src/parser.js | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/__tests__/namespaces.mjs b/src/__tests__/namespaces.mjs index 46e66b6..50cdde1 100644 --- a/src/__tests__/namespaces.mjs +++ b/src/__tests__/namespaces.mjs @@ -77,6 +77,21 @@ test("ns alias for namespace", "f\\oo|h1.foo", (t, tree) => { t.deepEqual(tag.ns, "bar"); }); +test("empty namespace after a comment is not a prefix", "/* c */|b", (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[1].namespace, true); + t.deepEqual(tree.nodes[0].nodes[1].value, "b"); +}); + +test("empty namespace after a comma is not a prefix", ".a,|b", (t, tree) => { + t.deepEqual(tree.nodes[1].nodes[0].namespace, true); + t.deepEqual(tree.nodes[1].nodes[0].value, "b"); +}); + +test("empty namespace after a combinator is not a prefix", ".a > |b", (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[2].namespace, true); + t.deepEqual(tree.nodes[0].nodes[2].value, "b"); +}); + throws("lone pipe symbol", "|"); throws("lone pipe symbol with leading spaces", " |"); throws("lone pipe symbol with trailing spaces", "| "); diff --git a/src/parser.js b/src/parser.js index 3b00836..efc1789 100644 --- a/src/parser.js +++ b/src/parser.js @@ -696,7 +696,17 @@ export default class Parser { } namespace() { - const before = (this.prevToken && this.content(this.prevToken)) || true; + const prev = this.prevToken; + // Only treat the previous token as a namespace prefix when it can actually + // be one (a type/word or the universal `*`). A comment, comma, combinator + // or whitespace before `|` means an empty namespace, not a prefix. + const before = + prev && + (prev[TOKEN.TYPE] === tokens.word || + prev[TOKEN.TYPE] === tokens.asterisk || + prev[TOKEN.TYPE] === tokens.ampersand) + ? this.content(prev) + : true; if (this.nextToken[TOKEN.TYPE] === tokens.word) { this.position++; return this.word(before);