diff --git a/src/__tests__/nonstandard.mjs b/src/__tests__/nonstandard.mjs index 064bc3d..8990eac 100644 --- a/src/__tests__/nonstandard.mjs +++ b/src/__tests__/nonstandard.mjs @@ -31,6 +31,41 @@ test("sass escapes (2)", "[lang=#{$locale}]", (t, tree) => { t.deepEqual(tree.nodes[0].nodes[0].value, "#{$locale}"); }); +test("sass interpolation as an attribute name", "[#{$attr}]", (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[0].type, "attribute"); + t.deepEqual(tree.nodes[0].nodes[0].attribute, "#{$attr}"); + t.deepEqual(tree.nodes[0].nodes[0].operator, undefined); + t.deepEqual(tree.nodes[0].nodes[0].value, undefined); +}); + +test("sass variable as an attribute name", "[$attr]", (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[0].attribute, "$attr"); + t.deepEqual(tree.nodes[0].nodes[0].operator, undefined); +}); + +test( + "sass interpolation on both sides of an attribute selector", + "[#{$attr}=#{$value}]", + (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[0].attribute, "#{$attr}"); + t.deepEqual(tree.nodes[0].nodes[0].operator, "="); + t.deepEqual(tree.nodes[0].nodes[0].value, "#{$value}"); + }, +); + +// The `$` opening the name must not be mistaken for the suffix match operator, +// and the `$=` that follows must still parse as one. +test("sass variable name with a suffix match operator", '[$attr$="x"]', (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[0].attribute, "$attr"); + t.deepEqual(tree.nodes[0].nodes[0].operator, "$="); + t.deepEqual(tree.nodes[0].nodes[0].value, "x"); +}); + +test("namespaced sass variable attribute name", "[ns|$attr]", (t, tree) => { + t.deepEqual(tree.nodes[0].nodes[0].namespace, "ns"); + t.deepEqual(tree.nodes[0].nodes[0].attribute, "$attr"); +}); + test("placeholder", "%foo", (t, tree) => { t.deepEqual(tree.nodes[0].nodes[0].type, "tag"); t.deepEqual(tree.nodes[0].nodes[0].value, "%foo"); diff --git a/src/parser.js b/src/parser.js index bcec5ac..cd993e9 100644 --- a/src/parser.js +++ b/src/parser.js @@ -254,6 +254,22 @@ export default class Parser { } break; } + if ( + (lastAdded === "attribute" || !node.attribute) && + !(next && next[TOKEN.TYPE] === tokens.equals) + ) { + // A `$` that is not followed by `=` is part of the attribute name + // rather than the suffix match operator. Preprocessors emit these + // through interpolation, e.g. `[#{$var}]` in SCSS. + let oldRawAttribute = getProp(node, "raws", "attribute"); + node.attribute = (node.attribute || "") + "$"; + if (oldRawAttribute) { + node.raws.attribute = oldRawAttribute + "$"; + } + lastAdded = "attribute"; + spaceAfterMeaningfulToken = false; + break; + } // Falls through case tokens.caret: if (next[TOKEN.TYPE] === tokens.equals) {