diff --git a/src/__tests__/sourceIndex.mjs b/src/__tests__/sourceIndex.mjs index 916458d..1010157 100644 --- a/src/__tests__/sourceIndex.mjs +++ b/src/__tests__/sourceIndex.mjs @@ -333,3 +333,33 @@ test("pseudo with arguments spanning multiple lines", "h1:not(\n\t.one,\n\t.two\ t.deepEqual(tree.nodes[0].nodes[1].nodes[1].nodes[0].source.end.column, 5, ".two end column"); t.deepEqual(tree.nodes[0].nodes[1].nodes[1].nodes[0].sourceIndex, 16, ".two sourceIndex"); }); + +test("namespaced tag starts at its namespace", "a ns|b", (t, tree) => { + const node = tree.nodes[0].nodes[2]; + t.deepEqual(node.sourceIndex, 2); + t.deepEqual(node.source.start.column, 3); + t.deepEqual(node.source.end.column, 6); +}); + +test("namespaced universal starts at its namespace", "*|*", (t, tree) => { + const node = tree.nodes[0].nodes[0]; + t.deepEqual(node.sourceIndex, 0); + t.deepEqual(node.source.start.column, 1); + t.deepEqual(node.source.end.column, 3); +}); + +test("tag with an empty namespace starts at the pipe", "|b.c", (t, tree) => { + const [tag, cls] = tree.nodes[0].nodes; + t.deepEqual(tag.sourceIndex, 0); + t.deepEqual(tag.source.start.column, 1); + t.deepEqual(tag.source.end.column, 2); + t.deepEqual(cls.sourceIndex, 2); + t.deepEqual(cls.source.start.column, 3); +}); + +test("namespaced tag on a later line starts at its namespace", "a,\n ns|b", (t, tree) => { + const node = tree.nodes[1].nodes[0]; + t.deepEqual(node.sourceIndex, 5); + t.deepEqual(node.source.start.line, 2); + t.deepEqual(node.source.start.column, 3); +}); diff --git a/src/parser.js b/src/parser.js index 24d35be..c5f14ec 100644 --- a/src/parser.js +++ b/src/parser.js @@ -848,15 +848,26 @@ export default class Parser { // `currToken`, which is the pipe itself and always present here. return this.unexpectedPipe(); } - if (this.nextToken[TOKEN.TYPE] === tokens.word) { - this.position++; - return this.word(before); - } else if (this.nextToken[TOKEN.TYPE] === tokens.asterisk) { - this.position++; - return this.universal(before); + const type = this.nextToken[TOKEN.TYPE]; + if (type !== tokens.word && type !== tokens.asterisk) { + return this.unexpectedPipe(); + } + // The node starts at its prefix, or at the `|` when there is none, but + // `word()` and `universal()` only see the name after the `|`. + const start = before === true ? this.currToken : prev; + const nodes = this.current.nodes; + const index = nodes.length; + this.position++; + if (type === tokens.word) { + this.word(before); + } else { + this.universal(before); + } + const node = nodes[index]; + if (node) { + node.sourceIndex = start[TOKEN.START_POS]; + node.source.start = { line: start[TOKEN.START_LINE], column: start[TOKEN.START_COL] }; } - - this.unexpectedPipe(); } nesting() {