]> git.proxmox.com Git - rustc.git/blame - src/vendor/minifier/src/css/token.rs
New upstream version 1.29.0+dfsg1
[rustc.git] / src / vendor / minifier / src / css / token.rs
CommitLineData
8faf50e0
XL
1// MIT License
2//
3// Copyright (c) 2018 Guillaume Gomez
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23use std::fmt;
24use std::iter::Peekable;
25use std::str::CharIndices;
26
27pub trait MyTryFrom<T>: Sized {
28 type Error;
29 fn try_from(value: T) -> Result<Self, Self::Error>;
30}
31
32#[derive(Debug, PartialEq, Eq, Clone, Copy)]
33pub enum ReservedChar {
34 Comma,
35 SuperiorThan,
36 OpenParenthese,
37 CloseParenthese,
38 OpenCurlyBrace,
39 CloseCurlyBrace,
40 OpenBracket,
41 CloseBracket,
42 Colon,
43 SemiColon,
44 Slash,
45 Plus,
46 EqualSign,
47 Space,
48 Tab,
49 Backline,
50 Star,
51 Quote,
52 DoubleQuote,
53 Pipe,
54 Tilde,
55 Dollar,
56 Circumflex,
57}
58
59impl fmt::Display for ReservedChar {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61 write!(f, "{}",
62 match *self {
63 ReservedChar::Comma => ',',
64 ReservedChar::OpenParenthese => '(',
65 ReservedChar::CloseParenthese => ')',
66 ReservedChar::OpenCurlyBrace => '{',
67 ReservedChar::CloseCurlyBrace => '}',
68 ReservedChar::OpenBracket => '[',
69 ReservedChar::CloseBracket => ']',
70 ReservedChar::Colon => ':',
71 ReservedChar::SemiColon => ';',
72 ReservedChar::Slash => '/',
73 ReservedChar::Star => '*',
74 ReservedChar::Plus => '+',
75 ReservedChar::EqualSign => '=',
76 ReservedChar::Space => ' ',
77 ReservedChar::Tab => '\t',
78 ReservedChar::Backline => '\n',
79 ReservedChar::SuperiorThan => '>',
80 ReservedChar::Quote => '\'',
81 ReservedChar::DoubleQuote => '"',
82 ReservedChar::Pipe => '|',
83 ReservedChar::Tilde => '~',
84 ReservedChar::Dollar => '$',
85 ReservedChar::Circumflex => '^',
86 })
87 }
88}
89
90impl MyTryFrom<char> for ReservedChar {
91 type Error = &'static str;
92
93 fn try_from(value: char) -> Result<ReservedChar, Self::Error> {
94 match value {
95 '\'' => Ok(ReservedChar::Quote),
96 '"' => Ok(ReservedChar::DoubleQuote),
97 ',' => Ok(ReservedChar::Comma),
98 '(' => Ok(ReservedChar::OpenParenthese),
99 ')' => Ok(ReservedChar::CloseParenthese),
100 '{' => Ok(ReservedChar::OpenCurlyBrace),
101 '}' => Ok(ReservedChar::CloseCurlyBrace),
102 '[' => Ok(ReservedChar::OpenBracket),
103 ']' => Ok(ReservedChar::CloseBracket),
104 ':' => Ok(ReservedChar::Colon),
105 ';' => Ok(ReservedChar::SemiColon),
106 '/' => Ok(ReservedChar::Slash),
107 '*' => Ok(ReservedChar::Star),
108 '+' => Ok(ReservedChar::Plus),
109 '=' => Ok(ReservedChar::EqualSign),
110 ' ' => Ok(ReservedChar::Space),
111 '\t' => Ok(ReservedChar::Tab),
112 '\n' |
113 '\r' => Ok(ReservedChar::Backline),
114 '>' => Ok(ReservedChar::SuperiorThan),
115 '|' => Ok(ReservedChar::Pipe),
116 '~' => Ok(ReservedChar::Tilde),
117 '$' => Ok(ReservedChar::Dollar),
118 '^' => Ok(ReservedChar::Circumflex),
119 _ => Err("Unknown reserved char"),
120 }
121 }
122}
123
124impl ReservedChar {
125 fn is_useless(&self) -> bool {
126 *self == ReservedChar::Space ||
127 *self == ReservedChar::Tab ||
128 *self == ReservedChar::Backline
129 }
130}
131
132#[derive(Eq, PartialEq, Clone, Debug)]
133pub enum SelectorElement<'a> {
134 PseudoClass(&'a str),
135 Class(&'a str),
136 Id(&'a str),
137 Tag(&'a str),
138 Media(&'a str),
139}
140
141impl<'a> MyTryFrom<&'a str> for SelectorElement<'a> {
142 type Error = &'static str;
143
144 fn try_from(value: &'a str) -> Result<SelectorElement, Self::Error> {
145 if value.starts_with('.') {
146 if value.len() > 1 {
147 Ok(SelectorElement::Class(&value[1..]))
148 } else {
149 Err("cannot determine selector")
150 }
151 } else if value.starts_with('#') {
152 if value.len() > 1 {
153 Ok(SelectorElement::Id(&value[1..]))
154 } else {
155 Err("cannot determine selector")
156 }
157 } else if value.starts_with('@') {
158 if value.len() > 1 {
159 Ok(SelectorElement::Media(&value[1..]))
160 } else {
161 Err("cannot determine selector")
162 }
163 } else if value.starts_with(':') {
164 if value.len() > 1 {
165 Ok(SelectorElement::PseudoClass(&value[1..]))
166 } else {
167 Err("cannot determine selector")
168 }
169 } else if value.chars().next().unwrap_or(' ').is_alphabetic() {
170 Ok(SelectorElement::Tag(value))
171 } else {
172 Err("unknown selector")
173 }
174 }
175}
176
177impl<'a> fmt::Display for SelectorElement<'a> {
178 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179 match *self {
180 SelectorElement::Class(c) => write!(f, ".{}", c),
181 SelectorElement::Id(i) => write!(f, "#{}", i),
182 SelectorElement::Tag(t) => write!(f, "{}", t),
183 SelectorElement::Media(m) => write!(f, "@{} ", m),
184 SelectorElement::PseudoClass(pc) => write!(f, ":{}", pc),
185 }
186 }
187}
188
189#[derive(Eq, PartialEq, Clone, Debug, Copy)]
190pub enum SelectorOperator {
191 /// `~=`
192 OneAttributeEquals,
193 /// `|=`
194 EqualsOrStartsWithFollowedByDash,
195 /// `$=`
196 EndsWith,
197 /// `^=`
198 FirstStartsWith,
199 /// `*=`
200 Contains,
201}
202
203impl fmt::Display for SelectorOperator {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 match *self {
206 SelectorOperator::OneAttributeEquals => write!(f, "{}", "~="),
207 SelectorOperator::EqualsOrStartsWithFollowedByDash => write!(f, "{}", "|="),
208 SelectorOperator::EndsWith => write!(f, "{}", "$="),
209 SelectorOperator::FirstStartsWith => write!(f, "{}", "^="),
210 SelectorOperator::Contains => write!(f, "{}", "*="),
211 }
212 }
213}
214
215#[derive(Eq, PartialEq, Clone, Debug)]
216pub enum Token<'a> {
217 /// Comment.
218 Comment(&'a str),
219 /// Comment starting with `/**`.
220 License(&'a str),
221 Char(ReservedChar),
222 Other(&'a str),
223 SelectorElement(SelectorElement<'a>),
224 String(&'a str),
225 SelectorOperator(SelectorOperator),
226}
227
228impl<'a> fmt::Display for Token<'a> {
229 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230 match *self {
231 // Token::AtRule(at_rule) => write!(f, "{}", at_rule, content),
232 // Token::ElementRule(selectors) => write!(f, "{}", x),
233 Token::Comment(c) => write!(f, "{}", c),
234 Token::License(l) => writeln!(f, "/*!{}*/", l),
235 Token::Char(c) => write!(f, "{}", c),
236 Token::Other(s) => write!(f, "{}", s),
237 Token::SelectorElement(ref se) => write!(f, "{}", se),
238 Token::String(s) => write!(f, "{}", s),
239 Token::SelectorOperator(so) => write!(f, "{}", so),
240 }
241 }
242}
243
244impl<'a> Token<'a> {
245 fn is_comment(&self) -> bool {
246 match *self {
247 Token::Comment(_) => true,
248 _ => false,
249 }
250 }
251
252 fn is_char(&self) -> bool {
253 match *self {
254 Token::Char(_) => true,
255 _ => false,
256 }
257 }
258
259 fn get_char(&self) -> Option<ReservedChar> {
260 match *self {
261 Token::Char(c) => Some(c),
262 _ => None,
263 }
264 }
265
266 fn is_useless(&self) -> bool {
267 match *self {
268 Token::Char(c) => c.is_useless(),
269 _ => false,
270 }
271 }
272
273 fn is_media(&self, media: &str) -> bool {
274 match *self {
275 Token::SelectorElement(SelectorElement::Media(s)) => s == media,
276 _ => false,
277 }
278 }
279
280 fn is_a_media(&self) -> bool {
281 match *self {
282 Token::SelectorElement(SelectorElement::Media(_)) => true,
283 _ => false,
284 }
285 }
286
287 fn is_a_license(&self) -> bool {
288 match *self {
289 Token::License(_) => true,
290 _ => false,
291 }
292 }
293}
294
295impl<'a> PartialEq<ReservedChar> for Token<'a> {
296 fn eq(&self, other: &ReservedChar) -> bool {
297 match *self {
298 Token::Char(c) => c == *other,
299 _ => false,
300 }
301 }
302}
303
304fn get_line_comment<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
305 start_pos: &mut usize) -> Option<Token<'a>> {
306 *start_pos += 1;
307 while let Some((pos, c)) = iterator.next() {
308 if let Ok(c) = ReservedChar::try_from(c) {
309 if c == ReservedChar::Backline {
310 let ret = Some(Token::Comment(&source[*start_pos..pos]));
311 *start_pos = pos;
312 return ret;
313 }
314 }
315 }
316 None
317}
318
319fn get_comment<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>,
320 start_pos: &mut usize) -> Option<Token<'a>> {
321 let mut prev = ReservedChar::Quote;
322 *start_pos += 1;
323 let builder = if let Some((_, c)) = iterator.next() {
324 if c == '!' || c == '*' {
325 *start_pos += 1;
326 Token::License
327 } else {
328 if let Ok(c) = ReservedChar::try_from(c) {
329 prev = c;
330 }
331 Token::Comment
332 }
333 } else {
334 Token::Comment
335 };
336
337 while let Some((pos, c)) = iterator.next() {
338 if let Ok(c) = ReservedChar::try_from(c) {
339 if c == ReservedChar::Slash && prev == ReservedChar::Star {
340 let ret = Some(builder(&source[*start_pos..pos - 1]));
341 *start_pos = pos;
342 return ret;
343 }
344 prev = c;
345 } else {
346 prev = ReservedChar::Space;
347 }
348 }
349 None
350}
351
352fn get_string<'a>(source: &'a str, iterator: &mut Peekable<CharIndices>, start_pos: &mut usize,
353 start: ReservedChar) -> Option<Token<'a>> {
354 while let Some((pos, c)) = iterator.next() {
355 if c == '\\' {
356 // we skip next character
357 iterator.next();
358 continue
359 }
360 if let Ok(c) = ReservedChar::try_from(c) {
361 if c == start {
362 let ret = Some(Token::String(&source[*start_pos..pos + 1]));
363 *start_pos = pos;
364 return ret;
365 }
366 }
367 }
368 None
369}
370
371fn fill_other<'a>(source: &'a str, v: &mut Vec<Token<'a>>, start: usize, pos: usize,
372 is_in_block: isize, is_in_media: bool, is_in_attribute_selector: bool) {
373 if start < pos {
374 if is_in_attribute_selector == false &&
375 ((is_in_block == 0 && is_in_media == false) ||
376 (is_in_media == true && is_in_block == 1)) {
377 let mut is_pseudo_class = false;
378 let mut add = 0;
379 if let Some(&Token::Char(ReservedChar::Colon)) = v.last() {
380 is_pseudo_class = true;
381 add = 1;
382 }
383 if let Ok(s) = SelectorElement::try_from(&source[start - add..pos]) {
384 if is_pseudo_class {
385 v.pop();
386 }
387 v.push(Token::SelectorElement(s));
388 } else {
389 let s = &source[start..pos];
390 if !s.starts_with(':') && !s.starts_with('.') && !s.starts_with('#') &&
391 !s.starts_with('@') {
392 v.push(Token::Other(s));
393 }
394 }
395 } else {
396 v.push(Token::Other(&source[start..pos]));
397 }
398 }
399}
400
401pub fn tokenize<'a>(source: &'a str) -> Result<Tokens<'a>, &'static str> {
402 let mut v = Vec::with_capacity(1000);
403 let mut iterator = source.char_indices().peekable();
404 let mut start = 0;
405 let mut is_in_block: isize = 0;
406 let mut is_in_media = false;
407 let mut is_in_attribute_selector = false;
408
409 loop {
410 let (mut pos, c) = match iterator.next() {
411 Some(x) => x,
412 None => {
413 fill_other(source, &mut v, start, source.len(), is_in_block, is_in_media,
414 is_in_attribute_selector);
415 break
416 }
417 };
418 if let Ok(c) = ReservedChar::try_from(c) {
419 fill_other(source, &mut v, start, pos, is_in_block, is_in_media,
420 is_in_attribute_selector);
421 is_in_media = is_in_media || v.last()
422 .unwrap_or(&Token::Char(ReservedChar::Space))
423 .is_media("media");
424 if c == ReservedChar::Quote || c == ReservedChar::DoubleQuote {
425 if let Some(s) = get_string(source, &mut iterator, &mut pos, c) {
426 v.push(s);
427 }
428 } else if c == ReservedChar::Star &&
429 *v.last().unwrap_or(&Token::Char(ReservedChar::Space)) == ReservedChar::Slash {
430 v.pop();
431 if let Some(s) = get_comment(source, &mut iterator, &mut pos) {
432 v.push(s);
433 }
434 } else if c == ReservedChar::Slash &&
435 *v.last().unwrap_or(&Token::Char(ReservedChar::Space)) == ReservedChar::Slash {
436 v.pop();
437 if let Some(s) = get_line_comment(source, &mut iterator, &mut pos) {
438 v.push(s);
439 }
440 } else if c == ReservedChar::OpenBracket {
441 if is_in_attribute_selector {
442 return Err("Already in attribute selector");
443 }
444 is_in_attribute_selector = true;
445 v.push(Token::Char(c));
446 } else if c == ReservedChar::CloseBracket {
447 if !is_in_attribute_selector {
448 return Err("Unexpected ']'");
449 }
450 is_in_attribute_selector = false;
451 v.push(Token::Char(c));
452 } else if c == ReservedChar::OpenCurlyBrace {
453 is_in_block += 1;
454 v.push(Token::Char(c));
455 } else if c == ReservedChar::CloseCurlyBrace {
456 is_in_block -= 1;
457 if is_in_block < 0 {
458 return Err("Too much '}'");
459 } else if is_in_block == 0 {
460 is_in_media = false;
461 }
462 v.push(Token::Char(c));
463 } else if c == ReservedChar::EqualSign {
464 match match v.last()
465 .unwrap_or(&Token::Char(ReservedChar::Space))
466 .get_char()
467 .unwrap_or(ReservedChar::Space) {
468 ReservedChar::Tilde => Some(SelectorOperator::OneAttributeEquals),
469 ReservedChar::Pipe => Some(SelectorOperator::EqualsOrStartsWithFollowedByDash),
470 ReservedChar::Dollar => Some(SelectorOperator::EndsWith),
471 ReservedChar::Circumflex => Some(SelectorOperator::FirstStartsWith),
472 ReservedChar::Star => Some(SelectorOperator::Contains),
473 _ => None,
474 } {
475 Some(r) => {
476 v.pop();
477 v.push(Token::SelectorOperator(r));
478 }
479 None => v.push(Token::Char(c)),
480 }
481 } else if !c.is_useless() {
482 v.push(Token::Char(c));
483 } else if !v.last().unwrap_or(&Token::Char(ReservedChar::Space)).is_useless() &&
484 !v.last().unwrap_or(&Token::Char(ReservedChar::OpenCurlyBrace)).is_char() {
485 v.push(Token::Char(ReservedChar::Space));
486 }
487 start = pos + 1;
488 }
489 }
490 Ok(Tokens(clean_tokens(v)))
491}
492
493fn clean_tokens<'a>(mut v: Vec<Token<'a>>) -> Vec<Token<'a>> {
494 let mut i = 0;
495
496 while i < v.len() {
497 if v[i].is_useless() {
498 if (i > 0 && (v[i - 1].is_char() ||
499 v[i - 1].is_a_media() ||
500 v[i - 1].is_a_license())) ||
501 (i < v.len() - 1 && v[i + 1].is_char()) {
502 v.remove(i);
503 continue
504 }
505 } else if v[i].is_comment() {
506 v.remove(i);
507 continue
508 }
509 i += 1;
510 }
511 v
512}
513
514#[derive(Debug, PartialEq, Eq, Clone)]
515pub struct Tokens<'a>(pub Vec<Token<'a>>);
516
517impl<'a> fmt::Display for Tokens<'a> {
518 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
519 for token in self.0.iter() {
520 write!(f, "{}", token)?;
521 }
522 Ok(())
523 }
524}
525
526#[test]
527fn css_basic() {
528 let s = r#"
529/*! just some license */
530.foo > #bar p:hover {
531 color: blue;
532 background: "blue";
533}
534
535// a comment!
536@media screen and (max-width: 640px) {
537 .block:hover {
538 display: block;
539 }
540}"#;
541 let expected = vec![Token::License(" just some license "),
542 Token::SelectorElement(SelectorElement::Class("foo")),
543 Token::Char(ReservedChar::SuperiorThan),
544 Token::SelectorElement(SelectorElement::Id("bar")),
545 Token::Char(ReservedChar::Space),
546 Token::SelectorElement(SelectorElement::Tag("p")),
547 Token::SelectorElement(SelectorElement::PseudoClass("hover")),
548 Token::Char(ReservedChar::OpenCurlyBrace),
549 Token::Other("color"),
550 Token::Char(ReservedChar::Colon),
551 Token::Other("blue"),
552 Token::Char(ReservedChar::SemiColon),
553 Token::Other("background"),
554 Token::Char(ReservedChar::Colon),
555 Token::String("\"blue\""),
556 Token::Char(ReservedChar::SemiColon),
557 Token::Char(ReservedChar::CloseCurlyBrace),
558 Token::SelectorElement(SelectorElement::Media("media")),
559 Token::Other("screen"),
560 Token::Char(ReservedChar::Space),
561 Token::Other("and"),
562 Token::Char(ReservedChar::OpenParenthese),
563 Token::Other("max-width"),
564 Token::Char(ReservedChar::Colon),
565 Token::Other("640px"),
566 Token::Char(ReservedChar::CloseParenthese),
567 Token::Char(ReservedChar::OpenCurlyBrace),
568 Token::SelectorElement(SelectorElement::Class("block")),
569 Token::SelectorElement(SelectorElement::PseudoClass("hover")),
570 Token::Char(ReservedChar::OpenCurlyBrace),
571 Token::Other("display"),
572 Token::Char(ReservedChar::Colon),
573 Token::Other("block"),
574 Token::Char(ReservedChar::SemiColon),
575 Token::Char(ReservedChar::CloseCurlyBrace),
576 Token::Char(ReservedChar::CloseCurlyBrace)];
577 assert_eq!(tokenize(s), Ok(Tokens(expected)));
578}
579
580#[test]
581fn elem_selector() {
582 let s = r#"
583/** just some license */
584a[href*="example"] {
585 background: yellow;
586}
587a[href$=".org"] {
588 font-style: italic;
589}
590span[lang|="zh"] {
591 color: red;
592}
593a[href^="/"] {
594 background-color: gold;
595}
596div[value~="test"] {
597 border-width: 1px;
598}
599span[lang="pt"] {
600 font-size: 12em; // I love big fonts
601}
602"#;
603 let expected = vec![Token::License(" just some license "),
604 Token::SelectorElement(SelectorElement::Tag("a")),
605 Token::Char(ReservedChar::OpenBracket),
606 Token::Other("href"),
607 Token::SelectorOperator(SelectorOperator::Contains),
608 Token::String("\"example\""),
609 Token::Char(ReservedChar::CloseBracket),
610 Token::Char(ReservedChar::OpenCurlyBrace),
611 Token::Other("background"),
612 Token::Char(ReservedChar::Colon),
613 Token::Other("yellow"),
614 Token::Char(ReservedChar::SemiColon),
615 Token::Char(ReservedChar::CloseCurlyBrace),
616
617 Token::SelectorElement(SelectorElement::Tag("a")),
618 Token::Char(ReservedChar::OpenBracket),
619 Token::Other("href"),
620 Token::SelectorOperator(SelectorOperator::EndsWith),
621 Token::String("\".org\""),
622 Token::Char(ReservedChar::CloseBracket),
623 Token::Char(ReservedChar::OpenCurlyBrace),
624 Token::Other("font-style"),
625 Token::Char(ReservedChar::Colon),
626 Token::Other("italic"),
627 Token::Char(ReservedChar::SemiColon),
628 Token::Char(ReservedChar::CloseCurlyBrace),
629
630 Token::SelectorElement(SelectorElement::Tag("span")),
631 Token::Char(ReservedChar::OpenBracket),
632 Token::Other("lang"),
633 Token::SelectorOperator(SelectorOperator::EqualsOrStartsWithFollowedByDash),
634 Token::String("\"zh\""),
635 Token::Char(ReservedChar::CloseBracket),
636 Token::Char(ReservedChar::OpenCurlyBrace),
637 Token::Other("color"),
638 Token::Char(ReservedChar::Colon),
639 Token::Other("red"),
640 Token::Char(ReservedChar::SemiColon),
641 Token::Char(ReservedChar::CloseCurlyBrace),
642
643 Token::SelectorElement(SelectorElement::Tag("a")),
644 Token::Char(ReservedChar::OpenBracket),
645 Token::Other("href"),
646 Token::SelectorOperator(SelectorOperator::FirstStartsWith),
647 Token::String("\"/\""),
648 Token::Char(ReservedChar::CloseBracket),
649 Token::Char(ReservedChar::OpenCurlyBrace),
650 Token::Other("background-color"),
651 Token::Char(ReservedChar::Colon),
652 Token::Other("gold"),
653 Token::Char(ReservedChar::SemiColon),
654 Token::Char(ReservedChar::CloseCurlyBrace),
655
656 Token::SelectorElement(SelectorElement::Tag("div")),
657 Token::Char(ReservedChar::OpenBracket),
658 Token::Other("value"),
659 Token::SelectorOperator(SelectorOperator::OneAttributeEquals),
660 Token::String("\"test\""),
661 Token::Char(ReservedChar::CloseBracket),
662 Token::Char(ReservedChar::OpenCurlyBrace),
663 Token::Other("border-width"),
664 Token::Char(ReservedChar::Colon),
665 Token::Other("1px"),
666 Token::Char(ReservedChar::SemiColon),
667 Token::Char(ReservedChar::CloseCurlyBrace),
668
669 Token::SelectorElement(SelectorElement::Tag("span")),
670 Token::Char(ReservedChar::OpenBracket),
671 Token::Other("lang"),
672 Token::Char(ReservedChar::EqualSign),
673 Token::String("\"pt\""),
674 Token::Char(ReservedChar::CloseBracket),
675 Token::Char(ReservedChar::OpenCurlyBrace),
676 Token::Other("font-size"),
677 Token::Char(ReservedChar::Colon),
678 Token::Other("12em"),
679 Token::Char(ReservedChar::SemiColon),
680 Token::Char(ReservedChar::CloseCurlyBrace)];
681 assert_eq!(tokenize(s), Ok(Tokens(expected)));
682}
683
684#[test]
685fn check_media() {
686 let s = "@media (max-width: 700px) { color: red; }";
687
688 let expected = vec![Token::SelectorElement(SelectorElement::Media("media")),
689 Token::Char(ReservedChar::OpenParenthese),
690 Token::Other("max-width"),
691 Token::Char(ReservedChar::Colon),
692 Token::Other("700px"),
693 Token::Char(ReservedChar::CloseParenthese),
694 Token::Char(ReservedChar::OpenCurlyBrace),
695 Token::SelectorElement(SelectorElement::Tag("color")),
696 Token::Char(ReservedChar::Colon),
697 Token::Other("red"),
698 Token::Char(ReservedChar::SemiColon),
699 Token::Char(ReservedChar::CloseCurlyBrace)];
700 assert_eq!(tokenize(s), Ok(Tokens(expected)));
701}