we (web engine): Experimental web browser project to understand the limits of Claude
at encoding-sniffing 1484 lines 46 kB view raw
1//! CSS parser per CSS Syntax Module Level 3 §5. 2//! 3//! Consumes tokens from the tokenizer and produces a structured stylesheet. 4 5use crate::tokenizer::{HashType, NumericType, Token, Tokenizer}; 6 7// --------------------------------------------------------------------------- 8// AST types 9// --------------------------------------------------------------------------- 10 11/// A parsed CSS stylesheet: a list of rules. 12#[derive(Debug, Clone, PartialEq)] 13pub struct Stylesheet { 14 pub rules: Vec<Rule>, 15} 16 17/// A top-level rule: either a style rule or an at-rule. 18#[derive(Debug, Clone, PartialEq)] 19pub enum Rule { 20 Style(StyleRule), 21 Media(MediaRule), 22 Import(ImportRule), 23} 24 25/// A style rule: selector list + declarations. 26#[derive(Debug, Clone, PartialEq)] 27pub struct StyleRule { 28 pub selectors: SelectorList, 29 pub declarations: Vec<Declaration>, 30} 31 32/// A `@media` rule with a prelude (media query text) and nested rules. 33#[derive(Debug, Clone, PartialEq)] 34pub struct MediaRule { 35 pub query: String, 36 pub rules: Vec<Rule>, 37} 38 39/// An `@import` rule with a URL. 40#[derive(Debug, Clone, PartialEq)] 41pub struct ImportRule { 42 pub url: String, 43} 44 45/// A comma-separated list of selectors. 46#[derive(Debug, Clone, PartialEq)] 47pub struct SelectorList { 48 pub selectors: Vec<Selector>, 49} 50 51/// A complex selector: a chain of compound selectors joined by combinators. 52#[derive(Debug, Clone, PartialEq)] 53pub struct Selector { 54 pub components: Vec<SelectorComponent>, 55} 56 57/// Either a compound selector or a combinator between compounds. 58#[derive(Debug, Clone, PartialEq)] 59pub enum SelectorComponent { 60 Compound(CompoundSelector), 61 Combinator(Combinator), 62} 63 64/// A compound selector: a sequence of simple selectors with no combinator. 65#[derive(Debug, Clone, PartialEq)] 66pub struct CompoundSelector { 67 pub simple: Vec<SimpleSelector>, 68} 69 70/// A simple selector. 71#[derive(Debug, Clone, PartialEq)] 72pub enum SimpleSelector { 73 Type(String), 74 Universal, 75 Class(String), 76 Id(String), 77 Attribute(AttributeSelector), 78 PseudoClass(String), 79} 80 81/// An attribute selector. 82#[derive(Debug, Clone, PartialEq)] 83pub struct AttributeSelector { 84 pub name: String, 85 pub op: Option<AttributeOp>, 86 pub value: Option<String>, 87} 88 89/// Attribute matching operator. 90#[derive(Debug, Clone, Copy, PartialEq, Eq)] 91pub enum AttributeOp { 92 /// `=` 93 Exact, 94 /// `~=` 95 Includes, 96 /// `|=` 97 DashMatch, 98 /// `^=` 99 Prefix, 100 /// `$=` 101 Suffix, 102 /// `*=` 103 Substring, 104} 105 106/// A combinator between compound selectors. 107#[derive(Debug, Clone, Copy, PartialEq, Eq)] 108pub enum Combinator { 109 /// Descendant (whitespace) 110 Descendant, 111 /// Child (`>`) 112 Child, 113 /// Adjacent sibling (`+`) 114 AdjacentSibling, 115 /// General sibling (`~`) 116 GeneralSibling, 117} 118 119/// A CSS property declaration. 120#[derive(Debug, Clone, PartialEq)] 121pub struct Declaration { 122 pub property: String, 123 pub value: Vec<ComponentValue>, 124 pub important: bool, 125} 126 127/// A component value in a declaration value. We store these as typed tokens 128/// so downstream code can interpret them without re-tokenizing. 129#[derive(Debug, Clone, PartialEq)] 130pub enum ComponentValue { 131 Ident(String), 132 String(String), 133 Number(f64, NumericType), 134 Percentage(f64), 135 Dimension(f64, NumericType, String), 136 Hash(String, HashType), 137 Function(String, Vec<ComponentValue>), 138 Delim(char), 139 Comma, 140 Whitespace, 141} 142 143// --------------------------------------------------------------------------- 144// Parser 145// --------------------------------------------------------------------------- 146 147/// CSS parser. Consumes tokens and produces AST. 148pub struct Parser { 149 tokens: Vec<Token>, 150 pos: usize, 151} 152 153impl Parser { 154 /// Parse a full stylesheet from CSS source text. 155 pub fn parse(input: &str) -> Stylesheet { 156 let tokens = Tokenizer::tokenize(input); 157 let mut parser = Self { tokens, pos: 0 }; 158 parser.parse_stylesheet() 159 } 160 161 /// Parse a list of declarations (for `style` attribute values). 162 pub fn parse_declarations(input: &str) -> Vec<Declaration> { 163 let tokens = Tokenizer::tokenize(input); 164 let mut parser = Self { tokens, pos: 0 }; 165 parser.parse_declaration_list() 166 } 167 168 /// Parse a selector list from a string (for `querySelector`/`querySelectorAll`). 169 pub fn parse_selectors(input: &str) -> SelectorList { 170 let tokens = Tokenizer::tokenize(input); 171 let mut parser = Self { tokens, pos: 0 }; 172 parser.parse_selector_list() 173 } 174 175 // -- Token access ------------------------------------------------------- 176 177 fn peek(&self) -> &Token { 178 self.tokens.get(self.pos).unwrap_or(&Token::Eof) 179 } 180 181 fn advance(&mut self) -> Token { 182 let tok = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof); 183 if self.pos < self.tokens.len() { 184 self.pos += 1; 185 } 186 tok 187 } 188 189 fn is_eof(&self) -> bool { 190 self.pos >= self.tokens.len() 191 } 192 193 fn skip_whitespace(&mut self) { 194 while matches!(self.peek(), Token::Whitespace) { 195 self.advance(); 196 } 197 } 198 199 // -- Stylesheet parsing ------------------------------------------------- 200 201 fn parse_stylesheet(&mut self) -> Stylesheet { 202 let mut rules = Vec::new(); 203 loop { 204 self.skip_whitespace(); 205 if self.is_eof() { 206 break; 207 } 208 match self.peek() { 209 Token::AtKeyword(_) => { 210 if let Some(rule) = self.parse_at_rule() { 211 rules.push(rule); 212 } 213 } 214 Token::Cdo | Token::Cdc => { 215 self.advance(); 216 } 217 _ => { 218 if let Some(rule) = self.parse_style_rule() { 219 rules.push(Rule::Style(rule)); 220 } 221 } 222 } 223 } 224 Stylesheet { rules } 225 } 226 227 // -- At-rules ----------------------------------------------------------- 228 229 fn parse_at_rule(&mut self) -> Option<Rule> { 230 let name = match self.advance() { 231 Token::AtKeyword(name) => name, 232 _ => return None, 233 }; 234 235 match name.to_ascii_lowercase().as_str() { 236 "media" => self.parse_media_rule(), 237 "import" => self.parse_import_rule(), 238 _ => { 239 // Unknown at-rule: skip to end of block or semicolon 240 self.skip_at_rule_body(); 241 None 242 } 243 } 244 } 245 246 fn parse_media_rule(&mut self) -> Option<Rule> { 247 self.skip_whitespace(); 248 249 // Collect prelude tokens as the media query string 250 let mut query = String::new(); 251 loop { 252 match self.peek() { 253 Token::LeftBrace | Token::Eof => break, 254 Token::Whitespace => { 255 if !query.is_empty() { 256 query.push(' '); 257 } 258 self.advance(); 259 } 260 _ => { 261 let tok = self.advance(); 262 query.push_str(&token_to_string(&tok)); 263 } 264 } 265 } 266 267 // Expect `{` 268 if !matches!(self.peek(), Token::LeftBrace) { 269 return None; 270 } 271 self.advance(); 272 273 // Parse nested rules until `}` 274 let mut rules = Vec::new(); 275 loop { 276 self.skip_whitespace(); 277 if self.is_eof() { 278 break; 279 } 280 if matches!(self.peek(), Token::RightBrace) { 281 self.advance(); 282 break; 283 } 284 match self.peek() { 285 Token::AtKeyword(_) => { 286 if let Some(rule) = self.parse_at_rule() { 287 rules.push(rule); 288 } 289 } 290 _ => { 291 if let Some(rule) = self.parse_style_rule() { 292 rules.push(Rule::Style(rule)); 293 } 294 } 295 } 296 } 297 298 Some(Rule::Media(MediaRule { 299 query: query.trim().to_string(), 300 rules, 301 })) 302 } 303 304 fn parse_import_rule(&mut self) -> Option<Rule> { 305 self.skip_whitespace(); 306 let url = match self.peek() { 307 Token::String(_) => { 308 if let Token::String(s) = self.advance() { 309 s 310 } else { 311 unreachable!() 312 } 313 } 314 Token::Url(_) => { 315 if let Token::Url(s) = self.advance() { 316 s 317 } else { 318 unreachable!() 319 } 320 } 321 Token::Function(name) if name.eq_ignore_ascii_case("url") => { 322 self.advance(); 323 self.skip_whitespace(); 324 let url = match self.advance() { 325 Token::String(s) => s, 326 _ => { 327 self.skip_to_semicolon(); 328 return None; 329 } 330 }; 331 self.skip_whitespace(); 332 // consume closing paren 333 if matches!(self.peek(), Token::RightParen) { 334 self.advance(); 335 } 336 url 337 } 338 _ => { 339 self.skip_to_semicolon(); 340 return None; 341 } 342 }; 343 // Consume optional trailing tokens until semicolon 344 self.skip_to_semicolon(); 345 Some(Rule::Import(ImportRule { url })) 346 } 347 348 fn skip_at_rule_body(&mut self) { 349 let mut brace_depth = 0; 350 loop { 351 match self.peek() { 352 Token::Eof => return, 353 Token::Semicolon if brace_depth == 0 => { 354 self.advance(); 355 return; 356 } 357 Token::LeftBrace => { 358 brace_depth += 1; 359 self.advance(); 360 } 361 Token::RightBrace => { 362 if brace_depth == 0 { 363 self.advance(); 364 return; 365 } 366 brace_depth -= 1; 367 self.advance(); 368 if brace_depth == 0 { 369 return; 370 } 371 } 372 _ => { 373 self.advance(); 374 } 375 } 376 } 377 } 378 379 fn skip_to_semicolon(&mut self) { 380 loop { 381 match self.peek() { 382 Token::Eof | Token::Semicolon => { 383 if matches!(self.peek(), Token::Semicolon) { 384 self.advance(); 385 } 386 return; 387 } 388 _ => { 389 self.advance(); 390 } 391 } 392 } 393 } 394 395 // -- Style rules -------------------------------------------------------- 396 397 fn parse_style_rule(&mut self) -> Option<StyleRule> { 398 let selectors = self.parse_selector_list(); 399 400 // Expect `{` 401 self.skip_whitespace(); 402 if !matches!(self.peek(), Token::LeftBrace) { 403 // Error recovery: skip to end of block or next rule 404 self.skip_at_rule_body(); 405 return None; 406 } 407 self.advance(); 408 409 let declarations = self.parse_declaration_list_until_brace(); 410 411 // Consume `}` 412 if matches!(self.peek(), Token::RightBrace) { 413 self.advance(); 414 } 415 416 if selectors.selectors.is_empty() { 417 return None; 418 } 419 420 Some(StyleRule { 421 selectors, 422 declarations, 423 }) 424 } 425 426 // -- Selector parsing --------------------------------------------------- 427 428 fn parse_selector_list(&mut self) -> SelectorList { 429 let mut selectors = Vec::new(); 430 self.skip_whitespace(); 431 432 if let Some(sel) = self.parse_selector() { 433 selectors.push(sel); 434 } 435 436 loop { 437 self.skip_whitespace(); 438 if !matches!(self.peek(), Token::Comma) { 439 break; 440 } 441 self.advance(); // consume comma 442 self.skip_whitespace(); 443 if let Some(sel) = self.parse_selector() { 444 selectors.push(sel); 445 } 446 } 447 448 SelectorList { selectors } 449 } 450 451 fn parse_selector(&mut self) -> Option<Selector> { 452 let mut components = Vec::new(); 453 let mut last_was_compound = false; 454 let mut had_whitespace = false; 455 456 loop { 457 // Check for end of selector 458 match self.peek() { 459 Token::Comma 460 | Token::LeftBrace 461 | Token::RightBrace 462 | Token::Semicolon 463 | Token::Eof => break, 464 Token::Whitespace => { 465 had_whitespace = true; 466 self.advance(); 467 continue; 468 } 469 _ => {} 470 } 471 472 // Check for explicit combinator 473 let combinator = match self.peek() { 474 Token::Delim('>') => { 475 self.advance(); 476 Some(Combinator::Child) 477 } 478 Token::Delim('+') => { 479 self.advance(); 480 Some(Combinator::AdjacentSibling) 481 } 482 Token::Delim('~') => { 483 self.advance(); 484 Some(Combinator::GeneralSibling) 485 } 486 _ => None, 487 }; 488 489 if let Some(comb) = combinator { 490 if last_was_compound { 491 components.push(SelectorComponent::Combinator(comb)); 492 last_was_compound = false; 493 had_whitespace = false; 494 } 495 self.skip_whitespace(); 496 continue; 497 } 498 499 // Implicit descendant combinator if there was whitespace 500 if had_whitespace && last_was_compound { 501 components.push(SelectorComponent::Combinator(Combinator::Descendant)); 502 had_whitespace = false; 503 } 504 505 // Parse compound selector 506 if let Some(compound) = self.parse_compound_selector() { 507 components.push(SelectorComponent::Compound(compound)); 508 last_was_compound = true; 509 } else { 510 break; 511 } 512 } 513 514 if components.is_empty() { 515 None 516 } else { 517 Some(Selector { components }) 518 } 519 } 520 521 fn parse_compound_selector(&mut self) -> Option<CompoundSelector> { 522 let mut simple = Vec::new(); 523 524 loop { 525 match self.peek() { 526 // Type or universal 527 Token::Ident(_) if simple.is_empty() || !has_type_or_universal(&simple) => { 528 if let Token::Ident(name) = self.advance() { 529 simple.push(SimpleSelector::Type(name.to_ascii_lowercase())); 530 } 531 } 532 Token::Delim('*') if simple.is_empty() || !has_type_or_universal(&simple) => { 533 self.advance(); 534 simple.push(SimpleSelector::Universal); 535 } 536 // Class 537 Token::Delim('.') => { 538 self.advance(); 539 match self.advance() { 540 Token::Ident(name) => simple.push(SimpleSelector::Class(name)), 541 _ => break, 542 } 543 } 544 // ID 545 Token::Hash(_, HashType::Id) => { 546 if let Token::Hash(name, _) = self.advance() { 547 simple.push(SimpleSelector::Id(name)); 548 } 549 } 550 // Attribute 551 Token::LeftBracket => { 552 self.advance(); 553 if let Some(attr) = self.parse_attribute_selector() { 554 simple.push(SimpleSelector::Attribute(attr)); 555 } 556 } 557 // Pseudo-class 558 Token::Colon => { 559 self.advance(); 560 match self.advance() { 561 Token::Ident(name) => { 562 simple.push(SimpleSelector::PseudoClass(name.to_ascii_lowercase())) 563 } 564 Token::Function(name) => { 565 // Parse pseudo-class with arguments: skip to closing paren 566 let mut pname = name.to_ascii_lowercase(); 567 pname.push('('); 568 let mut depth = 1; 569 loop { 570 match self.peek() { 571 Token::Eof => break, 572 Token::LeftParen => { 573 depth += 1; 574 pname.push('('); 575 self.advance(); 576 } 577 Token::RightParen => { 578 depth -= 1; 579 if depth == 0 { 580 self.advance(); 581 break; 582 } 583 pname.push(')'); 584 self.advance(); 585 } 586 _ => { 587 let tok = self.advance(); 588 pname.push_str(&token_to_string(&tok)); 589 } 590 } 591 } 592 pname.push(')'); 593 simple.push(SimpleSelector::PseudoClass(pname)); 594 } 595 _ => break, 596 } 597 } 598 // Ident after already having a type selector → new compound starts 599 Token::Ident(_) => break, 600 _ => break, 601 } 602 } 603 604 if simple.is_empty() { 605 None 606 } else { 607 Some(CompoundSelector { simple }) 608 } 609 } 610 611 fn parse_attribute_selector(&mut self) -> Option<AttributeSelector> { 612 self.skip_whitespace(); 613 614 let name = match self.advance() { 615 Token::Ident(name) => name.to_ascii_lowercase(), 616 _ => { 617 self.skip_to_bracket_close(); 618 return None; 619 } 620 }; 621 622 self.skip_whitespace(); 623 624 // Check for close bracket (presence-only selector) 625 if matches!(self.peek(), Token::RightBracket) { 626 self.advance(); 627 return Some(AttributeSelector { 628 name, 629 op: None, 630 value: None, 631 }); 632 } 633 634 // Parse operator 635 let op = match self.peek() { 636 Token::Delim('=') => { 637 self.advance(); 638 AttributeOp::Exact 639 } 640 Token::Delim('~') => { 641 self.advance(); 642 if matches!(self.peek(), Token::Delim('=')) { 643 self.advance(); 644 } 645 AttributeOp::Includes 646 } 647 Token::Delim('|') => { 648 self.advance(); 649 if matches!(self.peek(), Token::Delim('=')) { 650 self.advance(); 651 } 652 AttributeOp::DashMatch 653 } 654 Token::Delim('^') => { 655 self.advance(); 656 if matches!(self.peek(), Token::Delim('=')) { 657 self.advance(); 658 } 659 AttributeOp::Prefix 660 } 661 Token::Delim('$') => { 662 self.advance(); 663 if matches!(self.peek(), Token::Delim('=')) { 664 self.advance(); 665 } 666 AttributeOp::Suffix 667 } 668 Token::Delim('*') => { 669 self.advance(); 670 if matches!(self.peek(), Token::Delim('=')) { 671 self.advance(); 672 } 673 AttributeOp::Substring 674 } 675 _ => { 676 self.skip_to_bracket_close(); 677 return None; 678 } 679 }; 680 681 self.skip_whitespace(); 682 683 // Parse value 684 let value = match self.advance() { 685 Token::Ident(v) => v, 686 Token::String(v) => v, 687 _ => { 688 self.skip_to_bracket_close(); 689 return None; 690 } 691 }; 692 693 self.skip_whitespace(); 694 695 // Consume closing bracket 696 if matches!(self.peek(), Token::RightBracket) { 697 self.advance(); 698 } 699 700 Some(AttributeSelector { 701 name, 702 op: Some(op), 703 value: Some(value), 704 }) 705 } 706 707 fn skip_to_bracket_close(&mut self) { 708 loop { 709 match self.peek() { 710 Token::RightBracket | Token::Eof => { 711 if matches!(self.peek(), Token::RightBracket) { 712 self.advance(); 713 } 714 return; 715 } 716 _ => { 717 self.advance(); 718 } 719 } 720 } 721 } 722 723 // -- Declaration parsing ------------------------------------------------ 724 725 fn parse_declaration_list(&mut self) -> Vec<Declaration> { 726 let mut declarations = Vec::new(); 727 loop { 728 self.skip_whitespace(); 729 if self.is_eof() { 730 break; 731 } 732 if matches!(self.peek(), Token::Semicolon) { 733 self.advance(); 734 continue; 735 } 736 if let Some(decl) = self.parse_declaration() { 737 declarations.push(decl); 738 } 739 } 740 declarations 741 } 742 743 fn parse_declaration_list_until_brace(&mut self) -> Vec<Declaration> { 744 let mut declarations = Vec::new(); 745 loop { 746 self.skip_whitespace(); 747 if self.is_eof() || matches!(self.peek(), Token::RightBrace) { 748 break; 749 } 750 if matches!(self.peek(), Token::Semicolon) { 751 self.advance(); 752 continue; 753 } 754 if let Some(decl) = self.parse_declaration() { 755 declarations.push(decl); 756 } 757 } 758 declarations 759 } 760 761 fn parse_declaration(&mut self) -> Option<Declaration> { 762 // Property name 763 let property = match self.peek() { 764 Token::Ident(_) => { 765 if let Token::Ident(name) = self.advance() { 766 name.to_ascii_lowercase() 767 } else { 768 unreachable!() 769 } 770 } 771 _ => { 772 // Error recovery: skip to next semicolon or closing brace 773 self.skip_declaration_error(); 774 return None; 775 } 776 }; 777 778 self.skip_whitespace(); 779 780 // Expect colon 781 if !matches!(self.peek(), Token::Colon) { 782 self.skip_declaration_error(); 783 return None; 784 } 785 self.advance(); 786 787 self.skip_whitespace(); 788 789 // Parse value 790 let (value, important) = self.parse_declaration_value(); 791 792 if value.is_empty() { 793 return None; 794 } 795 796 Some(Declaration { 797 property, 798 value, 799 important, 800 }) 801 } 802 803 fn parse_declaration_value(&mut self) -> (Vec<ComponentValue>, bool) { 804 let mut values = Vec::new(); 805 let mut important = false; 806 807 loop { 808 match self.peek() { 809 Token::Semicolon | Token::RightBrace | Token::Eof => break, 810 Token::Whitespace => { 811 self.advance(); 812 // Only add whitespace if there are already values and we're 813 // not at the end of the declaration 814 if !values.is_empty() 815 && !matches!( 816 self.peek(), 817 Token::Semicolon | Token::RightBrace | Token::Eof 818 ) 819 { 820 values.push(ComponentValue::Whitespace); 821 } 822 } 823 Token::Delim('!') => { 824 self.advance(); 825 self.skip_whitespace(); 826 if let Token::Ident(ref s) = self.peek() { 827 if s.eq_ignore_ascii_case("important") { 828 important = true; 829 self.advance(); 830 } 831 } 832 } 833 _ => { 834 if let Some(cv) = self.parse_component_value() { 835 values.push(cv); 836 } 837 } 838 } 839 } 840 841 // Trim trailing whitespace 842 while matches!(values.last(), Some(ComponentValue::Whitespace)) { 843 values.pop(); 844 } 845 846 (values, important) 847 } 848 849 fn parse_component_value(&mut self) -> Option<ComponentValue> { 850 match self.advance() { 851 Token::Ident(s) => Some(ComponentValue::Ident(s)), 852 Token::String(s) => Some(ComponentValue::String(s)), 853 Token::Number(n, t) => Some(ComponentValue::Number(n, t)), 854 Token::Percentage(n) => Some(ComponentValue::Percentage(n)), 855 Token::Dimension(n, t, u) => Some(ComponentValue::Dimension(n, t, u)), 856 Token::Hash(s, ht) => Some(ComponentValue::Hash(s, ht)), 857 Token::Comma => Some(ComponentValue::Comma), 858 Token::Delim(c) => Some(ComponentValue::Delim(c)), 859 Token::Function(name) => { 860 let args = self.parse_function_args(); 861 Some(ComponentValue::Function(name, args)) 862 } 863 _ => None, 864 } 865 } 866 867 fn parse_function_args(&mut self) -> Vec<ComponentValue> { 868 let mut args = Vec::new(); 869 loop { 870 match self.peek() { 871 Token::RightParen | Token::Eof => { 872 if matches!(self.peek(), Token::RightParen) { 873 self.advance(); 874 } 875 break; 876 } 877 Token::Whitespace => { 878 self.advance(); 879 if !args.is_empty() && !matches!(self.peek(), Token::RightParen | Token::Eof) { 880 args.push(ComponentValue::Whitespace); 881 } 882 } 883 _ => { 884 if let Some(cv) = self.parse_component_value() { 885 args.push(cv); 886 } 887 } 888 } 889 } 890 args 891 } 892 893 fn skip_declaration_error(&mut self) { 894 loop { 895 match self.peek() { 896 Token::Semicolon => { 897 self.advance(); 898 return; 899 } 900 Token::RightBrace | Token::Eof => return, 901 _ => { 902 self.advance(); 903 } 904 } 905 } 906 } 907} 908 909// --------------------------------------------------------------------------- 910// Helpers 911// --------------------------------------------------------------------------- 912 913fn has_type_or_universal(selectors: &[SimpleSelector]) -> bool { 914 selectors 915 .iter() 916 .any(|s| matches!(s, SimpleSelector::Type(_) | SimpleSelector::Universal)) 917} 918 919fn token_to_string(token: &Token) -> String { 920 match token { 921 Token::Ident(s) => s.clone(), 922 Token::Function(s) => format!("{s}("), 923 Token::AtKeyword(s) => format!("@{s}"), 924 Token::Hash(s, _) => format!("#{s}"), 925 Token::String(s) => format!("\"{s}\""), 926 Token::Url(s) => format!("url({s})"), 927 Token::Number(n, _) => format!("{n}"), 928 Token::Percentage(n) => format!("{n}%"), 929 Token::Dimension(n, _, u) => format!("{n}{u}"), 930 Token::Whitespace => " ".to_string(), 931 Token::Colon => ":".to_string(), 932 Token::Semicolon => ";".to_string(), 933 Token::Comma => ",".to_string(), 934 Token::LeftBracket => "[".to_string(), 935 Token::RightBracket => "]".to_string(), 936 Token::LeftParen => "(".to_string(), 937 Token::RightParen => ")".to_string(), 938 Token::LeftBrace => "{".to_string(), 939 Token::RightBrace => "}".to_string(), 940 Token::Delim(c) => c.to_string(), 941 Token::Cdo => "<!--".to_string(), 942 Token::Cdc => "-->".to_string(), 943 Token::BadString | Token::BadUrl | Token::Eof => String::new(), 944 } 945} 946 947// --------------------------------------------------------------------------- 948// Tests 949// --------------------------------------------------------------------------- 950 951#[cfg(test)] 952mod tests { 953 use super::*; 954 955 // -- Selector tests ----------------------------------------------------- 956 957 #[test] 958 fn test_type_selector() { 959 let ss = Parser::parse("div { }"); 960 assert_eq!(ss.rules.len(), 1); 961 let rule = match &ss.rules[0] { 962 Rule::Style(r) => r, 963 _ => panic!("expected style rule"), 964 }; 965 assert_eq!(rule.selectors.selectors.len(), 1); 966 let sel = &rule.selectors.selectors[0]; 967 assert_eq!(sel.components.len(), 1); 968 match &sel.components[0] { 969 SelectorComponent::Compound(c) => { 970 assert_eq!(c.simple.len(), 1); 971 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 972 } 973 _ => panic!("expected compound"), 974 } 975 } 976 977 #[test] 978 fn test_universal_selector() { 979 let ss = Parser::parse("* { }"); 980 let rule = match &ss.rules[0] { 981 Rule::Style(r) => r, 982 _ => panic!("expected style rule"), 983 }; 984 let sel = &rule.selectors.selectors[0]; 985 match &sel.components[0] { 986 SelectorComponent::Compound(c) => { 987 assert_eq!(c.simple[0], SimpleSelector::Universal); 988 } 989 _ => panic!("expected compound"), 990 } 991 } 992 993 #[test] 994 fn test_class_selector() { 995 let ss = Parser::parse(".foo { }"); 996 let rule = match &ss.rules[0] { 997 Rule::Style(r) => r, 998 _ => panic!("expected style rule"), 999 }; 1000 let sel = &rule.selectors.selectors[0]; 1001 match &sel.components[0] { 1002 SelectorComponent::Compound(c) => { 1003 assert_eq!(c.simple[0], SimpleSelector::Class("foo".into())); 1004 } 1005 _ => panic!("expected compound"), 1006 } 1007 } 1008 1009 #[test] 1010 fn test_id_selector() { 1011 let ss = Parser::parse("#main { }"); 1012 let rule = match &ss.rules[0] { 1013 Rule::Style(r) => r, 1014 _ => panic!("expected style rule"), 1015 }; 1016 let sel = &rule.selectors.selectors[0]; 1017 match &sel.components[0] { 1018 SelectorComponent::Compound(c) => { 1019 assert_eq!(c.simple[0], SimpleSelector::Id("main".into())); 1020 } 1021 _ => panic!("expected compound"), 1022 } 1023 } 1024 1025 #[test] 1026 fn test_compound_selector() { 1027 let ss = Parser::parse("div.foo#bar { }"); 1028 let rule = match &ss.rules[0] { 1029 Rule::Style(r) => r, 1030 _ => panic!("expected style rule"), 1031 }; 1032 let sel = &rule.selectors.selectors[0]; 1033 match &sel.components[0] { 1034 SelectorComponent::Compound(c) => { 1035 assert_eq!(c.simple.len(), 3); 1036 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 1037 assert_eq!(c.simple[1], SimpleSelector::Class("foo".into())); 1038 assert_eq!(c.simple[2], SimpleSelector::Id("bar".into())); 1039 } 1040 _ => panic!("expected compound"), 1041 } 1042 } 1043 1044 #[test] 1045 fn test_selector_list() { 1046 let ss = Parser::parse("h1, h2, h3 { }"); 1047 let rule = match &ss.rules[0] { 1048 Rule::Style(r) => r, 1049 _ => panic!("expected style rule"), 1050 }; 1051 assert_eq!(rule.selectors.selectors.len(), 3); 1052 } 1053 1054 #[test] 1055 fn test_descendant_combinator() { 1056 let ss = Parser::parse("div p { }"); 1057 let rule = match &ss.rules[0] { 1058 Rule::Style(r) => r, 1059 _ => panic!("expected style rule"), 1060 }; 1061 let sel = &rule.selectors.selectors[0]; 1062 assert_eq!(sel.components.len(), 3); 1063 assert!(matches!( 1064 sel.components[1], 1065 SelectorComponent::Combinator(Combinator::Descendant) 1066 )); 1067 } 1068 1069 #[test] 1070 fn test_child_combinator() { 1071 let ss = Parser::parse("div > p { }"); 1072 let rule = match &ss.rules[0] { 1073 Rule::Style(r) => r, 1074 _ => panic!("expected style rule"), 1075 }; 1076 let sel = &rule.selectors.selectors[0]; 1077 assert_eq!(sel.components.len(), 3); 1078 assert!(matches!( 1079 sel.components[1], 1080 SelectorComponent::Combinator(Combinator::Child) 1081 )); 1082 } 1083 1084 #[test] 1085 fn test_adjacent_sibling_combinator() { 1086 let ss = Parser::parse("h1 + p { }"); 1087 let rule = match &ss.rules[0] { 1088 Rule::Style(r) => r, 1089 _ => panic!("expected style rule"), 1090 }; 1091 let sel = &rule.selectors.selectors[0]; 1092 assert!(matches!( 1093 sel.components[1], 1094 SelectorComponent::Combinator(Combinator::AdjacentSibling) 1095 )); 1096 } 1097 1098 #[test] 1099 fn test_general_sibling_combinator() { 1100 let ss = Parser::parse("h1 ~ p { }"); 1101 let rule = match &ss.rules[0] { 1102 Rule::Style(r) => r, 1103 _ => panic!("expected style rule"), 1104 }; 1105 let sel = &rule.selectors.selectors[0]; 1106 assert!(matches!( 1107 sel.components[1], 1108 SelectorComponent::Combinator(Combinator::GeneralSibling) 1109 )); 1110 } 1111 1112 #[test] 1113 fn test_attribute_presence() { 1114 let ss = Parser::parse("[disabled] { }"); 1115 let rule = match &ss.rules[0] { 1116 Rule::Style(r) => r, 1117 _ => panic!("expected style rule"), 1118 }; 1119 let sel = &rule.selectors.selectors[0]; 1120 match &sel.components[0] { 1121 SelectorComponent::Compound(c) => match &c.simple[0] { 1122 SimpleSelector::Attribute(attr) => { 1123 assert_eq!(attr.name, "disabled"); 1124 assert!(attr.op.is_none()); 1125 assert!(attr.value.is_none()); 1126 } 1127 _ => panic!("expected attribute selector"), 1128 }, 1129 _ => panic!("expected compound"), 1130 } 1131 } 1132 1133 #[test] 1134 fn test_attribute_exact() { 1135 let ss = Parser::parse("[type=\"text\"] { }"); 1136 let rule = match &ss.rules[0] { 1137 Rule::Style(r) => r, 1138 _ => panic!("expected style rule"), 1139 }; 1140 let sel = &rule.selectors.selectors[0]; 1141 match &sel.components[0] { 1142 SelectorComponent::Compound(c) => match &c.simple[0] { 1143 SimpleSelector::Attribute(attr) => { 1144 assert_eq!(attr.name, "type"); 1145 assert_eq!(attr.op, Some(AttributeOp::Exact)); 1146 assert_eq!(attr.value, Some("text".into())); 1147 } 1148 _ => panic!("expected attribute selector"), 1149 }, 1150 _ => panic!("expected compound"), 1151 } 1152 } 1153 1154 #[test] 1155 fn test_attribute_operators() { 1156 let ops = vec![ 1157 ("[a~=b] { }", AttributeOp::Includes), 1158 ("[a|=b] { }", AttributeOp::DashMatch), 1159 ("[a^=b] { }", AttributeOp::Prefix), 1160 ("[a$=b] { }", AttributeOp::Suffix), 1161 ("[a*=b] { }", AttributeOp::Substring), 1162 ]; 1163 for (input, expected_op) in ops { 1164 let ss = Parser::parse(input); 1165 let rule = match &ss.rules[0] { 1166 Rule::Style(r) => r, 1167 _ => panic!("expected style rule"), 1168 }; 1169 let sel = &rule.selectors.selectors[0]; 1170 match &sel.components[0] { 1171 SelectorComponent::Compound(c) => match &c.simple[0] { 1172 SimpleSelector::Attribute(attr) => { 1173 assert_eq!(attr.op, Some(expected_op), "failed for {input}"); 1174 } 1175 _ => panic!("expected attribute selector"), 1176 }, 1177 _ => panic!("expected compound"), 1178 } 1179 } 1180 } 1181 1182 #[test] 1183 fn test_pseudo_class() { 1184 let ss = Parser::parse("a:hover { }"); 1185 let rule = match &ss.rules[0] { 1186 Rule::Style(r) => r, 1187 _ => panic!("expected style rule"), 1188 }; 1189 let sel = &rule.selectors.selectors[0]; 1190 match &sel.components[0] { 1191 SelectorComponent::Compound(c) => { 1192 assert_eq!(c.simple.len(), 2); 1193 assert_eq!(c.simple[0], SimpleSelector::Type("a".into())); 1194 assert_eq!(c.simple[1], SimpleSelector::PseudoClass("hover".into())); 1195 } 1196 _ => panic!("expected compound"), 1197 } 1198 } 1199 1200 #[test] 1201 fn test_pseudo_class_first_child() { 1202 let ss = Parser::parse("p:first-child { }"); 1203 let rule = match &ss.rules[0] { 1204 Rule::Style(r) => r, 1205 _ => panic!("expected style rule"), 1206 }; 1207 let sel = &rule.selectors.selectors[0]; 1208 match &sel.components[0] { 1209 SelectorComponent::Compound(c) => { 1210 assert_eq!( 1211 c.simple[1], 1212 SimpleSelector::PseudoClass("first-child".into()) 1213 ); 1214 } 1215 _ => panic!("expected compound"), 1216 } 1217 } 1218 1219 // -- Declaration tests -------------------------------------------------- 1220 1221 #[test] 1222 fn test_simple_declaration() { 1223 let ss = Parser::parse("p { color: red; }"); 1224 let rule = match &ss.rules[0] { 1225 Rule::Style(r) => r, 1226 _ => panic!("expected style rule"), 1227 }; 1228 assert_eq!(rule.declarations.len(), 1); 1229 assert_eq!(rule.declarations[0].property, "color"); 1230 assert_eq!(rule.declarations[0].value.len(), 1); 1231 assert_eq!( 1232 rule.declarations[0].value[0], 1233 ComponentValue::Ident("red".into()) 1234 ); 1235 assert!(!rule.declarations[0].important); 1236 } 1237 1238 #[test] 1239 fn test_multiple_declarations() { 1240 let ss = Parser::parse("p { color: red; font-size: 16px; }"); 1241 let rule = match &ss.rules[0] { 1242 Rule::Style(r) => r, 1243 _ => panic!("expected style rule"), 1244 }; 1245 assert_eq!(rule.declarations.len(), 2); 1246 assert_eq!(rule.declarations[0].property, "color"); 1247 assert_eq!(rule.declarations[1].property, "font-size"); 1248 } 1249 1250 #[test] 1251 fn test_important_declaration() { 1252 let ss = Parser::parse("p { color: red !important; }"); 1253 let rule = match &ss.rules[0] { 1254 Rule::Style(r) => r, 1255 _ => panic!("expected style rule"), 1256 }; 1257 assert!(rule.declarations[0].important); 1258 } 1259 1260 #[test] 1261 fn test_declaration_with_function() { 1262 let ss = Parser::parse("p { color: rgb(255, 0, 0); }"); 1263 let rule = match &ss.rules[0] { 1264 Rule::Style(r) => r, 1265 _ => panic!("expected style rule"), 1266 }; 1267 assert_eq!(rule.declarations[0].property, "color"); 1268 match &rule.declarations[0].value[0] { 1269 ComponentValue::Function(name, args) => { 1270 assert_eq!(name, "rgb"); 1271 // 255, 0, 0 → Number, Comma, WS, Number, Comma, WS, Number 1272 assert!(!args.is_empty()); 1273 } 1274 _ => panic!("expected function value"), 1275 } 1276 } 1277 1278 #[test] 1279 fn test_declaration_with_hash_color() { 1280 let ss = Parser::parse("p { color: #ff0000; }"); 1281 let rule = match &ss.rules[0] { 1282 Rule::Style(r) => r, 1283 _ => panic!("expected style rule"), 1284 }; 1285 assert_eq!( 1286 rule.declarations[0].value[0], 1287 ComponentValue::Hash("ff0000".into(), HashType::Id) 1288 ); 1289 } 1290 1291 #[test] 1292 fn test_declaration_with_dimension() { 1293 let ss = Parser::parse("p { margin: 10px; }"); 1294 let rule = match &ss.rules[0] { 1295 Rule::Style(r) => r, 1296 _ => panic!("expected style rule"), 1297 }; 1298 assert_eq!( 1299 rule.declarations[0].value[0], 1300 ComponentValue::Dimension(10.0, NumericType::Integer, "px".into()) 1301 ); 1302 } 1303 1304 #[test] 1305 fn test_declaration_with_percentage() { 1306 let ss = Parser::parse("p { width: 50%; }"); 1307 let rule = match &ss.rules[0] { 1308 Rule::Style(r) => r, 1309 _ => panic!("expected style rule"), 1310 }; 1311 assert_eq!( 1312 rule.declarations[0].value[0], 1313 ComponentValue::Percentage(50.0) 1314 ); 1315 } 1316 1317 #[test] 1318 fn test_parse_inline_style() { 1319 let decls = Parser::parse_declarations("color: red; font-size: 16px"); 1320 assert_eq!(decls.len(), 2); 1321 assert_eq!(decls[0].property, "color"); 1322 assert_eq!(decls[1].property, "font-size"); 1323 } 1324 1325 // -- Error recovery tests ----------------------------------------------- 1326 1327 #[test] 1328 fn test_invalid_declaration_skipped() { 1329 let ss = Parser::parse("p { ??? ; color: red; }"); 1330 let rule = match &ss.rules[0] { 1331 Rule::Style(r) => r, 1332 _ => panic!("expected style rule"), 1333 }; 1334 // The invalid declaration should be skipped, color should remain 1335 assert_eq!(rule.declarations.len(), 1); 1336 assert_eq!(rule.declarations[0].property, "color"); 1337 } 1338 1339 #[test] 1340 fn test_missing_colon_skipped() { 1341 let ss = Parser::parse("p { color red; font-size: 16px; }"); 1342 let rule = match &ss.rules[0] { 1343 Rule::Style(r) => r, 1344 _ => panic!("expected style rule"), 1345 }; 1346 assert_eq!(rule.declarations.len(), 1); 1347 assert_eq!(rule.declarations[0].property, "font-size"); 1348 } 1349 1350 #[test] 1351 fn test_empty_stylesheet() { 1352 let ss = Parser::parse(""); 1353 assert_eq!(ss.rules.len(), 0); 1354 } 1355 1356 #[test] 1357 fn test_empty_rule() { 1358 let ss = Parser::parse("p { }"); 1359 let rule = match &ss.rules[0] { 1360 Rule::Style(r) => r, 1361 _ => panic!("expected style rule"), 1362 }; 1363 assert_eq!(rule.declarations.len(), 0); 1364 } 1365 1366 #[test] 1367 fn test_multiple_rules() { 1368 let ss = Parser::parse("h1 { color: blue; } p { color: red; }"); 1369 assert_eq!(ss.rules.len(), 2); 1370 } 1371 1372 // -- @-rule tests ------------------------------------------------------- 1373 1374 #[test] 1375 fn test_import_rule_string() { 1376 let ss = Parser::parse("@import \"style.css\";"); 1377 assert_eq!(ss.rules.len(), 1); 1378 match &ss.rules[0] { 1379 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1380 _ => panic!("expected import rule"), 1381 } 1382 } 1383 1384 #[test] 1385 fn test_import_rule_url() { 1386 let ss = Parser::parse("@import url(style.css);"); 1387 assert_eq!(ss.rules.len(), 1); 1388 match &ss.rules[0] { 1389 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1390 _ => panic!("expected import rule"), 1391 } 1392 } 1393 1394 #[test] 1395 fn test_media_rule() { 1396 let ss = Parser::parse("@media screen { p { color: red; } }"); 1397 assert_eq!(ss.rules.len(), 1); 1398 match &ss.rules[0] { 1399 Rule::Media(m) => { 1400 assert_eq!(m.query, "screen"); 1401 assert_eq!(m.rules.len(), 1); 1402 } 1403 _ => panic!("expected media rule"), 1404 } 1405 } 1406 1407 #[test] 1408 fn test_media_rule_complex_query() { 1409 let ss = Parser::parse("@media screen and (max-width: 600px) { p { font-size: 14px; } }"); 1410 match &ss.rules[0] { 1411 Rule::Media(m) => { 1412 assert!(m.query.contains("screen")); 1413 assert!(m.query.contains("max-width")); 1414 assert_eq!(m.rules.len(), 1); 1415 } 1416 _ => panic!("expected media rule"), 1417 } 1418 } 1419 1420 #[test] 1421 fn test_unknown_at_rule_skipped() { 1422 let ss = Parser::parse("@charset \"UTF-8\"; p { color: red; }"); 1423 assert_eq!(ss.rules.len(), 1); 1424 match &ss.rules[0] { 1425 Rule::Style(r) => assert_eq!(r.declarations[0].property, "color"), 1426 _ => panic!("expected style rule"), 1427 } 1428 } 1429 1430 // -- Integration tests -------------------------------------------------- 1431 1432 #[test] 1433 fn test_real_css() { 1434 let css = r#" 1435 body { 1436 margin: 0; 1437 font-family: sans-serif; 1438 background-color: #fff; 1439 } 1440 h1 { 1441 color: #333; 1442 font-size: 24px; 1443 } 1444 .container { 1445 max-width: 960px; 1446 margin: 0 auto; 1447 } 1448 a:hover { 1449 color: blue; 1450 text-decoration: underline; 1451 } 1452 "#; 1453 let ss = Parser::parse(css); 1454 assert_eq!(ss.rules.len(), 4); 1455 } 1456 1457 #[test] 1458 fn test_declaration_no_trailing_semicolon() { 1459 let ss = Parser::parse("p { color: red }"); 1460 let rule = match &ss.rules[0] { 1461 Rule::Style(r) => r, 1462 _ => panic!("expected style rule"), 1463 }; 1464 assert_eq!(rule.declarations.len(), 1); 1465 assert_eq!(rule.declarations[0].property, "color"); 1466 } 1467 1468 #[test] 1469 fn test_multi_value_declaration() { 1470 let ss = Parser::parse("p { margin: 10px 20px 30px 40px; }"); 1471 let rule = match &ss.rules[0] { 1472 Rule::Style(r) => r, 1473 _ => panic!("expected style rule"), 1474 }; 1475 // 10px WS 20px WS 30px WS 40px 1476 assert_eq!(rule.declarations[0].value.len(), 7); 1477 } 1478 1479 #[test] 1480 fn test_cdo_cdc_ignored() { 1481 let ss = Parser::parse("<!-- p { color: red; } -->"); 1482 assert_eq!(ss.rules.len(), 1); 1483 } 1484}