1use std::ops::Range;
2
3use cssparser::{CowRcStr, ParseError, ParserInput, Token};
4
5pub mod error;
6pub mod js_bindings;
7pub mod output;
8mod step;
9
10use output::StyleSheetOutput;
11use step::{StepParser, StepToken};
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct StyleSheetOptions {
15 pub class_prefix: Option<String>,
16 pub class_prefix_sign: Option<String>,
17 pub rpx_ratio: f32,
18 pub import_sign: Option<String>,
19 pub convert_host: bool,
20 pub host_is: Option<String>,
21}
22
23impl Default for StyleSheetOptions {
24 fn default() -> Self {
25 Self {
26 class_prefix: None,
27 class_prefix_sign: None,
28 rpx_ratio: 750.,
29 import_sign: None,
30 convert_host: false,
31 host_is: None,
32 }
33 }
34}
35
36pub struct StyleSheetTransformer {
37 options: StyleSheetOptions,
38 path: String,
39 normal_output: StyleSheetOutput,
40 low_priority_output: StyleSheetOutput,
41 using_low_priority: bool,
42 warnings: Vec<error::ParseError>,
43 cur_at_rule_stacks: Vec<String>,
44}
45
46impl StyleSheetTransformer {
47 pub fn from_css(path: &str, css: &str, options: StyleSheetOptions) -> Self {
48 let parser_input = &mut ParserInput::new(css);
49 let parser = &mut cssparser::Parser::new(parser_input);
50 let mut input = StepParser::wrap(parser);
51
52 let mut this = Self {
54 options,
55 path: path.to_string(),
56 normal_output: StyleSheetOutput::new(path, css),
57 low_priority_output: StyleSheetOutput::new(path, css),
58 using_low_priority: false,
59 warnings: vec![],
60 cur_at_rule_stacks: vec![],
61 };
62
63 {
64 parse_rules(&mut input, &mut this);
65 }
66 this
67 }
68
69 fn add_warning(&mut self, kind: error::ParseErrorKind, location: Range<error::Position>) {
70 self.warnings.push(error::ParseError {
71 path: self.path.clone(),
72 kind,
73 location,
74 });
75 }
76
77 pub fn warnings(&self) -> impl Iterator<Item = &error::ParseError> {
78 self.warnings.iter()
79 }
80
81 pub fn take_warnings(&mut self) -> Vec<error::ParseError> {
82 std::mem::replace(&mut self.warnings, vec![])
83 }
84
85 pub fn output(self) -> StyleSheetOutput {
86 self.normal_output
87 }
88
89 pub fn output_and_low_priority_output(self) -> (StyleSheetOutput, StyleSheetOutput) {
90 (self.normal_output, self.low_priority_output)
91 }
92
93 fn current_output(&self) -> &StyleSheetOutput {
94 if self.using_low_priority {
95 &self.low_priority_output
96 } else {
97 &self.normal_output
98 }
99 }
100
101 fn current_output_mut(&mut self) -> &mut StyleSheetOutput {
102 if self.using_low_priority {
103 &mut self.low_priority_output
104 } else {
105 &mut self.normal_output
106 }
107 }
108
109 fn append_nested_block(
110 &mut self,
111 token: StepToken,
112 _input: &mut StepParser,
113 ) -> StepToken<'static> {
114 let close = match &*token {
115 Token::CurlyBracketBlock => Token::CloseCurlyBracket,
116 Token::SquareBracketBlock => Token::CloseSquareBracket,
117 Token::ParenthesisBlock => Token::CloseParenthesis,
118 Token::Function(_) => Token::CloseParenthesis,
119 _ => unreachable!(),
120 };
121 let position = token.position;
122 self.current_output_mut().append_token(token, None);
123 StepToken::wrap(close, position)
124 }
125
126 fn append_nested_block_close(&mut self, close: StepToken<'static>, _input: &mut StepParser) {
127 self.current_output_mut().append_token(close, None);
128 }
129
130 fn write_in_low_priority<R>(
131 &mut self,
132 input: &mut StepParser,
133 f: impl FnOnce(&mut Self, &mut StepParser) -> R,
134 ) -> R {
135 self.using_low_priority = true;
136 for item in self.cur_at_rule_stacks.iter() {
137 self.low_priority_output.append_raw(&item);
138 self.low_priority_output.append_raw("{");
139 }
140 let r = f(self, input);
141 for _ in self.cur_at_rule_stacks.iter() {
142 self.low_priority_output.append_raw("}");
143 }
144 self.using_low_priority = false;
145 r
146 }
147
148 fn append_token(&mut self, token: StepToken, _input: &mut StepParser, src: Option<Token>) {
149 self.current_output_mut().append_token(token, src)
150 }
151
152 fn append_token_space_preserved(
153 &mut self,
154 token: StepToken,
155 _input: &mut StepParser,
156 src: Option<Token>,
157 ) {
158 self.current_output_mut()
159 .append_token_space_preserved(token, src)
160 }
161
162 fn cur_output_utf8_len(&self) -> usize {
163 self.current_output().cur_utf8_len()
164 }
165
166 fn get_output_segment(&self, range: std::ops::Range<usize>) -> &str {
167 self.current_output().get_output_segment(range)
168 }
169
170 fn wrap_at_rule_output<'a, R>(
171 &mut self,
172 input: &mut StepParser,
173 at_rule_str: String,
174 f: impl FnOnce(&mut Self, &mut StepParser) -> R,
175 ) -> R {
176 self.cur_at_rule_stacks.push(at_rule_str);
177 let r = f(self, input);
178 self.cur_at_rule_stacks.pop();
179 r
180 }
181}
182
183fn write_maybe_class_name(
184 input: &mut StepParser,
185 ss: &mut StyleSheetTransformer,
186 next: &StepToken,
187 src: &CowRcStr,
188 in_class: bool,
189) {
190 if in_class {
191 if let Some(content) = ss.options.class_prefix_sign.clone() {
192 let st = StepToken::wrap(Token::Comment(&content), next.position);
193 ss.append_token(st, input, None);
194 }
195 }
196 if in_class && ss.options.class_prefix.is_some() {
197 let s = format!("{}--{}", ss.options.class_prefix.as_ref().unwrap(), src);
198 let st = StepToken::wrap(Token::Ident(s.as_str().into()), next.position);
199 ss.append_token_space_preserved(st, input, Some(Token::Ident(src.clone())));
200 } else {
201 ss.append_token_space_preserved(next.clone(), input, None);
202 }
203}
204
205fn write_maybe_rpx_dimension(
206 input: &mut StepParser,
207 ss: &mut StyleSheetTransformer,
208 next: &StepToken,
209 has_sign: bool,
210 value: f32,
211 int_value: Option<i32>,
212 unit: &CowRcStr,
213) {
214 let unit_str: &str = &unit;
215 if unit_str == "rpx" {
216 let new_value = value * 100. / ss.options.rpx_ratio;
217 let new_int_value = if (new_value.round() - new_value).abs() <= f32::EPSILON {
218 Some(new_value.round() as i32)
219 } else {
220 None
221 };
222 let t = Token::Dimension {
223 has_sign,
224 value: new_value,
225 int_value: new_int_value,
226 unit: "vw".into(),
227 };
228 let st = StepToken::wrap(t, next.position);
229 ss.append_token(
230 st,
231 input,
232 Some(Token::Dimension {
233 has_sign,
234 value,
235 unit: unit.clone(),
236 int_value,
237 }),
238 );
239 } else {
240 let token = Token::Dimension {
241 has_sign,
242 value,
243 unit: unit.clone(),
244 int_value,
245 };
246 let st = StepToken::wrap(token, next.position);
247 ss.append_token(st, input, None);
248 }
249}
250
251fn parse_rules(input: &mut StepParser, ss: &mut StyleSheetTransformer) {
252 let mut at_file_start = true;
253 while !input.is_exhausted() {
254 if !parse_at_rule(input, ss, at_file_start) {
255 parse_qualified_rule(input, ss);
256 }
257 at_file_start = false;
258 }
259}
260
261fn parse_at_rule(
262 input: &mut StepParser,
263 ss: &mut StyleSheetTransformer,
264 at_file_start: bool,
265) -> bool {
266 let Ok(peek) = input.peek() else { return false };
267 if let Token::AtKeyword(x) = &*peek {
268 input.next().ok();
269 let at_keyword: &str = &x;
270 if at_keyword == "import" && ss.options.import_sign.is_some() {
271 let import_sign = ss.options.import_sign.clone().unwrap();
273 let start_pos = input.position();
274 if !at_file_start {
275 ss.add_warning(
276 error::ParseErrorKind::IllegalImportPosition,
277 start_pos..start_pos,
278 );
279 }
280 let r = input.try_parse::<_, _, ParseError<()>>(|input| {
281 let rel_path = input.expect_string_cloned()?;
282 let mut close_stack = vec![];
283 let mut has_media = false;
284 while let Ok(peek) = input.peek() {
285 match &*peek {
286 Token::Function(x) => {
287 let xs: &str = &x;
288 if !matches!(xs, "layer" | "supports") {
289 ss.add_warning(
290 error::ParseErrorKind::UnexpectedCharacter,
291 peek.position..peek.position,
292 );
293 break;
294 }
295 input.next().ok();
296 let st = StepToken::wrap(Token::AtKeyword(x.clone()), peek.position);
297 ss.append_token(st, input, Some(peek.token.clone()));
298 match xs {
299 "layer" => {
300 convert_class_names_and_rpx_in_block(input, ss);
301 }
302 "supports" => {
303 let st =
304 StepToken::wrap(Token::ParenthesisBlock, peek.position);
305 let close = ss.append_nested_block(st, input);
306 convert_class_names_and_rpx_in_block(input, ss);
307 ss.append_nested_block_close(close, input);
308 }
309 _ => unreachable!(),
310 }
311 let st = StepToken::wrap(Token::CurlyBracketBlock, peek.position);
312 let close = ss.append_nested_block(st, input);
313 close_stack.push(close);
314 }
315 Token::Ident(_) | Token::ParenthesisBlock => {
316 has_media = true;
317 break;
318 }
319 Token::Semicolon => {
320 input.next().ok();
321 break;
322 }
323 _ => {
324 ss.add_warning(
325 error::ParseErrorKind::UnexpectedCharacter,
326 peek.position..peek.position,
327 );
328 return Err(input.new_error_for_next_token());
329 }
330 }
331 }
332 let pos = input.position();
333 if has_media {
334 let st = StepToken::wrap(Token::AtKeyword("media".into()), start_pos);
335 ss.append_token(st, input, None);
336 while let Ok(next) = input.next() {
337 match &*next {
338 Token::CurlyBracketBlock => {
339 ss.add_warning(
340 error::ParseErrorKind::UnexpectedCharacter,
341 pos..pos,
342 );
343 return Err(input.new_error_for_next_token());
344 }
345 Token::SquareBracketBlock
346 | Token::ParenthesisBlock
347 | Token::Function(_) => {
348 let close = ss.append_nested_block(next, input);
349 convert_class_names_and_rpx_in_block(input, ss);
350 ss.append_nested_block_close(close, input);
351 }
352 Token::Semicolon => {
353 break;
354 }
355 _ => {
356 ss.append_token(next, input, None);
357 }
358 }
359 }
360 let st = StepToken::wrap(Token::CurlyBracketBlock, start_pos);
361 let close = ss.append_nested_block(st, input);
362 close_stack.push(close);
363 }
364 let comment = format!("{} {}", import_sign, urlencoding::encode(&rel_path));
365 let st = StepToken::wrap(Token::Comment(comment.as_str()), start_pos);
366 ss.append_token(st, input, None);
367 while let Some(close) = close_stack.pop() {
368 ss.append_nested_block_close(close, input);
369 }
370 Ok(())
371 });
372 if r.is_err() {
373 while let Ok(x) = input.next() {
374 match &*x {
375 Token::CurlyBracketBlock | Token::Semicolon => break,
376 _ => {}
377 }
378 }
379 }
380 } else {
381 let st = StepToken::wrap(Token::AtKeyword(x.clone()), peek.position);
383 let output_index = ss.cur_output_utf8_len();
384 ss.append_token(st, input, None);
385 let x: &str = &x;
386 let contain_rule_list = matches!(x, "media" | "supports" | "document");
387 loop {
388 let r = input.try_parse::<_, _, ParseError<()>>(|input| {
389 let next = input.next()?;
390 match next.token.clone() {
391 Token::CurlyBracketBlock => {
392 let at_rule_str = ss
393 .get_output_segment(output_index..ss.cur_output_utf8_len())
394 .to_string();
395 ss.wrap_at_rule_output(input, at_rule_str, |ss, input| {
396 let close = ss.append_nested_block(next, input);
397 if contain_rule_list {
398 input
399 .parse_nested_block::<_, (), ()>(|nested_input| {
400 let input = &mut StepParser::wrap(nested_input);
401 parse_rules(input, ss);
402 Ok(())
403 })
404 .ok();
405 } else {
406 convert_rpx_in_block(input, ss, None);
407 }
408 ss.append_nested_block_close(close, input);
409 });
410 return Ok(false);
411 }
412 Token::SquareBracketBlock
413 | Token::ParenthesisBlock
414 | Token::Function(_) => {
415 let close = ss.append_nested_block(next, input);
416 convert_class_names_and_rpx_in_block(input, ss);
417 ss.append_nested_block_close(close, input);
418 }
419 Token::Semicolon => {
420 ss.append_token(next, input, None);
421 return Ok(false);
422 }
423 _ => {
424 ss.append_token(next, input, None);
425 }
426 }
427 Ok(true)
428 });
429 match r {
430 Ok(cont) => {
431 if !cont {
432 break;
433 }
434 }
435 Err(_) => break,
436 }
437 }
438 }
439 true
440 } else {
441 false
442 }
443}
444
445fn parse_qualified_rule(input: &mut StepParser, ss: &mut StyleSheetTransformer) {
446 let mut in_class = false;
447 let mut has_whitespace = false;
448 input.skip_whitespace();
449 if ss.options.convert_host {
450 let r = input.try_parse::<_, _, ParseError<()>>(|input| {
451 input.expect_colon()?;
452 let Ok(next) = input.next() else {
453 return Ok(());
454 };
455 let mut invalid = match &*next {
456 Token::Ident(x) if x.as_bytes() == b"host" => None,
457 Token::Function(x) if x.as_bytes() == b"host" => Some(input.position()),
458 _ => return Err(input.new_custom_error(())),
459 };
460 let next = loop {
461 let Ok(next) = input.next() else {
462 return Ok(());
463 };
464 if *next != Token::CurlyBracketBlock {
465 if invalid.is_none() {
466 invalid = Some(input.position());
467 }
468 } else {
469 break next;
470 }
471 };
472 if let Some(pos) = invalid {
473 ss.add_warning(error::ParseErrorKind::HostSelectorCombination, pos..pos);
474 } else {
475 ss.write_in_low_priority(input, |ss, input| {
476 let write_attr_selector =
477 |ss: &mut StyleSheetTransformer,
478 input: &mut StepParser,
479 name: &str,
480 value: &str| {
481 ss.append_token(
482 StepToken::wrap_at(Token::SquareBracketBlock, &next),
483 input,
484 None,
485 );
486 ss.append_token(
487 StepToken::wrap_at(Token::Ident(name.into()), &next),
488 input,
489 None,
490 );
491 ss.append_token(
492 StepToken::wrap_at(Token::Delim('='), &next),
493 input,
494 None,
495 );
496 let quoted_value = Token::QuotedString(value.into());
497 ss.append_token(StepToken::wrap_at(quoted_value, &next), input, None);
498 ss.append_token(
499 StepToken::wrap_at(Token::CloseSquareBracket, &next),
500 input,
501 None,
502 );
503 };
504 let p = ss.options.class_prefix.clone().unwrap_or_default();
505 write_attr_selector(ss, input, "wx-host", &p);
506 if let Some(host_is) = ss.options.host_is.clone() {
507 ss.append_token(StepToken::wrap_at(Token::Comma, &next), input, None);
508 write_attr_selector(ss, input, "is", &host_is);
509 }
510 let close = ss.append_nested_block(next, input);
511 convert_rpx_in_block(input, ss, None);
512 ss.append_nested_block_close(close, input);
513 });
514 }
515 Ok(())
516 });
517 if r.is_ok() {
518 return;
519 }
520 }
521 loop {
522 let r = input.try_parse::<_, _, ParseError<()>>(|input| {
523 let next = input.next_including_whitespace()?;
524 match &*next {
525 Token::CurlyBracketBlock | Token::WhiteSpace(_) => {}
526 _ => {
527 if has_whitespace {
528 let st = StepToken::wrap(Token::WhiteSpace(" "), next.position);
529 ss.append_token_space_preserved(st, input, None);
530 }
531 }
532 }
533 has_whitespace = false;
534 match &*next {
535 Token::CurlyBracketBlock => {
536 let close = ss.append_nested_block(next, input);
537 convert_rpx_in_block(input, ss, None);
538 ss.append_nested_block_close(close, input);
539 return Ok(false);
540 }
541 Token::SquareBracketBlock | Token::ParenthesisBlock | Token::Function(_) => {
542 let close = ss.append_nested_block(next, input);
543 convert_class_names_and_rpx_in_block(input, ss);
544 ss.append_nested_block_close(close, input);
545 in_class = false;
546 }
547 Token::Delim('.') => {
548 ss.append_token_space_preserved(next, input, None);
549 in_class = true;
550 }
551 Token::Ident(src) => {
552 write_maybe_class_name(input, ss, &next, src, in_class);
553 in_class = false;
554 }
555 Token::WhiteSpace(_) => {
556 has_whitespace = true;
557 in_class = false;
558 }
559 _ => {
560 ss.append_token_space_preserved(next.clone(), input, None);
561 in_class = false;
562 }
563 }
564 Ok(true)
565 });
566 match r {
567 Ok(cont) => {
568 if !cont {
569 break;
570 }
571 }
572 Err(_) => break,
573 }
574 }
575}
576
577fn convert_class_names_and_rpx_in_block(input: &mut StepParser, ss: &mut StyleSheetTransformer) {
578 input
579 .parse_nested_block::<_, (), ()>(|nested_input| {
580 let input = &mut StepParser::wrap(nested_input);
581 let mut in_class = false;
582 let mut has_whitespace = false;
583 input.skip_whitespace();
584 loop {
585 let next = input.next_including_whitespace()?;
586 match &*next {
587 Token::CurlyBracketBlock | Token::WhiteSpace(_) => {}
588 _ => {
589 if has_whitespace {
590 let st = StepToken::wrap(Token::WhiteSpace(" "), next.position);
591 ss.append_token_space_preserved(st, input, None);
592 }
593 }
594 }
595 has_whitespace = false;
596 match &*next {
597 Token::CurlyBracketBlock
598 | Token::SquareBracketBlock
599 | Token::ParenthesisBlock => {
600 let close = ss.append_nested_block(next, input);
601 convert_class_names_and_rpx_in_block(input, ss);
602 ss.append_nested_block_close(close, input);
603 in_class = false;
604 }
605 Token::Function(func) => {
606 let func: &str = func;
607 let config = if func == "calc" {
608 Some(ConvertOptions { in_calc: true })
609 } else {
610 None
611 };
612 let close = ss.append_nested_block(next.clone(), input);
613 convert_rpx_in_block(input, ss, config);
614 ss.append_nested_block_close(close, input);
615 in_class = false;
616 }
617 Token::Delim('.') => {
618 ss.append_token(next, input, None);
619 in_class = true;
620 }
621 Token::Ident(src) => {
622 write_maybe_class_name(input, ss, &next, src, in_class);
623 in_class = false;
624 }
625 Token::Dimension {
626 has_sign,
627 value,
628 unit,
629 int_value,
630 } => {
631 write_maybe_rpx_dimension(
632 input, ss, &next, *has_sign, *value, *int_value, unit,
633 );
634 in_class = false;
635 }
636 Token::WhiteSpace(_) => {
637 has_whitespace = true;
638 in_class = false;
639 }
640 _ => {
641 ss.append_token(next, input, None);
642 in_class = false;
643 }
644 }
645 }
646 })
647 .ok();
648}
649
650struct ConvertOptions {
651 in_calc: bool,
652}
653
654fn convert_rpx_in_block(
655 input: &mut StepParser,
656 ss: &mut StyleSheetTransformer,
657 convert_options: Option<ConvertOptions>,
658) {
659 let mut skip_whitespace = true;
660 let mut in_calc = false;
661 if let Some(options) = convert_options {
662 if options.in_calc {
663 skip_whitespace = false;
664 in_calc = true;
665 }
666 }
667 input
668 .parse_nested_block::<_, (), ()>(|nested_input| {
669 let input = &mut StepParser::wrap(nested_input);
670 let mut prev_token: Option<StepToken> = None;
671 loop {
672 let next = if skip_whitespace {
673 input.next()?
674 } else {
675 input.next_including_whitespace()?
676 };
677 match &*next {
678 Token::CurlyBracketBlock
679 | Token::SquareBracketBlock
680 | Token::ParenthesisBlock => {
681 let close = ss.append_nested_block(next.clone(), input);
682 convert_rpx_in_block(input, ss, None);
683 ss.append_nested_block_close(close, input);
684 }
685 Token::Function(func) => {
686 let func: &str = func;
687 let config = if func == "calc" {
688 Some(ConvertOptions { in_calc: true })
689 } else {
690 None
691 };
692 let close = ss.append_nested_block(next.clone(), input);
693 convert_rpx_in_block(input, ss, config);
694 ss.append_nested_block_close(close, input);
695 }
696 Token::Dimension {
697 has_sign,
698 value,
699 unit,
700 int_value,
701 } => {
702 write_maybe_rpx_dimension(
703 input, ss, &next, *has_sign, *value, *int_value, unit,
704 );
705 }
706 Token::WhiteSpace(_) => {
707 let mut skip = true;
708 if in_calc {
709 let _ = input.try_parse::<_, (), ()>(|input| {
712 let next_token =
713 input.next_including_whitespace().map_err(|_| ())?;
714 match &*next_token {
715 Token::Delim(c) if *c == '+' || *c == '-' => {
716 skip = false;
717 }
718 _ => {}
719 }
720 Err(())
721 });
722 if let Some(prev_token) = prev_token {
724 match &*prev_token {
725 Token::Delim(c) if *c == '+' || *c == '-' => {
726 skip = false;
727 }
728 _ => {}
729 }
730 }
731 }
732 if !skip {
733 let st = StepToken::wrap(Token::WhiteSpace(" "), next.position);
734 ss.append_token(st, input, None);
735 }
736 }
737 _ => {
738 ss.append_token(next.clone(), input, None);
739 }
740 }
741 prev_token = Some(next);
742 }
743 })
744 .ok();
745}
746
747#[cfg(test)]
748mod test {
749 use sourcemap::SourceMap;
750
751 use super::*;
752
753 #[test]
754 fn remove_comments() {
755 let trans = StyleSheetTransformer::from_css(
756 "",
757 r#" /* test */ .a { } "#,
758 StyleSheetOptions {
759 ..Default::default()
760 },
761 );
762 let mut s = Vec::new();
763 let output = trans.output();
764 output.write(&mut s).unwrap();
765 let mut sm = Vec::new();
766 output.write_source_map(&mut sm).unwrap();
767 assert_eq!(std::str::from_utf8(&s).unwrap(), ".a{}");
768 }
769
770 #[test]
771 fn add_class_prefix() {
772 let trans = StyleSheetTransformer::from_css(
773 "",
774 r#"
775 #a.b [g] .c.d g:e(.f) {
776 key: .v f(.c) d.e;
777 }
778 "#,
779 StyleSheetOptions {
780 class_prefix: Some("p".into()),
781 ..Default::default()
782 },
783 );
784 let mut s = Vec::new();
785 let output = trans.output();
786 output.write(&mut s).unwrap();
787 let mut sm = Vec::new();
788 output.write_source_map(&mut sm).unwrap();
789 assert_eq!(
790 std::str::from_utf8(&s).unwrap(),
791 "#a.p--b [g] .p--c.p--d g:e(.p--f){key:.v f(.c)d.e;}"
792 );
793 }
794
795 #[test]
796 fn allow_class_prefix_sign() {
797 let trans = StyleSheetTransformer::from_css(
798 "",
799 r#"
800 .a g:e(.f) {}
801 "#,
802 StyleSheetOptions {
803 class_prefix: None,
804 class_prefix_sign: Some("TEST".into()),
805 ..Default::default()
806 },
807 );
808 let mut s = Vec::new();
809 let output = trans.output();
810 output.write(&mut s).unwrap();
811 let mut sm = Vec::new();
812 output.write_source_map(&mut sm).unwrap();
813 assert_eq!(
814 std::str::from_utf8(&s).unwrap(),
815 "./*TEST*/a g:e(./*TEST*/f){}"
816 );
817 }
818
819 #[test]
820 fn allow_class_prefix_sign_before_prefix() {
821 let trans = StyleSheetTransformer::from_css(
822 "",
823 r#"
824 .a {}
825 "#,
826 StyleSheetOptions {
827 class_prefix: Some("p".into()),
828 class_prefix_sign: Some("TEST".into()),
829 ..Default::default()
830 },
831 );
832 let mut s = Vec::new();
833 let output = trans.output();
834 output.write(&mut s).unwrap();
835 let mut sm = Vec::new();
836 output.write_source_map(&mut sm).unwrap();
837 assert_eq!(std::str::from_utf8(&s).unwrap(), "./*TEST*/p--a{}");
838 }
839
840 #[test]
841 fn transform_rpx() {
842 let trans = StyleSheetTransformer::from_css(
843 "",
844 r#"
845 #a.b[1rpx] (7.5rpx){
846 key: 7.5rpx f(7.5rpx);
847 }
848 "#,
849 StyleSheetOptions {
850 class_prefix: Some("p".into()),
851 ..Default::default()
852 },
853 );
854 let mut s = Vec::new();
855 let output = trans.output();
856 output.write(&mut s).unwrap();
857 assert_eq!(
858 std::str::from_utf8(&s).unwrap(),
859 "#a.p--b[0.133333vw] (1vw){key:1vw f(1vw);}"
860 );
861 }
862
863 #[test]
864 fn transform_rpx_in_simple_at_rules() {
865 let trans = StyleSheetTransformer::from_css(
866 "",
867 r#"
868 @a 75rpx;
869 .b {}
870 @c (75rpx .d) { 75rpx .e }
871 .f {}
872 "#,
873 StyleSheetOptions {
874 class_prefix: Some("p".into()),
875 ..Default::default()
876 },
877 );
878 let mut s = Vec::new();
879 let output = trans.output();
880 output.write(&mut s).unwrap();
881 assert_eq!(
882 std::str::from_utf8(&s).unwrap(),
883 "@a 75rpx;.p--b{}@c(10vw .p--d){10vw.e}.p--f{}"
884 );
885 }
886
887 #[test]
888 fn rules_in_at_media() {
889 let trans = StyleSheetTransformer::from_css(
890 "",
891 r#"
892 @media (width: 1rpx) {
893 .b [2rpx] {
894 key: 3rpx .a;
895 }
896 }
897 "#,
898 StyleSheetOptions {
899 class_prefix: Some("p".into()),
900 rpx_ratio: 10.,
901 ..Default::default()
902 },
903 );
904 let mut s = Vec::new();
905 let output = trans.output();
906 output.write(&mut s).unwrap();
907 assert_eq!(
908 std::str::from_utf8(&s).unwrap(),
909 "@media(width: 10vw){.p--b [20vw]{key:30vw.a;}}"
910 );
911 }
912
913 #[test]
914 fn host_select() {
915 let trans = StyleSheetTransformer::from_css(
916 "",
917 r#"
918 :host {
919 color: red;
920 }
921 "#,
922 StyleSheetOptions {
923 class_prefix: Some("ABC".into()),
924 convert_host: true,
925 ..Default::default()
926 },
927 );
928 let (output, lp) = trans.output_and_low_priority_output();
929 let mut s = Vec::new();
930 output.write(&mut s).unwrap();
931 assert!(std::str::from_utf8(&s).unwrap().is_empty());
932 let mut s = Vec::new();
933 lp.write(&mut s).unwrap();
934 assert_eq!(
935 std::str::from_utf8(&s).unwrap(),
936 r#"[wx-host="ABC"]{color:red;}"#
937 );
938 }
939
940 #[test]
941 fn illegal_host_combination() {
942 let trans = StyleSheetTransformer::from_css(
943 "",
944 r#"
945 :host(.a) {
946 color: red;
947 }
948 :host .a {
949 color: red;
950 }
951 .a { color: green }
952 "#,
953 StyleSheetOptions {
954 convert_host: true,
955 ..Default::default()
956 },
957 );
958 assert_eq!(
959 trans.warnings().map(|x| x.kind.clone()).collect::<Vec<_>>(),
960 [
961 error::ParseErrorKind::HostSelectorCombination,
962 error::ParseErrorKind::HostSelectorCombination
963 ],
964 );
965 let (output, lp) = trans.output_and_low_priority_output();
966 let mut s = Vec::new();
967 output.write(&mut s).unwrap();
968 assert_eq!(std::str::from_utf8(&s).unwrap(), r#".a{color:green}"#);
969 let mut s = Vec::new();
970 lp.write(&mut s).unwrap();
971 assert_eq!(std::str::from_utf8(&s).unwrap(), r#""#);
972 }
973
974 #[test]
975 fn host_select_inside_at_rules() {
976 let trans = StyleSheetTransformer::from_css(
977 "",
978 r#"
979 @media (width: 1px) {
980 @supports (color: red) {
981 .a {
982 color: red;
983 }
984 :host {
985 color: pink;
986 }
987 .b {
988 color: green;
989 }
990 }
991 }
992 "#,
993 StyleSheetOptions {
994 class_prefix: None,
995 convert_host: true,
996 host_is: Some("IS".into()),
997 ..Default::default()
998 },
999 );
1000 let (output, lp) = trans.output_and_low_priority_output();
1001 let mut s = Vec::new();
1002 output.write(&mut s).unwrap();
1003 assert_eq!(
1004 std::str::from_utf8(&s).unwrap(),
1005 r#"@media(width: 1px){@supports(color: red){.a{color:red;}.b{color:green;}}}"#
1006 );
1007 let mut s = Vec::new();
1008 lp.write(&mut s).unwrap();
1009 assert_eq!(
1010 std::str::from_utf8(&s).unwrap(),
1011 r#"@media(width: 1px){@supports(color: red){[wx-host=""],[is="IS"]{color:pink;}}}"#
1012 );
1013 }
1014
1015 #[test]
1016 fn import_sign_urlencoded() {
1017 let trans = StyleSheetTransformer::from_css(
1018 "",
1019 r#"
1020 @import './a\\b*?'
1021 "#,
1022 StyleSheetOptions {
1023 import_sign: Some("TEST".into()),
1024 ..Default::default()
1025 },
1026 );
1027 let mut s = Vec::new();
1028 let output = trans.output();
1029 output.write(&mut s).unwrap();
1030 assert_eq!(
1031 std::str::from_utf8(&s).unwrap(),
1032 r#"/*TEST .%2Fa%5Cb%2A%3F*/"#
1033 );
1034 }
1035
1036 #[test]
1037 fn import_sign_with_media_queries() {
1038 let trans = StyleSheetTransformer::from_css(
1039 "",
1040 r#"
1041 @import './a' (min-width: 10px);
1042 .a { }
1043 "#,
1044 StyleSheetOptions {
1045 import_sign: Some("TEST".into()),
1046 ..Default::default()
1047 },
1048 );
1049 let mut s = Vec::new();
1050 let output = trans.output();
1051 output.write(&mut s).unwrap();
1052 assert_eq!(
1053 std::str::from_utf8(&s).unwrap(),
1054 r#"@media(min-width: 10px){/*TEST .%2Fa*/}.a{}"#
1055 );
1056 }
1057
1058 #[test]
1059 fn import_sign_with_supports() {
1060 let trans = StyleSheetTransformer::from_css(
1061 "",
1062 r#"
1063 @import './a' layer(a) supports(color: red) print and (min-width: 10px);
1064 "#,
1065 StyleSheetOptions {
1066 import_sign: Some("TEST".into()),
1067 ..Default::default()
1068 },
1069 );
1070 let mut s = Vec::new();
1071 let output = trans.output();
1072 output.write(&mut s).unwrap();
1073 assert_eq!(
1074 std::str::from_utf8(&s).unwrap(),
1075 r#"@layer a{@supports(color: red){@media print and (min-width: 10px){/*TEST .%2Fa*/}}}"#
1076 );
1077 }
1078
1079 #[test]
1080 fn import_sign_not_at_top() {
1081 let trans = StyleSheetTransformer::from_css(
1082 "",
1083 r#"
1084 .a {}
1085 @import './a';
1086 "#,
1087 StyleSheetOptions {
1088 import_sign: Some("TEST".into()),
1089 ..Default::default()
1090 },
1091 );
1092 let mut s = Vec::new();
1093 assert_eq!(
1094 trans.warnings().map(|x| x.kind.clone()).collect::<Vec<_>>(),
1095 [error::ParseErrorKind::IllegalImportPosition],
1096 );
1097 let output = trans.output();
1098 output.write(&mut s).unwrap();
1099 assert_eq!(std::str::from_utf8(&s).unwrap(), r#".a{}/*TEST .%2Fa*/"#);
1100 }
1101
1102 #[test]
1103 fn minify_calc() {
1104 let trans = StyleSheetTransformer::from_css(
1105 "",
1106 r#"
1107 .a {
1108 margin: 10px 20rpx 30px calc(10px + 20px / 2);
1109 padding: calc(10rpx * 2 + 30px);
1110 }
1111 "#,
1112 StyleSheetOptions {
1113 class_prefix: Some("p".into()),
1114 rpx_ratio: 10.,
1115 ..Default::default()
1116 },
1117 );
1118 let mut s = Vec::new();
1119 let output = trans.output();
1120 output.write(&mut s).unwrap();
1121 assert_eq!(
1122 std::str::from_utf8(&s).unwrap(),
1123 ".p--a{margin:10px 200vw 30px calc(10px + 20px/2);padding:calc(100vw*2 + 30px);}"
1124 );
1125 }
1126
1127 #[test]
1128 fn minify_calc_source_map() {
1129 let trans = StyleSheetTransformer::from_css(
1130 "",
1131 r#" .a { padding: calc(10rpx * 2 + 30px); } "#,
1132 StyleSheetOptions {
1133 class_prefix: Some("p".into()),
1134 rpx_ratio: 10.,
1135 ..Default::default()
1136 },
1137 );
1138 let mut s = Vec::new();
1139 let output = trans.output();
1140 output.write(&mut s).unwrap();
1141 assert_eq!(
1142 std::str::from_utf8(&s).unwrap(),
1143 ".p--a{padding:calc(100vw*2 + 30px);}"
1144 );
1145 let mut sm = Vec::new();
1146 output.write_source_map(&mut sm).unwrap();
1147 let sm: &[u8] = &sm;
1148 let source_map = SourceMap::from_reader(sm).unwrap();
1149 {
1150 let token = source_map.lookup_token(0, 0).unwrap();
1151 assert_eq!(token.get_name(), None);
1152 assert_eq!(token.get_src_line(), 0);
1153 assert_eq!(token.get_src_col(), 1);
1154 }
1155 {
1156 let token = source_map.lookup_token(0, 1).unwrap();
1157 assert_eq!(token.get_src_line(), 0);
1158 assert_eq!(token.get_src_col(), 2);
1159 }
1160 {
1161 let token = source_map.lookup_token(0, 5).unwrap();
1162 assert_eq!(token.get_src_line(), 0);
1163 assert_eq!(token.get_src_col(), 4);
1164 }
1165 {
1166 let token = source_map.lookup_token(0, 6).unwrap();
1167 assert_eq!(token.get_src_line(), 0);
1168 assert_eq!(token.get_src_col(), 6);
1169 }
1170 {
1171 let token = source_map.lookup_token(0, 14).unwrap();
1172 assert_eq!(token.get_src_line(), 0);
1173 assert_eq!(token.get_src_col(), 15);
1174 }
1175 {
1176 let token = source_map.lookup_token(0, 19).unwrap();
1177 assert_eq!(token.get_src_line(), 0);
1178 assert_eq!(token.get_src_col(), 20);
1179 }
1180 {
1181 let token = source_map.lookup_token(0, 24).unwrap();
1182 assert_eq!(token.get_src_line(), 0);
1183 assert_eq!(token.get_src_col(), 27);
1184 }
1185 {
1186 let token = source_map.lookup_token(0, 27).unwrap();
1187 assert_eq!(token.get_src_line(), 0);
1188 assert_eq!(token.get_src_col(), 33);
1189 }
1190 {
1191 let token = source_map.lookup_token(0, 29).unwrap();
1192 assert_eq!(token.get_src_line(), 0);
1193 assert_eq!(token.get_src_col(), 36);
1194 }
1195 }
1196
1197 #[test]
1198 fn source_map() {
1199 let trans = StyleSheetTransformer::from_css(
1200 "",
1201 r#" .a { width: 1rpx } "#,
1202 StyleSheetOptions {
1203 class_prefix: Some("p".into()),
1204 rpx_ratio: 10.,
1205 ..Default::default()
1206 },
1207 );
1208 let mut s = Vec::new();
1209 let output = trans.output();
1210 output.write(&mut s).unwrap();
1211 assert_eq!(std::str::from_utf8(&s).unwrap(), ".p--a{width:10vw}");
1212 let mut sm = Vec::new();
1213 output.write_source_map(&mut sm).unwrap();
1214 let sm: &[u8] = &sm;
1215 let source_map = SourceMap::from_reader(sm).unwrap();
1216 {
1217 let token = source_map.lookup_token(0, 0).unwrap();
1218 assert_eq!(token.get_name(), None);
1219 assert_eq!(token.get_src_line(), 0);
1220 assert_eq!(token.get_src_col(), 1);
1221 }
1222 {
1223 let token = source_map.lookup_token(0, 1).unwrap();
1224 assert_eq!(token.get_src_line(), 0);
1225 assert_eq!(token.get_src_col(), 2);
1226 }
1227 {
1228 let token = source_map.lookup_token(0, 5).unwrap();
1229 assert_eq!(token.get_src_line(), 0);
1230 assert_eq!(token.get_src_col(), 4);
1231 }
1232 {
1233 let token = source_map.lookup_token(0, 6).unwrap();
1234 assert_eq!(token.get_src_line(), 0);
1235 assert_eq!(token.get_src_col(), 6);
1236 }
1237 {
1238 let token = source_map.lookup_token(0, 12).unwrap();
1239 assert_eq!(token.get_src_line(), 0);
1240 assert_eq!(token.get_src_col(), 13);
1241 }
1242 }
1243}