]> git.proxmox.com Git - rustc.git/blame - vendor/minifier/src/css/token.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / vendor / minifier / src / css / token.rs
CommitLineData
5099ac24 1// Take a look at the license at the top of the repository in the LICENSE file.
8faf50e0 2
6a06907d 3use std::convert::TryFrom;
8faf50e0
XL
4use std::fmt;
5use std::iter::Peekable;
6use std::str::CharIndices;
7
8faf50e0
XL
8#[derive(Debug, PartialEq, Eq, Clone, Copy)]
9pub enum ReservedChar {
10 Comma,
11 SuperiorThan,
12 OpenParenthese,
13 CloseParenthese,
14 OpenCurlyBrace,
15 CloseCurlyBrace,
16 OpenBracket,
17 CloseBracket,
18 Colon,
19 SemiColon,
20 Slash,
21 Plus,
22 EqualSign,
23 Space,
24 Tab,
25 Backline,
26 Star,
27 Quote,
28 DoubleQuote,
29 Pipe,
30 Tilde,
31 Dollar,
32 Circumflex,
33}
34
35impl fmt::Display for ReservedChar {
923072b8 36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6a06907d
XL
37 write!(
38 f,
39 "{}",
40 match *self {
41 ReservedChar::Comma => ',',
42 ReservedChar::OpenParenthese => '(',
43 ReservedChar::CloseParenthese => ')',
44 ReservedChar::OpenCurlyBrace => '{',
45 ReservedChar::CloseCurlyBrace => '}',
46 ReservedChar::OpenBracket => '[',
47 ReservedChar::CloseBracket => ']',
48 ReservedChar::Colon => ':',
49 ReservedChar::SemiColon => ';',
50 ReservedChar::Slash => '/',
51 ReservedChar::Star => '*',
52 ReservedChar::Plus => '+',
53 ReservedChar::EqualSign => '=',
54 ReservedChar::Space => ' ',
55 ReservedChar::Tab => '\t',
56 ReservedChar::Backline => '\n',
57 ReservedChar::SuperiorThan => '>',
58 ReservedChar::Quote => '\'',
59 ReservedChar::DoubleQuote => '"',
60 ReservedChar::Pipe => '|',
61 ReservedChar::Tilde => '~',
62 ReservedChar::Dollar => '$',
63 ReservedChar::Circumflex => '^',
64 }
65 )
8faf50e0
XL
66 }
67}
68
6a06907d 69impl TryFrom<char> for ReservedChar {
8faf50e0
XL
70 type Error = &'static str;
71
72 fn try_from(value: char) -> Result<ReservedChar, Self::Error> {
73 match value {
74 '\'' => Ok(ReservedChar::Quote),
6a06907d
XL
75 '"' => Ok(ReservedChar::DoubleQuote),
76 ',' => Ok(ReservedChar::Comma),
77 '(' => Ok(ReservedChar::OpenParenthese),
78 ')' => Ok(ReservedChar::CloseParenthese),
79 '{' => Ok(ReservedChar::OpenCurlyBrace),
80 '}' => Ok(ReservedChar::CloseCurlyBrace),
81 '[' => Ok(ReservedChar::OpenBracket),
82 ']' => Ok(ReservedChar::CloseBracket),
83 ':' => Ok(ReservedChar::Colon),
84 ';' => Ok(ReservedChar::SemiColon),
85 '/' => Ok(ReservedChar::Slash),
86 '*' => Ok(ReservedChar::Star),
87 '+' => Ok(ReservedChar::Plus),
88 '=' => Ok(ReservedChar::EqualSign),
89 ' ' => Ok(ReservedChar::Space),
8faf50e0 90 '\t' => Ok(ReservedChar::Tab),
6a06907d
XL
91 '\n' | '\r' => Ok(ReservedChar::Backline),
92 '>' => Ok(ReservedChar::SuperiorThan),
93 '|' => Ok(ReservedChar::Pipe),
94 '~' => Ok(ReservedChar::Tilde),
95 '$' => Ok(ReservedChar::Dollar),
96 '^' => Ok(ReservedChar::Circumflex),
97 _ => Err("Unknown reserved char"),
8faf50e0
XL
98 }
99 }
100}
101
102impl ReservedChar {
103 fn is_useless(&self) -> bool {
6a06907d
XL
104 *self == ReservedChar::Space
105 || *self == ReservedChar::Tab
106 || *self == ReservedChar::Backline
8faf50e0 107 }
0bf4aa26
XL
108
109 fn is_operator(&self) -> bool {
110 Operator::try_from(*self).is_ok()
111 }
112}
113
114#[derive(Debug, PartialEq, Eq, Clone, Copy)]
115pub enum Operator {
116 Plus,
117 Multiply,
118 Minus,
119 Modulo,
120 Divide,
121}
122
123impl fmt::Display for Operator {
923072b8 124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6a06907d
XL
125 write!(
126 f,
127 "{}",
128 match *self {
129 Operator::Plus => '+',
130 Operator::Multiply => '*',
131 Operator::Minus => '-',
132 Operator::Modulo => '%',
133 Operator::Divide => '/',
134 }
135 )
0bf4aa26
XL
136 }
137}
138
6a06907d 139impl TryFrom<char> for Operator {
0bf4aa26
XL
140 type Error = &'static str;
141
142 fn try_from(value: char) -> Result<Operator, Self::Error> {
143 match value {
144 '+' => Ok(Operator::Plus),
145 '*' => Ok(Operator::Multiply),
146 '-' => Ok(Operator::Minus),
147 '%' => Ok(Operator::Modulo),
148 '/' => Ok(Operator::Divide),
6a06907d 149 _ => Err("Unknown operator"),
0bf4aa26
XL
150 }
151 }
152}
153
6a06907d 154impl TryFrom<ReservedChar> for Operator {
0bf4aa26
XL
155 type Error = &'static str;
156
157 fn try_from(value: ReservedChar) -> Result<Operator, Self::Error> {
158 match value {
6a06907d
XL
159 ReservedChar::Slash => Ok(Operator::Divide),
160 ReservedChar::Star => Ok(Operator::Multiply),
161 ReservedChar::Plus => Ok(Operator::Plus),
162 _ => Err("Unknown operator"),
0bf4aa26
XL
163 }
164 }
8faf50e0
XL
165}
166
167#[derive(Eq, PartialEq, Clone, Debug)]
168pub enum SelectorElement<'a> {
169 PseudoClass(&'a str),
170 Class(&'a str),
171 Id(&'a str),
172 Tag(&'a str),
173 Media(&'a str),
174}
175
6a06907d 176impl<'a> TryFrom<&'a str> for SelectorElement<'a> {
8faf50e0
XL
177 type Error = &'static str;
178
923072b8 179 fn try_from(value: &'a str) -> Result<SelectorElement<'_>, Self::Error> {
6a06907d
XL
180 if let Some(value) = value.strip_prefix('.') {
181 if value.is_empty() {
8faf50e0 182 Err("cannot determine selector")
8faf50e0 183 } else {
6a06907d 184 Ok(SelectorElement::Class(value))
8faf50e0 185 }
6a06907d
XL
186 } else if let Some(value) = value.strip_prefix('#') {
187 if value.is_empty() {
8faf50e0 188 Err("cannot determine selector")
6a06907d
XL
189 } else {
190 Ok(SelectorElement::Id(value))
8faf50e0 191 }
6a06907d
XL
192 } else if let Some(value) = value.strip_prefix('@') {
193 if value.is_empty() {
194 Err("cannot determine selector")
8faf50e0 195 } else {
6a06907d
XL
196 Ok(SelectorElement::Media(value))
197 }
198 } else if let Some(value) = value.strip_prefix(':') {
199 if value.is_empty() {
8faf50e0 200 Err("cannot determine selector")
6a06907d
XL
201 } else {
202 Ok(SelectorElement::PseudoClass(value))
8faf50e0
XL
203 }
204 } else if value.chars().next().unwrap_or(' ').is_alphabetic() {
205 Ok(SelectorElement::Tag(value))
206 } else {
207 Err("unknown selector")
208 }
209 }
210}
211
212impl<'a> fmt::Display for SelectorElement<'a> {
923072b8 213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8faf50e0
XL
214 match *self {
215 SelectorElement::Class(c) => write!(f, ".{}", c),
216 SelectorElement::Id(i) => write!(f, "#{}", i),
217 SelectorElement::Tag(t) => write!(f, "{}", t),
218 SelectorElement::Media(m) => write!(f, "@{} ", m),
219 SelectorElement::PseudoClass(pc) => write!(f, ":{}", pc),
220 }
221 }
222}
223
224#[derive(Eq, PartialEq, Clone, Debug, Copy)]
225pub enum SelectorOperator {
226 /// `~=`
227 OneAttributeEquals,
228 /// `|=`
229 EqualsOrStartsWithFollowedByDash,
230 /// `$=`
231 EndsWith,
232 /// `^=`
233 FirstStartsWith,
234 /// `*=`
235 Contains,
236}
237
238impl fmt::Display for SelectorOperator {
923072b8 239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8faf50e0 240 match *self {
6a06907d
XL
241 SelectorOperator::OneAttributeEquals => write!(f, "~="),
242 SelectorOperator::EqualsOrStartsWithFollowedByDash => write!(f, "|="),
243 SelectorOperator::EndsWith => write!(f, "$="),
244 SelectorOperator::FirstStartsWith => write!(f, "^="),
245 SelectorOperator::Contains => write!(f, "*="),
8faf50e0
XL
246 }
247 }
248}
249
250#[derive(Eq, PartialEq, Clone, Debug)]
251pub enum Token<'a> {
252 /// Comment.
253 Comment(&'a str),
254 /// Comment starting with `/**`.
255 License(&'a str),
256 Char(ReservedChar),
257 Other(&'a str),
258 SelectorElement(SelectorElement<'a>),
259 String(&'a str),
260 SelectorOperator(SelectorOperator),
0bf4aa26 261 Operator(Operator),
8faf50e0
XL
262}
263
264impl<'a> fmt::Display for Token<'a> {
923072b8 265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8faf50e0
XL
266 match *self {
267 // Token::AtRule(at_rule) => write!(f, "{}", at_rule, content),
268 // Token::ElementRule(selectors) => write!(f, "{}", x),
269 Token::Comment(c) => write!(f, "{}", c),
270 Token::License(l) => writeln!(f, "/*!{}*/", l),
271 Token::Char(c) => write!(f, "{}", c),
272 Token::Other(s) => write!(f, "{}", s),
273 Token::SelectorElement(ref se) => write!(f, "{}", se),
274 Token::String(s) => write!(f, "{}", s),
275 Token::SelectorOperator(so) => write!(f, "{}", so),
0bf4aa26 276 Token::Operator(op) => write!(f, "{}", op),
8faf50e0
XL
277 }
278 }
279}
280
281impl<'a> Token<'a> {
282 fn is_comment(&self) -> bool {
6a06907d 283 matches!(*self, Token::Comment(_))
8faf50e0
XL
284 }
285
286 fn is_char(&self) -> bool {
6a06907d 287 matches!(*self, Token::Char(_))
8faf50e0
XL
288 }
289
290 fn get_char(&self) -> Option<ReservedChar> {
291 match *self {
292 Token::Char(c) => Some(c),
293 _ => None,
294 }
295 }
296
297 fn is_useless(&self) -> bool {
298 match *self {
299 Token::Char(c) => c.is_useless(),
300 _ => false,
301 }
302 }
303
8faf50e0 304 fn is_a_media(&self) -> bool {
6a06907d 305 matches!(*self, Token::SelectorElement(SelectorElement::Media(_)))
8faf50e0
XL
306 }
307
308 fn is_a_license(&self) -> bool {
6a06907d 309 matches!(*self, Token::License(_))
8faf50e0 310 }
0bf4aa26
XL
311
312 fn is_operator(&self) -> bool {
313 match *self {
314 Token::Operator(_) => true,
315 Token::Char(c) => c.is_operator(),
316 _ => false,
317 }
318 }
8faf50e0
XL
319}
320
321impl<'a> PartialEq<ReservedChar> for Token<'a> {
322 fn eq(&self, other: &ReservedChar) -> bool {
323 match *self {
324 Token::Char(c) => c == *other,
325 _ => false,
326 }
327 }
328}
329
6a06907d
XL
330fn get_comment<'a>(
331 source: &'a str,
923072b8 332 iterator: &mut Peekable<CharIndices<'_>>,
6a06907d
XL
333 start_pos: &mut usize,
334) -> Option<Token<'a>> {
8faf50e0
XL
335 let mut prev = ReservedChar::Quote;
336 *start_pos += 1;
337 let builder = if let Some((_, c)) = iterator.next() {
17df50a5 338 if c == '!' || (c == '*' && iterator.peek().map(|(_, c)| c) != Some(&'/')) {
8faf50e0
XL
339 *start_pos += 1;
340 Token::License
341 } else {
342 if let Ok(c) = ReservedChar::try_from(c) {
343 prev = c;
344 }
345 Token::Comment
346 }
347 } else {
348 Token::Comment
349 };
350
6a06907d 351 for (pos, c) in iterator {
8faf50e0
XL
352 if let Ok(c) = ReservedChar::try_from(c) {
353 if c == ReservedChar::Slash && prev == ReservedChar::Star {
354 let ret = Some(builder(&source[*start_pos..pos - 1]));
355 *start_pos = pos;
356 return ret;
357 }
358 prev = c;
359 } else {
360 prev = ReservedChar::Space;
361 }
362 }
363 None
364}
365
6a06907d
XL
366fn get_string<'a>(
367 source: &'a str,
923072b8 368 iterator: &mut Peekable<CharIndices<'_>>,
6a06907d
XL
369 start_pos: &mut usize,
370 start: ReservedChar,
371) -> Option<Token<'a>> {
8faf50e0
XL
372 while let Some((pos, c)) = iterator.next() {
373 if c == '\\' {
374 // we skip next character
375 iterator.next();
6a06907d 376 continue;
8faf50e0
XL
377 }
378 if let Ok(c) = ReservedChar::try_from(c) {
379 if c == start {
380 let ret = Some(Token::String(&source[*start_pos..pos + 1]));
381 *start_pos = pos;
382 return ret;
383 }
384 }
385 }
386 None
387}
388
6a06907d
XL
389fn fill_other<'a>(
390 source: &'a str,
391 v: &mut Vec<Token<'a>>,
392 start: usize,
393 pos: usize,
394 is_in_block: isize,
395 is_in_media: bool,
396 is_in_attribute_selector: bool,
397) {
8faf50e0 398 if start < pos {
6a06907d
XL
399 if !is_in_attribute_selector
400 && ((is_in_block == 0 && !is_in_media) || (is_in_media && is_in_block == 1))
401 {
8faf50e0
XL
402 let mut is_pseudo_class = false;
403 let mut add = 0;
404 if let Some(&Token::Char(ReservedChar::Colon)) = v.last() {
405 is_pseudo_class = true;
406 add = 1;
407 }
408 if let Ok(s) = SelectorElement::try_from(&source[start - add..pos]) {
409 if is_pseudo_class {
410 v.pop();
411 }
412 v.push(Token::SelectorElement(s));
413 } else {
414 let s = &source[start..pos];
6a06907d
XL
415 if !s.starts_with(':')
416 && !s.starts_with('.')
417 && !s.starts_with('#')
418 && !s.starts_with('@')
419 {
b7449926 420 v.push(Token::Other(s));
6a06907d 421 }
8faf50e0
XL
422 }
423 } else {
424 v.push(Token::Other(&source[start..pos]));
425 }
426 }
427}
428
6a06907d 429#[allow(clippy::comparison_chain)]
923072b8 430pub(super) fn tokenize<'a>(source: &'a str) -> Result<Tokens<'a>, &'static str> {
8faf50e0
XL
431 let mut v = Vec::with_capacity(1000);
432 let mut iterator = source.char_indices().peekable();
433 let mut start = 0;
434 let mut is_in_block: isize = 0;
435 let mut is_in_media = false;
436 let mut is_in_attribute_selector = false;
437
438 loop {
439 let (mut pos, c) = match iterator.next() {
440 Some(x) => x,
441 None => {
6a06907d
XL
442 fill_other(
443 source,
444 &mut v,
445 start,
446 source.len(),
447 is_in_block,
448 is_in_media,
449 is_in_attribute_selector,
450 );
451 break;
8faf50e0
XL
452 }
453 };
454 if let Ok(c) = ReservedChar::try_from(c) {
6a06907d
XL
455 fill_other(
456 source,
457 &mut v,
458 start,
459 pos,
460 is_in_block,
461 is_in_media,
462 is_in_attribute_selector,
463 );
464 is_in_media = is_in_media
465 || v.last()
466 .unwrap_or(&Token::Char(ReservedChar::Space))
5099ac24 467 .is_a_media();
5e7ed085
FG
468 match c {
469 ReservedChar::Quote | ReservedChar::DoubleQuote => {
b7449926
XL
470 if let Some(s) = get_string(source, &mut iterator, &mut pos, c) {
471 v.push(s);
8faf50e0 472 }
5e7ed085
FG
473 }
474 ReservedChar::Star
475 if *v.last().unwrap_or(&Token::Char(ReservedChar::Space))
476 == ReservedChar::Slash =>
477 {
b7449926
XL
478 v.pop();
479 if let Some(s) = get_comment(source, &mut iterator, &mut pos) {
480 v.push(s);
481 }
5e7ed085
FG
482 }
483 ReservedChar::OpenBracket => {
b7449926
XL
484 if is_in_attribute_selector {
485 return Err("Already in attribute selector");
486 }
487 is_in_attribute_selector = true;
488 v.push(Token::Char(c));
5e7ed085
FG
489 }
490 ReservedChar::CloseBracket => {
b7449926
XL
491 if !is_in_attribute_selector {
492 return Err("Unexpected ']'");
493 }
494 is_in_attribute_selector = false;
495 v.push(Token::Char(c));
5e7ed085
FG
496 }
497 ReservedChar::OpenCurlyBrace => {
b7449926
XL
498 is_in_block += 1;
499 v.push(Token::Char(c));
5e7ed085
FG
500 }
501 ReservedChar::CloseCurlyBrace => {
b7449926
XL
502 is_in_block -= 1;
503 if is_in_block < 0 {
504 return Err("Too much '}'");
505 } else if is_in_block == 0 {
506 is_in_media = false;
507 }
508 v.push(Token::Char(c));
5e7ed085
FG
509 }
510 ReservedChar::SemiColon if is_in_block == 0 => {
511 is_in_media = false;
512 v.push(Token::Char(c));
513 }
514 ReservedChar::EqualSign => {
515 match match v
516 .last()
517 .unwrap_or(&Token::Char(ReservedChar::Space))
518 .get_char()
519 .unwrap_or(ReservedChar::Space)
520 {
b7449926 521 ReservedChar::Tilde => Some(SelectorOperator::OneAttributeEquals),
5e7ed085
FG
522 ReservedChar::Pipe => {
523 Some(SelectorOperator::EqualsOrStartsWithFollowedByDash)
524 }
b7449926
XL
525 ReservedChar::Dollar => Some(SelectorOperator::EndsWith),
526 ReservedChar::Circumflex => Some(SelectorOperator::FirstStartsWith),
527 ReservedChar::Star => Some(SelectorOperator::Contains),
528 _ => None,
529 } {
530 Some(r) => {
531 v.pop();
532 v.push(Token::SelectorOperator(r));
533 }
534 None => v.push(Token::Char(c)),
535 }
5e7ed085
FG
536 }
537 c if !c.is_useless() => {
b7449926 538 v.push(Token::Char(c));
5e7ed085
FG
539 }
540 c => {
541 if !v
542 .last()
543 .unwrap_or(&Token::Char(ReservedChar::Space))
544 .is_useless()
545 && (!v
546 .last()
547 .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace))
548 .is_char()
549 || v.last()
550 .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace))
551 .is_operator()
552 || v.last()
553 .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace))
554 .get_char()
555 == Some(ReservedChar::CloseParenthese)
556 || v.last()
557 .unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace))
558 .get_char()
559 == Some(ReservedChar::CloseBracket))
560 {
561 v.push(Token::Char(ReservedChar::Space));
562 } else if let Ok(op) = Operator::try_from(c) {
563 v.push(Token::Operator(op));
564 }
565 }
8faf50e0
XL
566 }
567 start = pos + 1;
568 }
569 }
570 Ok(Tokens(clean_tokens(v)))
571}
572
6a06907d 573fn clean_tokens(mut v: Vec<Token<'_>>) -> Vec<Token<'_>> {
8faf50e0 574 let mut i = 0;
0bf4aa26
XL
575 let mut is_in_calc = false;
576 let mut paren = 0;
8faf50e0
XL
577
578 while i < v.len() {
0bf4aa26
XL
579 if v[i] == Token::Other("calc") {
580 is_in_calc = true;
6a06907d 581 } else if is_in_calc {
0bf4aa26
XL
582 if v[i] == Token::Char(ReservedChar::CloseParenthese) {
583 paren -= 1;
584 is_in_calc = paren != 0;
585 } else if v[i] == Token::Char(ReservedChar::OpenParenthese) {
586 paren += 1;
587 }
588 }
6a06907d 589
8faf50e0 590 if v[i].is_useless() {
6a06907d
XL
591 if i > 0 && v[i - 1] == Token::Char(ReservedChar::CloseBracket) {
592 if i + 1 < v.len()
593 && (v[i + 1].is_useless()
594 || v[i + 1] == Token::Char(ReservedChar::OpenCurlyBrace))
595 {
596 v.remove(i);
597 continue;
598 }
5099ac24
FG
599 } else if i > 0
600 && (v[i - 1] == Token::Other("and")
601 || v[i - 1] == Token::Other("or")
602 || v[i - 1] == Token::Other("not"))
603 {
604 // retain the space after "and", "or" or "not"
6a06907d
XL
605 } else if (is_in_calc && v[i - 1].is_useless())
606 || !is_in_calc
607 && ((i > 0
608 && ((v[i - 1].is_char()
609 && v[i - 1] != Token::Char(ReservedChar::CloseParenthese))
610 || v[i - 1].is_a_media()
611 || v[i - 1].is_a_license()))
612 || (i < v.len() - 1 && v[i + 1].is_char()))
613 {
8faf50e0 614 v.remove(i);
6a06907d 615 continue;
8faf50e0
XL
616 }
617 } else if v[i].is_comment() {
618 v.remove(i);
6a06907d 619 continue;
8faf50e0
XL
620 }
621 i += 1;
622 }
623 v
624}
625
626#[derive(Debug, PartialEq, Eq, Clone)]
923072b8
FG
627pub(super) struct Tokens<'a>(Vec<Token<'a>>);
628
629impl<'a> Tokens<'a> {
630 pub(super) fn write<W: std::io::Write>(self, mut w: W) -> std::io::Result<()> {
631 for token in self.0.iter() {
632 write!(w, "{}", token)?;
633 }
634 Ok(())
635 }
636}
8faf50e0
XL
637
638impl<'a> fmt::Display for Tokens<'a> {
923072b8 639 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8faf50e0
XL
640 for token in self.0.iter() {
641 write!(f, "{}", token)?;
642 }
643 Ok(())
644 }
645}
646
647#[test]
648fn css_basic() {
649 let s = r#"
650/*! just some license */
651.foo > #bar p:hover {
652 color: blue;
653 background: "blue";
654}
655
17df50a5 656/* a comment! */
8faf50e0
XL
657@media screen and (max-width: 640px) {
658 .block:hover {
659 display: block;
660 }
661}"#;
6a06907d
XL
662 let expected = vec![
663 Token::License(" just some license "),
664 Token::SelectorElement(SelectorElement::Class("foo")),
665 Token::Char(ReservedChar::SuperiorThan),
666 Token::SelectorElement(SelectorElement::Id("bar")),
667 Token::Char(ReservedChar::Space),
668 Token::SelectorElement(SelectorElement::Tag("p")),
669 Token::SelectorElement(SelectorElement::PseudoClass("hover")),
670 Token::Char(ReservedChar::OpenCurlyBrace),
671 Token::Other("color"),
672 Token::Char(ReservedChar::Colon),
673 Token::Other("blue"),
674 Token::Char(ReservedChar::SemiColon),
675 Token::Other("background"),
676 Token::Char(ReservedChar::Colon),
677 Token::String("\"blue\""),
678 Token::Char(ReservedChar::SemiColon),
679 Token::Char(ReservedChar::CloseCurlyBrace),
680 Token::SelectorElement(SelectorElement::Media("media")),
681 Token::Other("screen"),
682 Token::Char(ReservedChar::Space),
683 Token::Other("and"),
684 Token::Char(ReservedChar::Space),
685 Token::Char(ReservedChar::OpenParenthese),
686 Token::Other("max-width"),
687 Token::Char(ReservedChar::Colon),
688 Token::Other("640px"),
689 Token::Char(ReservedChar::CloseParenthese),
690 Token::Char(ReservedChar::OpenCurlyBrace),
691 Token::SelectorElement(SelectorElement::Class("block")),
692 Token::SelectorElement(SelectorElement::PseudoClass("hover")),
693 Token::Char(ReservedChar::OpenCurlyBrace),
694 Token::Other("display"),
695 Token::Char(ReservedChar::Colon),
696 Token::Other("block"),
697 Token::Char(ReservedChar::SemiColon),
698 Token::Char(ReservedChar::CloseCurlyBrace),
699 Token::Char(ReservedChar::CloseCurlyBrace),
700 ];
8faf50e0
XL
701 assert_eq!(tokenize(s), Ok(Tokens(expected)));
702}
703
704#[test]
705fn elem_selector() {
706 let s = r#"
707/** just some license */
708a[href*="example"] {
709 background: yellow;
710}
711a[href$=".org"] {
712 font-style: italic;
713}
714span[lang|="zh"] {
715 color: red;
716}
717a[href^="/"] {
718 background-color: gold;
719}
720div[value~="test"] {
721 border-width: 1px;
722}
723span[lang="pt"] {
17df50a5 724 font-size: 12em; /* I love big fonts */
8faf50e0
XL
725}
726"#;
6a06907d
XL
727 let expected = vec![
728 Token::License(" just some license "),
729 Token::SelectorElement(SelectorElement::Tag("a")),
730 Token::Char(ReservedChar::OpenBracket),
731 Token::Other("href"),
732 Token::SelectorOperator(SelectorOperator::Contains),
733 Token::String("\"example\""),
734 Token::Char(ReservedChar::CloseBracket),
735 Token::Char(ReservedChar::OpenCurlyBrace),
736 Token::Other("background"),
737 Token::Char(ReservedChar::Colon),
738 Token::Other("yellow"),
739 Token::Char(ReservedChar::SemiColon),
740 Token::Char(ReservedChar::CloseCurlyBrace),
741 Token::SelectorElement(SelectorElement::Tag("a")),
742 Token::Char(ReservedChar::OpenBracket),
743 Token::Other("href"),
744 Token::SelectorOperator(SelectorOperator::EndsWith),
745 Token::String("\".org\""),
746 Token::Char(ReservedChar::CloseBracket),
747 Token::Char(ReservedChar::OpenCurlyBrace),
748 Token::Other("font-style"),
749 Token::Char(ReservedChar::Colon),
750 Token::Other("italic"),
751 Token::Char(ReservedChar::SemiColon),
752 Token::Char(ReservedChar::CloseCurlyBrace),
753 Token::SelectorElement(SelectorElement::Tag("span")),
754 Token::Char(ReservedChar::OpenBracket),
755 Token::Other("lang"),
756 Token::SelectorOperator(SelectorOperator::EqualsOrStartsWithFollowedByDash),
757 Token::String("\"zh\""),
758 Token::Char(ReservedChar::CloseBracket),
759 Token::Char(ReservedChar::OpenCurlyBrace),
760 Token::Other("color"),
761 Token::Char(ReservedChar::Colon),
762 Token::Other("red"),
763 Token::Char(ReservedChar::SemiColon),
764 Token::Char(ReservedChar::CloseCurlyBrace),
765 Token::SelectorElement(SelectorElement::Tag("a")),
766 Token::Char(ReservedChar::OpenBracket),
767 Token::Other("href"),
768 Token::SelectorOperator(SelectorOperator::FirstStartsWith),
769 Token::String("\"/\""),
770 Token::Char(ReservedChar::CloseBracket),
771 Token::Char(ReservedChar::OpenCurlyBrace),
772 Token::Other("background-color"),
773 Token::Char(ReservedChar::Colon),
774 Token::Other("gold"),
775 Token::Char(ReservedChar::SemiColon),
776 Token::Char(ReservedChar::CloseCurlyBrace),
777 Token::SelectorElement(SelectorElement::Tag("div")),
778 Token::Char(ReservedChar::OpenBracket),
779 Token::Other("value"),
780 Token::SelectorOperator(SelectorOperator::OneAttributeEquals),
781 Token::String("\"test\""),
782 Token::Char(ReservedChar::CloseBracket),
783 Token::Char(ReservedChar::OpenCurlyBrace),
784 Token::Other("border-width"),
785 Token::Char(ReservedChar::Colon),
786 Token::Other("1px"),
787 Token::Char(ReservedChar::SemiColon),
788 Token::Char(ReservedChar::CloseCurlyBrace),
789 Token::SelectorElement(SelectorElement::Tag("span")),
790 Token::Char(ReservedChar::OpenBracket),
791 Token::Other("lang"),
792 Token::Char(ReservedChar::EqualSign),
793 Token::String("\"pt\""),
794 Token::Char(ReservedChar::CloseBracket),
795 Token::Char(ReservedChar::OpenCurlyBrace),
796 Token::Other("font-size"),
797 Token::Char(ReservedChar::Colon),
798 Token::Other("12em"),
799 Token::Char(ReservedChar::SemiColon),
800 Token::Char(ReservedChar::CloseCurlyBrace),
801 ];
8faf50e0
XL
802 assert_eq!(tokenize(s), Ok(Tokens(expected)));
803}
804
805#[test]
806fn check_media() {
807 let s = "@media (max-width: 700px) { color: red; }";
808
6a06907d
XL
809 let expected = vec![
810 Token::SelectorElement(SelectorElement::Media("media")),
811 Token::Char(ReservedChar::OpenParenthese),
812 Token::Other("max-width"),
813 Token::Char(ReservedChar::Colon),
814 Token::Other("700px"),
815 Token::Char(ReservedChar::CloseParenthese),
816 Token::Char(ReservedChar::OpenCurlyBrace),
817 Token::SelectorElement(SelectorElement::Tag("color")),
818 Token::Char(ReservedChar::Colon),
819 Token::Other("red"),
820 Token::Char(ReservedChar::SemiColon),
821 Token::Char(ReservedChar::CloseCurlyBrace),
822 ];
b7449926
XL
823
824 assert_eq!(tokenize(s), Ok(Tokens(expected)));
825}
826
5099ac24
FG
827#[test]
828fn check_supports() {
829 let s = "@supports not (display: grid) { div { float: right; } }";
830
831 let expected = vec![
832 Token::SelectorElement(SelectorElement::Media("supports")),
833 Token::Other("not"),
834 Token::Char(ReservedChar::Space),
835 Token::Char(ReservedChar::OpenParenthese),
836 Token::Other("display"),
837 Token::Char(ReservedChar::Colon),
838 Token::Other("grid"),
839 Token::Char(ReservedChar::CloseParenthese),
840 Token::Char(ReservedChar::OpenCurlyBrace),
841 Token::SelectorElement(SelectorElement::Tag("div")),
842 Token::Char(ReservedChar::OpenCurlyBrace),
843 Token::Other("float"),
844 Token::Char(ReservedChar::Colon),
845 Token::Other("right"),
846 Token::Char(ReservedChar::SemiColon),
847 Token::Char(ReservedChar::CloseCurlyBrace),
848 Token::Char(ReservedChar::CloseCurlyBrace),
849 ];
850
851 assert_eq!(tokenize(s), Ok(Tokens(expected)));
852}
853
b7449926
XL
854#[test]
855fn check_calc() {
856 let s = ".foo { width: calc(100% - 34px); }";
857
6a06907d
XL
858 let expected = vec![
859 Token::SelectorElement(SelectorElement::Class("foo")),
860 Token::Char(ReservedChar::OpenCurlyBrace),
861 Token::Other("width"),
862 Token::Char(ReservedChar::Colon),
863 Token::Other("calc"),
864 Token::Char(ReservedChar::OpenParenthese),
865 Token::Other("100%"),
866 Token::Char(ReservedChar::Space),
867 Token::Other("-"),
868 Token::Char(ReservedChar::Space),
869 Token::Other("34px"),
870 Token::Char(ReservedChar::CloseParenthese),
871 Token::Char(ReservedChar::SemiColon),
872 Token::Char(ReservedChar::CloseCurlyBrace),
873 ];
8faf50e0
XL
874 assert_eq!(tokenize(s), Ok(Tokens(expected)));
875}