Skip to content

Commit e2f9029

Browse files
authored
fix: don't treat a non-prefix token before | as a namespace (#324)
`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.
1 parent dd50ee1 commit e2f9029

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

‎src/__tests__/namespaces.mjs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ test("ns alias for namespace", "f\\oo|h1.foo", (t, tree) => {
7777
t.deepEqual(tag.ns, "bar");
7878
});
7979

80+
test("empty namespace after a comment is not a prefix", "/* c */|b", (t, tree) => {
81+
t.deepEqual(tree.nodes[0].nodes[1].namespace, true);
82+
t.deepEqual(tree.nodes[0].nodes[1].value, "b");
83+
});
84+
85+
test("empty namespace after a comma is not a prefix", ".a,|b", (t, tree) => {
86+
t.deepEqual(tree.nodes[1].nodes[0].namespace, true);
87+
t.deepEqual(tree.nodes[1].nodes[0].value, "b");
88+
});
89+
90+
test("empty namespace after a combinator is not a prefix", ".a > |b", (t, tree) => {
91+
t.deepEqual(tree.nodes[0].nodes[2].namespace, true);
92+
t.deepEqual(tree.nodes[0].nodes[2].value, "b");
93+
});
94+
8095
throws("lone pipe symbol", "|");
8196
throws("lone pipe symbol with leading spaces", " |");
8297
throws("lone pipe symbol with trailing spaces", "| ");

‎src/parser.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,17 @@ export default class Parser {
696696
}
697697

698698
namespace() {
699-
const before = (this.prevToken && this.content(this.prevToken)) || true;
699+
const prev = this.prevToken;
700+
// Only treat the previous token as a namespace prefix when it can actually
701+
// be one (a type/word or the universal `*`). A comment, comma, combinator
702+
// or whitespace before `|` means an empty namespace, not a prefix.
703+
const before =
704+
prev &&
705+
(prev[TOKEN.TYPE] === tokens.word ||
706+
prev[TOKEN.TYPE] === tokens.asterisk ||
707+
prev[TOKEN.TYPE] === tokens.ampersand)
708+
? this.content(prev)
709+
: true;
700710
if (this.nextToken[TOKEN.TYPE] === tokens.word) {
701711
this.position++;
702712
return this.word(before);

0 commit comments

Comments
 (0)