Skip to content

Commit a5756dc

Browse files
committed
rewrite parseCss to support nesting
1 parent 06872a5 commit a5756dc

File tree

2 files changed

+63
-32
lines changed

2 files changed

+63
-32
lines changed

packages/css-data/src/parse-css.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,18 @@ describe("Parse CSS", () => {
194194
]);
195195
});
196196

197+
test("attribute selector", () => {
198+
expect(parseCss(`[class^="a"] { color: #ff0000 }`)).toEqual([
199+
{
200+
selector: '[class^="a"]',
201+
property: "color",
202+
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
203+
},
204+
]);
205+
});
206+
197207
test("parse first pseudo class as selector", () => {
208+
// E.g. :root
198209
expect(parseCss(`:first-pseudo:my-state { color: #ff0000 }`)).toEqual([
199210
{
200211
selector: ":first-pseudo",
@@ -232,7 +243,7 @@ describe("Parse CSS", () => {
232243
]);
233244
});
234245

235-
test.only("parse multiple selectors, both with state", () => {
246+
test("parse multiple selectors, both with state", () => {
236247
expect(parseCss(`a:active, a:hover { color: #ff0000 }`)).toEqual([
237248
{
238249
selector: "a",

packages/css-data/src/parse-css.ts

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -169,43 +169,63 @@ export const parseCss = (css: string, options: ParserOptions = {}) => {
169169
breakpoint = generated;
170170
}
171171
}
172-
if (invalidBreakpoint) {
172+
if (invalidBreakpoint || this.rule.prelude.type !== "SelectorList") {
173173
return;
174174
}
175175

176176
const selectors: Selector[] = [];
177-
if (this.rule.prelude.type === "SelectorList") {
178-
for (const selector of this.rule.prelude.children) {
179-
if (selector.type !== "Selector" || selector.children.size > 2) {
180-
continue;
181-
}
182-
const [nameNode, stateNode] = selector.children;
183-
let name;
184-
if (
185-
nameNode.type === "ClassSelector" ||
186-
nameNode.type === "TypeSelector"
187-
) {
188-
name = nameNode.name;
189-
} else if (nameNode.type === "PseudoClassSelector") {
190-
name = `:${nameNode.name}`;
191-
} else {
192-
continue;
177+
178+
for (const node of this.rule.prelude.children) {
179+
if (node.type !== "Selector") {
180+
continue;
181+
}
182+
let selector: Selector | undefined = undefined;
183+
node.children.forEach((node) => {
184+
let name: string = "";
185+
let state: string | undefined;
186+
switch (node.type) {
187+
case "TypeSelector":
188+
name = node.name;
189+
break;
190+
case "ClassSelector":
191+
// .a {} vs a.b {}
192+
name = selector ? `.${node.name}` : node.name;
193+
break;
194+
case "AttributeSelector":
195+
if (node.value) {
196+
name = `[${csstree.generate(node.name)}${node.matcher}${csstree.generate(node.value)}]`;
197+
}
198+
break;
199+
case "PseudoClassSelector": {
200+
// First pseudo selector is not a state but an element selector, e.g. :root
201+
if (selector) {
202+
state = `:${node.name}`;
203+
} else {
204+
name = `:${node.name}`;
205+
}
206+
break;
207+
}
208+
case "Combinator":
209+
// " " vs " > "
210+
name = node.name === " " ? node.name : ` ${node.name} `;
211+
break;
212+
case "PseudoElementSelector":
213+
state = `::${node.name}`;
214+
break;
193215
}
194-
if (stateNode?.type === "PseudoClassSelector") {
195-
selectors.push({
196-
name,
197-
state: `:${stateNode.name}`,
198-
});
199-
} else if (stateNode?.type === "PseudoElementSelector") {
200-
selectors.push({
201-
name,
202-
state: `::${stateNode.name}`,
203-
});
204-
} else {
205-
selectors.push({
206-
name,
207-
});
216+
217+
if (selector) {
218+
selector.name += name;
219+
if (state) {
220+
selector.state = state;
221+
}
222+
return;
208223
}
224+
selector = { name, state };
225+
});
226+
if (selector) {
227+
selectors.push(selector);
228+
selector = undefined;
209229
}
210230
}
211231

0 commit comments

Comments
 (0)