diff --git a/packages/css-data/src/parse-css.test.ts b/packages/css-data/src/parse-css.test.ts index f18adb595fca..6d6e662b25c2 100644 --- a/packages/css-data/src/parse-css.test.ts +++ b/packages/css-data/src/parse-css.test.ts @@ -5,7 +5,7 @@ describe("Parse CSS", () => { test("longhand property name with keyword value", () => { expect(parseCss(`.test { background-color: red }`)).toEqual([ { - selector: "test", + selector: ".test", property: "backgroundColor", value: { type: "keyword", value: "red" }, }, @@ -15,7 +15,7 @@ describe("Parse CSS", () => { test("one class selector rules", () => { expect(parseCss(`.test { color: #ff0000 }`)).toEqual([ { - selector: "test", + selector: ".test", property: "color", value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" }, }, @@ -30,7 +30,7 @@ describe("Parse CSS", () => { `; expect(parseCss(css)).toEqual([ { - selector: "test", + selector: ".test", property: "backgroundImage", value: { type: "layers", @@ -45,7 +45,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundPositionX", value: { type: "layers", @@ -56,7 +56,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundPositionY", value: { type: "layers", @@ -67,7 +67,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundSize", value: { type: "layers", @@ -90,7 +90,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundRepeat", value: { type: "layers", @@ -101,7 +101,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundAttachment", value: { type: "layers", @@ -112,7 +112,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundOrigin", value: { type: "layers", @@ -123,7 +123,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundClip", value: { type: "layers", @@ -134,7 +134,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundColor", value: { alpha: 1, b: 252, g: 255, r: 235, type: "rgb" }, }, @@ -149,7 +149,7 @@ describe("Parse CSS", () => { `; expect(parseCss(css)).toEqual([ { - selector: "test", + selector: ".test", property: "backgroundImage", value: { type: "layers", @@ -157,7 +157,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundPositionX", value: { type: "layers", @@ -165,7 +165,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundPositionY", value: { type: "layers", @@ -173,7 +173,7 @@ describe("Parse CSS", () => { }, }, { - selector: "test", + selector: ".test", property: "backgroundSize", value: { type: "layers", @@ -194,7 +194,18 @@ describe("Parse CSS", () => { ]); }); + test("attribute selector", () => { + expect(parseCss(`[class^="a"] { color: #ff0000 }`)).toEqual([ + { + selector: '[class^="a"]', + property: "color", + value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" }, + }, + ]); + }); + test("parse first pseudo class as selector", () => { + // E.g. :root expect(parseCss(`:first-pseudo:my-state { color: #ff0000 }`)).toEqual([ { selector: ":first-pseudo", @@ -471,11 +482,33 @@ describe("Parse CSS", () => { }); test("parse child combinator", () => { - expect(parseCss(`a > b { color: #ff0000 }`)).toEqual([]); + expect(parseCss(`a > b { color: #ff0000 }`)).toEqual([ + { + selector: "a > b", + property: "color", + value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" }, + }, + ]); }); test("parse space combinator", () => { - expect(parseCss(`a b { color: #ff0000 }`)).toEqual([]); + expect(parseCss(`.a b { color: #ff0000 }`)).toEqual([ + { + selector: ".a b", + property: "color", + value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" }, + }, + ]); + }); + + test("parse nested selectors as one token", () => { + expect(parseCss(`a b c.d { color: #ff0000 }`)).toEqual([ + { + selector: "a b c.d", + property: "color", + value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" }, + }, + ]); }); }); diff --git a/packages/css-data/src/parse-css.ts b/packages/css-data/src/parse-css.ts index 9efb238c115a..1f4fc285ccef 100644 --- a/packages/css-data/src/parse-css.ts +++ b/packages/css-data/src/parse-css.ts @@ -161,6 +161,7 @@ export const parseCss = (css: string, options: ParserOptions = {}) => { if (node.type === "MediaQuery" && node.children.size > 1) { invalidBreakpoint = true; } + ``; }, }); const generated = csstree.generate(this.atrule.prelude); @@ -168,44 +169,62 @@ export const parseCss = (css: string, options: ParserOptions = {}) => { breakpoint = generated; } } - if (invalidBreakpoint) { + if (invalidBreakpoint || this.rule.prelude.type !== "SelectorList") { return; } const selectors: Selector[] = []; - if (this.rule.prelude.type === "SelectorList") { - for (const selector of this.rule.prelude.children) { - if (selector.type !== "Selector" || selector.children.size > 2) { - continue; - } - const [nameNode, stateNode] = selector.children; - let name; - if ( - nameNode.type === "ClassSelector" || - nameNode.type === "TypeSelector" - ) { - name = nameNode.name; - } else if (nameNode.type === "PseudoClassSelector") { - name = `:${nameNode.name}`; - } else { - continue; + + for (const node of this.rule.prelude.children) { + if (node.type !== "Selector") { + continue; + } + let selector: Selector | undefined = undefined; + for (const childNode of node.children) { + let name: string = ""; + let state: string | undefined; + switch (childNode.type) { + case "TypeSelector": + name = childNode.name; + break; + case "ClassSelector": + name = `.${childNode.name}`; + break; + case "AttributeSelector": + name = csstree.generate(childNode); + break; + case "PseudoClassSelector": { + // First pseudo selector is not a state but an element selector, e.g. :root + if (selector) { + state = `:${childNode.name}`; + } else { + name = `:${childNode.name}`; + } + break; + } + case "PseudoElementSelector": + state = `::${childNode.name}`; + break; + case "Combinator": + // " " vs " > " + name = + childNode.name === " " ? childNode.name : ` ${childNode.name} `; + break; } - if (stateNode?.type === "PseudoClassSelector") { - selectors.push({ - name, - state: `:${stateNode.name}`, - }); - } else if (stateNode?.type === "PseudoElementSelector") { - selectors.push({ - name, - state: `::${stateNode.name}`, - }); + + if (selector) { + selector.name += name; + if (state) { + selector.state = state; + } } else { - selectors.push({ - name, - }); + selector = { name, state }; } } + if (selector) { + selectors.push(selector); + selector = undefined; + } } const stringValue = csstree.generate(node.value);