( decls: ParsedStyleDecl[] )
| 195 | * descendant/child combinators) go into nestedClassRules for tree matching. |
| 196 | */ |
| 197 | const classifyRules = ( |
| 198 | decls: ParsedStyleDecl[] |
| 199 | ): { |
| 200 | classRules: Map<string, ParsedStyleDecl[]>; |
| 201 | nestedClassRules: Map<string, NestedClassRule>; |
| 202 | rootRules: ParsedStyleDecl[]; |
| 203 | hasNonClassRules: boolean; |
| 204 | } => { |
| 205 | const classRules = new Map<string, ParsedStyleDecl[]>(); |
| 206 | const nestedClassRules = new Map<string, NestedClassRule>(); |
| 207 | const rootRules: ParsedStyleDecl[] = []; |
| 208 | let hasNonClassRules = false; |
| 209 | |
| 210 | for (const decl of decls) { |
| 211 | if (decl.selector === ":root") { |
| 212 | rootRules.push(decl); |
| 213 | continue; |
| 214 | } |
| 215 | const parsed = parseClassBasedSelector(decl.selector); |
| 216 | if (parsed !== undefined) { |
| 217 | const selectorState = parsed.states?.[0]; |
| 218 | const effectiveDecl = selectorState |
| 219 | ? { ...decl, state: decl.state ?? selectorState } |
| 220 | : decl; |
| 221 | |
| 222 | if (parsed.ancestors && parsed.ancestors.length > 0) { |
| 223 | // Nested selector — needs tree matching |
| 224 | let entry = nestedClassRules.get(parsed.tokenName); |
| 225 | if (!entry) { |
| 226 | entry = { parsed, decls: [], selector: decl.selector }; |
| 227 | nestedClassRules.set(parsed.tokenName, entry); |
| 228 | } |
| 229 | entry.decls.push(effectiveDecl); |
| 230 | } else { |
| 231 | // Simple class selector |
| 232 | let rules = classRules.get(parsed.tokenName); |
| 233 | if (!rules) { |
| 234 | rules = []; |
| 235 | classRules.set(parsed.tokenName, rules); |
| 236 | } |
| 237 | rules.push(effectiveDecl); |
| 238 | } |
| 239 | } else { |
| 240 | hasNonClassRules = true; |
| 241 | } |
| 242 | } |
| 243 | return { classRules, nestedClassRules, rootRules, hasNonClassRules }; |
| 244 | }; |
| 245 | |
| 246 | /** |
| 247 | * Build the leftover CSS (non-class rules) from the original style text. |
no test coverage detected