diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/minifier/src/css/token.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vendor/minifier/src/css/token.rs | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/vendor/minifier/src/css/token.rs b/vendor/minifier/src/css/token.rs new file mode 100644 index 000000000..d2d738840 --- /dev/null +++ b/vendor/minifier/src/css/token.rs @@ -0,0 +1,875 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::convert::TryFrom; +use std::fmt; +use std::iter::Peekable; +use std::str::CharIndices; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ReservedChar { + Comma, + SuperiorThan, + OpenParenthese, + CloseParenthese, + OpenCurlyBrace, + CloseCurlyBrace, + OpenBracket, + CloseBracket, + Colon, + SemiColon, + Slash, + Plus, + EqualSign, + Space, + Tab, + Backline, + Star, + Quote, + DoubleQuote, + Pipe, + Tilde, + Dollar, + Circumflex, +} + +impl fmt::Display for ReservedChar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match *self { + ReservedChar::Comma => ',', + ReservedChar::OpenParenthese => '(', + ReservedChar::CloseParenthese => ')', + ReservedChar::OpenCurlyBrace => '{', + ReservedChar::CloseCurlyBrace => '}', + ReservedChar::OpenBracket => '[', + ReservedChar::CloseBracket => ']', + ReservedChar::Colon => ':', + ReservedChar::SemiColon => ';', + ReservedChar::Slash => '/', + ReservedChar::Star => '*', + ReservedChar::Plus => '+', + ReservedChar::EqualSign => '=', + ReservedChar::Space => ' ', + ReservedChar::Tab => '\t', + ReservedChar::Backline => '\n', + ReservedChar::SuperiorThan => '>', + ReservedChar::Quote => '\'', + ReservedChar::DoubleQuote => '"', + ReservedChar::Pipe => '|', + ReservedChar::Tilde => '~', + ReservedChar::Dollar => '$', + ReservedChar::Circumflex => '^', + } + ) + } +} + +impl TryFrom<char> for ReservedChar { + type Error = &'static str; + + fn try_from(value: char) -> Result<ReservedChar, Self::Error> { + match value { + '\'' => Ok(ReservedChar::Quote), + '"' => Ok(ReservedChar::DoubleQuote), + ',' => Ok(ReservedChar::Comma), + '(' => Ok(ReservedChar::OpenParenthese), + ')' => Ok(ReservedChar::CloseParenthese), + '{' => Ok(ReservedChar::OpenCurlyBrace), + '}' => Ok(ReservedChar::CloseCurlyBrace), + '[' => Ok(ReservedChar::OpenBracket), + ']' => Ok(ReservedChar::CloseBracket), + ':' => Ok(ReservedChar::Colon), + ';' => Ok(ReservedChar::SemiColon), + '/' => Ok(ReservedChar::Slash), + '*' => Ok(ReservedChar::Star), + '+' => Ok(ReservedChar::Plus), + '=' => Ok(ReservedChar::EqualSign), + ' ' => Ok(ReservedChar::Space), + '\t' => Ok(ReservedChar::Tab), + '\n' | '\r' => Ok(ReservedChar::Backline), + '>' => Ok(ReservedChar::SuperiorThan), + '|' => Ok(ReservedChar::Pipe), + '~' => Ok(ReservedChar::Tilde), + '$' => Ok(ReservedChar::Dollar), + '^' => Ok(ReservedChar::Circumflex), + _ => Err("Unknown reserved char"), + } + } +} + +impl ReservedChar { + fn is_useless(&self) -> bool { + *self == ReservedChar::Space + || *self == ReservedChar::Tab + || *self == ReservedChar::Backline + } + + fn is_operator(&self) -> bool { + Operator::try_from(*self).is_ok() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Operator { + Plus, + Multiply, + Minus, + Modulo, + Divide, +} + +impl fmt::Display for Operator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match *self { + Operator::Plus => '+', + Operator::Multiply => '*', + Operator::Minus => '-', + Operator::Modulo => '%', + Operator::Divide => '/', + } + ) + } +} + +impl TryFrom<char> for Operator { + type Error = &'static str; + + fn try_from(value: char) -> Result<Operator, Self::Error> { + match value { + '+' => Ok(Operator::Plus), + '*' => Ok(Operator::Multiply), + '-' => Ok(Operator::Minus), + '%' => Ok(Operator::Modulo), + '/' => Ok(Operator::Divide), + _ => Err("Unknown operator"), + } + } +} + +impl TryFrom<ReservedChar> for Operator { + type Error = &'static str; + + fn try_from(value: ReservedChar) -> Result<Operator, Self::Error> { + match value { + ReservedChar::Slash => Ok(Operator::Divide), + ReservedChar::Star => Ok(Operator::Multiply), + ReservedChar::Plus => Ok(Operator::Plus), + _ => Err("Unknown operator"), + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum SelectorElement<'a> { + PseudoClass(&'a str), + Class(&'a str), + Id(&'a str), + Tag(&'a str), + Media(&'a str), +} + +impl<'a> TryFrom<&'a str> for SelectorElement<'a> { + type Error = &'static str; + + fn try_from(value: &'a str) -> Result<SelectorElement<'_>, Self::Error> { + if let Some(value) = value.strip_prefix('.') { + if value.is_empty() { + Err("cannot determine selector") + } else { + Ok(SelectorElement::Class(value)) + } + } else if let Some(value) = value.strip_prefix('#') { + if value.is_empty() { + Err("cannot determine selector") + } else { + Ok(SelectorElement::Id(value)) + } + } else if let Some(value) = value.strip_prefix('@') { + if value.is_empty() { + Err("cannot determine selector") + } else { + Ok(SelectorElement::Media(value)) + } + } else if let Some(value) = value.strip_prefix(':') { + if value.is_empty() { + Err("cannot determine selector") + } else { + Ok(SelectorElement::PseudoClass(value)) + } + } else if value.chars().next().unwrap_or(' ').is_alphabetic() { + Ok(SelectorElement::Tag(value)) + } else { + Err("unknown selector") + } + } +} + +impl<'a> fmt::Display for SelectorElement<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + SelectorElement::Class(c) => write!(f, ".{}", c), + SelectorElement::Id(i) => write!(f, "#{}", i), + SelectorElement::Tag(t) => write!(f, "{}", t), + SelectorElement::Media(m) => write!(f, "@{} ", m), + SelectorElement::PseudoClass(pc) => write!(f, ":{}", pc), + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug, Copy)] +pub enum SelectorOperator { + /// `~=` + OneAttributeEquals, + /// `|=` + EqualsOrStartsWithFollowedByDash, + /// `$=` + EndsWith, + /// `^=` + FirstStartsWith, + /// `*=` + Contains, +} + +impl fmt::Display for SelectorOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + SelectorOperator::OneAttributeEquals => write!(f, "~="), + SelectorOperator::EqualsOrStartsWithFollowedByDash => write!(f, "|="), + SelectorOperator::EndsWith => write!(f, "$="), + SelectorOperator::FirstStartsWith => write!(f, "^="), + SelectorOperator::Contains => write!(f, "*="), + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum Token<'a> { + /// Comment. + Comment(&'a str), + /// Comment starting with `/**`. + License(&'a str), + Char(ReservedChar), + Other(&'a str), + SelectorElement(SelectorElement<'a>), + String(&'a str), + SelectorOperator(SelectorOperator), + Operator(Operator), +} + +impl<'a> fmt::Display for Token<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + // Token::AtRule(at_rule) => write!(f, "{}", at_rule, content), + // Token::ElementRule(selectors) => write!(f, "{}", x), + Token::Comment(c) => write!(f, "{}", c), + Token::License(l) => writeln!(f, "/*!{}*/", l), + Token::Char(c) => write!(f, "{}", c), + Token::Other(s) => write!(f, "{}", s), + Token::SelectorElement(ref se) => write!(f, "{}", se), + Token::String(s) => write!(f, "{}", s), + Token::SelectorOperator(so) => write!(f, "{}", so), + Token::Operator(op) => write!(f, "{}", op), + } + } +} + +impl<'a> Token<'a> { + fn is_comment(&self) -> bool { + matches!(*self, Token::Comment(_)) + } + + fn is_char(&self) -> bool { + matches!(*self, Token::Char(_)) + } + + fn get_char(&self) -> Option<ReservedChar> { + match *self { + Token::Char(c) => Some(c), + _ => None, + } + } + + fn is_useless(&self) -> bool { + match *self { + Token::Char(c) => c.is_useless(), + _ => false, + } + } + + fn is_a_media(&self) -> bool { + matches!(*self, Token::SelectorElement(SelectorElement::Media(_))) + } + + fn is_a_license(&self) -> bool { + matches!(*self, Token::License(_)) + } + + fn is_operator(&self) -> bool { + match *self { + Token::Operator(_) => true, + Token::Char(c) => c.is_operator(), + _ => false, + } + } +} + +impl<'a> PartialEq<ReservedChar> for Token<'a> { + fn eq(&self, other: &ReservedChar) -> bool { + match *self { + Token::Char(c) => c == *other, + _ => false, + } + } +} + +fn get_comment<'a>( + source: &'a str, + iterator: &mut Peekable<CharIndices<'_>>, + start_pos: &mut usize, +) -> Option<Token<'a>> { + let mut prev = ReservedChar::Quote; + *start_pos += 1; + let builder = if let Some((_, c)) = iterator.next() { + if c == '!' || (c == '*' && iterator.peek().map(|(_, c)| c) != Some(&'/')) { + *start_pos += 1; + Token::License + } else { + if let Ok(c) = ReservedChar::try_from(c) { + prev = c; + } + Token::Comment + } + } else { + Token::Comment + }; + + for (pos, c) in iterator { + if let Ok(c) = ReservedChar::try_from(c) { + if c == ReservedChar::Slash && prev == ReservedChar::Star { + let ret = Some(builder(&source[*start_pos..pos - 1])); + *start_pos = pos; + return ret; + } + prev = c; + } else { + prev = ReservedChar::Space; + } + } + None +} + +fn get_string<'a>( + source: &'a str, + iterator: &mut Peekable<CharIndices<'_>>, + start_pos: &mut usize, + start: ReservedChar, +) -> Option<Token<'a>> { + while let Some((pos, c)) = iterator.next() { + if c == '\\' { + // we skip next character + iterator.next(); + continue; + } + if let Ok(c) = ReservedChar::try_from(c) { + if c == start { + let ret = Some(Token::String(&source[*start_pos..pos + 1])); + *start_pos = pos; + return ret; + } + } + } + None +} + +fn fill_other<'a>( + source: &'a str, + v: &mut Vec<Token<'a>>, + start: usize, + pos: usize, + is_in_block: isize, + is_in_media: bool, + is_in_attribute_selector: bool, +) { + if start < pos { + if !is_in_attribute_selector + && ((is_in_block == 0 && !is_in_media) || (is_in_media && is_in_block == 1)) + { + let mut is_pseudo_class = false; + let mut add = 0; + if let Some(&Token::Char(ReservedChar::Colon)) = v.last() { + is_pseudo_class = true; + add = 1; + } + if let Ok(s) = SelectorElement::try_from(&source[start - add..pos]) { + if is_pseudo_class { + v.pop(); + } + v.push(Token::SelectorElement(s)); + } else { + let s = &source[start..pos]; + if !s.starts_with(':') + && !s.starts_with('.') + && !s.starts_with('#') + && !s.starts_with('@') + { + v.push(Token::Other(s)); + } + } + } else { + v.push(Token::Other(&source[start..pos])); + } + } +} + +#[allow(clippy::comparison_chain)] +pub(super) fn tokenize<'a>(source: &'a str) -> Result<Tokens<'a>, &'static str> { + let mut v = Vec::with_capacity(1000); + let mut iterator = source.char_indices().peekable(); + let mut start = 0; + let mut is_in_block: isize = 0; + let mut is_in_media = false; + let mut is_in_attribute_selector = false; + + loop { + let (mut pos, c) = match iterator.next() { + Some(x) => x, + None => { + fill_other( + source, + &mut v, + start, + source.len(), + is_in_block, + is_in_media, + is_in_attribute_selector, + ); + break; + } + }; + if let Ok(c) = ReservedChar::try_from(c) { + fill_other( + source, + &mut v, + start, + pos, + is_in_block, + is_in_media, + is_in_attribute_selector, + ); + is_in_media = is_in_media + || v.last() + .unwrap_or(&Token::Char(ReservedChar::Space)) + .is_a_media(); + match c { + ReservedChar::Quote | ReservedChar::DoubleQuote => { + if let Some(s) = get_string(source, &mut iterator, &mut pos, c) { + v.push(s); + } + } + ReservedChar::Star + if *v.last().unwrap_or(&Token::Char(ReservedChar::Space)) + == ReservedChar::Slash => + { + v.pop(); + if let Some(s) = get_comment(source, &mut iterator, &mut pos) { + v.push(s); + } + } + ReservedChar::OpenBracket => { + if is_in_attribute_selector { + return Err("Already in attribute selector"); + } + is_in_attribute_selector = true; + v.push(Token::Char(c)); + } + ReservedChar::CloseBracket => { + if !is_in_attribute_selector { + return Err("Unexpected ']'"); + } + is_in_attribute_selector = false; + v.push(Token::Char(c)); + } + ReservedChar::OpenCurlyBrace => { + is_in_block += 1; + v.push(Token::Char(c)); + } + ReservedChar::CloseCurlyBrace => { + is_in_block -= 1; + if is_in_block < 0 { + return Err("Too much '}'"); + } else if is_in_block == 0 { + is_in_media = false; + } + v.push(Token::Char(c)); + } + ReservedChar::SemiColon if is_in_block == 0 => { + is_in_media = false; + v.push(Token::Char(c)); + } + ReservedChar::EqualSign => { + match match v + .last() + .unwrap_or(&Token::Char(ReservedChar::Space)) + .get_char() + .unwrap_or(ReservedChar::Space) + { + ReservedChar::Tilde => Some(SelectorOperator::OneAttributeEquals), + ReservedChar::Pipe => { + Some(SelectorOperator::EqualsOrStartsWithFollowedByDash) + } + ReservedChar::Dollar => Some(SelectorOperator::EndsWith), + ReservedChar::Circumflex => Some(SelectorOperator::FirstStartsWith), + ReservedChar::Star => Some(SelectorOperator::Contains), + _ => None, + } { + Some(r) => { + v.pop(); + v.push(Token::SelectorOperator(r)); + } + None => v.push(Token::Char(c)), + } + } + c if !c.is_useless() => { + v.push(Token::Char(c)); + } + c => { + if !v + .last() + .unwrap_or(&Token::Char(ReservedChar::Space)) + .is_useless() + && (!v + .last() + .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace)) + .is_char() + || v.last() + .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace)) + .is_operator() + || v.last() + .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace)) + .get_char() + == Some(ReservedChar::CloseParenthese) + || v.last() + .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace)) + .get_char() + == Some(ReservedChar::CloseBracket)) + { + v.push(Token::Char(ReservedChar::Space)); + } else if let Ok(op) = Operator::try_from(c) { + v.push(Token::Operator(op)); + } + } + } + start = pos + 1; + } + } + Ok(Tokens(clean_tokens(v))) +} + +fn clean_tokens(mut v: Vec<Token<'_>>) -> Vec<Token<'_>> { + let mut i = 0; + let mut is_in_calc = false; + let mut paren = 0; + + while i < v.len() { + if v[i] == Token::Other("calc") { + is_in_calc = true; + } else if is_in_calc { + if v[i] == Token::Char(ReservedChar::CloseParenthese) { + paren -= 1; + is_in_calc = paren != 0; + } else if v[i] == Token::Char(ReservedChar::OpenParenthese) { + paren += 1; + } + } + + if v[i].is_useless() { + if i > 0 && v[i - 1] == Token::Char(ReservedChar::CloseBracket) { + if i + 1 < v.len() + && (v[i + 1].is_useless() + || v[i + 1] == Token::Char(ReservedChar::OpenCurlyBrace)) + { + v.remove(i); + continue; + } + } else if i > 0 + && (v[i - 1] == Token::Other("and") + || v[i - 1] == Token::Other("or") + || v[i - 1] == Token::Other("not")) + { + // retain the space after "and", "or" or "not" + } else if (is_in_calc && v[i - 1].is_useless()) + || !is_in_calc + && ((i > 0 + && ((v[i - 1].is_char() + && v[i - 1] != Token::Char(ReservedChar::CloseParenthese)) + || v[i - 1].is_a_media() + || v[i - 1].is_a_license())) + || (i < v.len() - 1 && v[i + 1].is_char())) + { + v.remove(i); + continue; + } + } else if v[i].is_comment() { + v.remove(i); + continue; + } + i += 1; + } + v +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub(super) struct Tokens<'a>(Vec<Token<'a>>); + +impl<'a> Tokens<'a> { + pub(super) fn write<W: std::io::Write>(self, mut w: W) -> std::io::Result<()> { + for token in self.0.iter() { + write!(w, "{}", token)?; + } + Ok(()) + } +} + +impl<'a> fmt::Display for Tokens<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for token in self.0.iter() { + write!(f, "{}", token)?; + } + Ok(()) + } +} + +#[test] +fn css_basic() { + let s = r#" +/*! just some license */ +.foo > #bar p:hover { + color: blue; + background: "blue"; +} + +/* a comment! */ +@media screen and (max-width: 640px) { + .block:hover { + display: block; + } +}"#; + let expected = vec![ + Token::License(" just some license "), + Token::SelectorElement(SelectorElement::Class("foo")), + Token::Char(ReservedChar::SuperiorThan), + Token::SelectorElement(SelectorElement::Id("bar")), + Token::Char(ReservedChar::Space), + Token::SelectorElement(SelectorElement::Tag("p")), + Token::SelectorElement(SelectorElement::PseudoClass("hover")), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("color"), + Token::Char(ReservedChar::Colon), + Token::Other("blue"), + Token::Char(ReservedChar::SemiColon), + Token::Other("background"), + Token::Char(ReservedChar::Colon), + Token::String("\"blue\""), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Media("media")), + Token::Other("screen"), + Token::Char(ReservedChar::Space), + Token::Other("and"), + Token::Char(ReservedChar::Space), + Token::Char(ReservedChar::OpenParenthese), + Token::Other("max-width"), + Token::Char(ReservedChar::Colon), + Token::Other("640px"), + Token::Char(ReservedChar::CloseParenthese), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::SelectorElement(SelectorElement::Class("block")), + Token::SelectorElement(SelectorElement::PseudoClass("hover")), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("display"), + Token::Char(ReservedChar::Colon), + Token::Other("block"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::Char(ReservedChar::CloseCurlyBrace), + ]; + assert_eq!(tokenize(s), Ok(Tokens(expected))); +} + +#[test] +fn elem_selector() { + let s = r#" +/** just some license */ +a[href*="example"] { + background: yellow; +} +a[href$=".org"] { + font-style: italic; +} +span[lang|="zh"] { + color: red; +} +a[href^="/"] { + background-color: gold; +} +div[value~="test"] { + border-width: 1px; +} +span[lang="pt"] { + font-size: 12em; /* I love big fonts */ +} +"#; + let expected = vec![ + Token::License(" just some license "), + Token::SelectorElement(SelectorElement::Tag("a")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("href"), + Token::SelectorOperator(SelectorOperator::Contains), + Token::String("\"example\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("background"), + Token::Char(ReservedChar::Colon), + Token::Other("yellow"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("a")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("href"), + Token::SelectorOperator(SelectorOperator::EndsWith), + Token::String("\".org\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("font-style"), + Token::Char(ReservedChar::Colon), + Token::Other("italic"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("span")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("lang"), + Token::SelectorOperator(SelectorOperator::EqualsOrStartsWithFollowedByDash), + Token::String("\"zh\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("color"), + Token::Char(ReservedChar::Colon), + Token::Other("red"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("a")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("href"), + Token::SelectorOperator(SelectorOperator::FirstStartsWith), + Token::String("\"/\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("background-color"), + Token::Char(ReservedChar::Colon), + Token::Other("gold"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("div")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("value"), + Token::SelectorOperator(SelectorOperator::OneAttributeEquals), + Token::String("\"test\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("border-width"), + Token::Char(ReservedChar::Colon), + Token::Other("1px"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("span")), + Token::Char(ReservedChar::OpenBracket), + Token::Other("lang"), + Token::Char(ReservedChar::EqualSign), + Token::String("\"pt\""), + Token::Char(ReservedChar::CloseBracket), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("font-size"), + Token::Char(ReservedChar::Colon), + Token::Other("12em"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + ]; + assert_eq!(tokenize(s), Ok(Tokens(expected))); +} + +#[test] +fn check_media() { + let s = "@media (max-width: 700px) { color: red; }"; + + let expected = vec![ + Token::SelectorElement(SelectorElement::Media("media")), + Token::Char(ReservedChar::OpenParenthese), + Token::Other("max-width"), + Token::Char(ReservedChar::Colon), + Token::Other("700px"), + Token::Char(ReservedChar::CloseParenthese), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("color")), + Token::Char(ReservedChar::Colon), + Token::Other("red"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + ]; + + assert_eq!(tokenize(s), Ok(Tokens(expected))); +} + +#[test] +fn check_supports() { + let s = "@supports not (display: grid) { div { float: right; } }"; + + let expected = vec![ + Token::SelectorElement(SelectorElement::Media("supports")), + Token::Other("not"), + Token::Char(ReservedChar::Space), + Token::Char(ReservedChar::OpenParenthese), + Token::Other("display"), + Token::Char(ReservedChar::Colon), + Token::Other("grid"), + Token::Char(ReservedChar::CloseParenthese), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::SelectorElement(SelectorElement::Tag("div")), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("float"), + Token::Char(ReservedChar::Colon), + Token::Other("right"), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + Token::Char(ReservedChar::CloseCurlyBrace), + ]; + + assert_eq!(tokenize(s), Ok(Tokens(expected))); +} + +#[test] +fn check_calc() { + let s = ".foo { width: calc(100% - 34px); }"; + + let expected = vec![ + Token::SelectorElement(SelectorElement::Class("foo")), + Token::Char(ReservedChar::OpenCurlyBrace), + Token::Other("width"), + Token::Char(ReservedChar::Colon), + Token::Other("calc"), + Token::Char(ReservedChar::OpenParenthese), + Token::Other("100%"), + Token::Char(ReservedChar::Space), + Token::Other("-"), + Token::Char(ReservedChar::Space), + Token::Other("34px"), + Token::Char(ReservedChar::CloseParenthese), + Token::Char(ReservedChar::SemiColon), + Token::Char(ReservedChar::CloseCurlyBrace), + ]; + assert_eq!(tokenize(s), Ok(Tokens(expected))); +} |