1use super::*;
2
3pub(crate) fn format_source(source: &str) -> FmtResult {
23 let formatted: String = format_euv_macros(source);
24 let changed: bool = formatted != source;
25 FmtResult::new(changed, formatted)
26}
27
28pub fn format_euv_macros<S>(source: S) -> String
42where
43 S: AsRef<str>,
44{
45 let source: &str = source.as_ref();
46 let mut result: String = String::new();
47 let chars: Vec<char> = source.chars().collect();
48 let len: usize = chars.len();
49 let mut position: usize = 0;
50 while position < len {
51 if is_euv_macro_start(&chars, position, len) {
52 let macro_name_end: usize = find_macro_name_end(&chars, position, len);
53 let name: String = chars[position..macro_name_end].iter().collect::<String>();
54 result.push_str(&name);
55 position = macro_name_end;
56 position = skip_whitespace_and_comments(&chars, position, len, &mut result);
57 if position < len && chars[position] == CHAR_MACRO_BANG {
58 result.push(CHAR_MACRO_BANG);
59 position += 1;
60 position = skip_whitespace_and_comments(&chars, position, len, &mut result);
61 if position < len && chars[position] == CHAR_BRACE_LEFT {
62 if !result.ends_with(CHAR_SPACE) {
63 result.push(CHAR_SPACE);
64 }
65 let (body_content, end_pos) = extract_brace_content(&chars, position);
66 let formatted_body: String = format_macro_body(&body_content);
67 if formatted_body.trim().is_empty() {
68 result.push(CHAR_BRACE_LEFT);
69 result.push(CHAR_BRACE_RIGHT);
70 } else {
71 let trimmed_body: &str = formatted_body.trim();
72 let last_newline_pos: usize = result
73 .rfind(CHAR_NEWLINE)
74 .map(|position: usize| position + 1)
75 .unwrap_or_default();
76 let macro_indent: usize = result[last_newline_pos..]
77 .chars()
78 .take_while(|character: &char| *character == CHAR_SPACE)
79 .count();
80 let min_indent: usize = body_content
81 .lines()
82 .filter(|line: &&str| !line.trim().is_empty())
83 .map(|line: &str| {
84 line.chars()
85 .take_while(|character: &char| character.is_whitespace())
86 .count()
87 })
88 .min()
89 .unwrap_or_default();
90 let base_indent: usize = if min_indent > 0 {
91 min_indent
92 } else {
93 macro_indent + 4
94 };
95 let indent_str: String = " ".repeat(base_indent);
96 let indented_body: String =
97 indented_body_skipping_block_comments(trimmed_body, &indent_str);
98 let outer_indent_str: String = " ".repeat(macro_indent);
99 result.push(CHAR_BRACE_LEFT);
100 result.push(CHAR_NEWLINE);
101 result.push_str(&indented_body);
102 result.push(CHAR_NEWLINE);
103 result.push_str(&outer_indent_str);
104 result.push(CHAR_BRACE_RIGHT);
105 }
106 position = end_pos;
107 continue;
108 }
109 }
110 continue;
111 }
112 if chars[position] == CHAR_DOUBLE_QUOTE || chars[position] == CHAR_SINGLE_QUOTE {
113 let (literal, end_pos) = extract_string_literal(&chars, position, len);
114 result.push_str(&literal);
115 position = end_pos;
116 continue;
117 }
118 if position + 1 < len
119 && chars[position] == CHAR_SLASH_FORWARD
120 && chars[position + 1] == CHAR_SLASH_FORWARD
121 {
122 let (comment, end_pos) = extract_line_comment(&chars, position, len);
123 result.push_str(&comment);
124 position = end_pos;
125 continue;
126 }
127 if position + 1 < len
128 && chars[position] == CHAR_SLASH_FORWARD
129 && chars[position + 1] == CHAR_ASTERISK
130 {
131 let (comment, end_pos) = extract_block_comment(&chars, position, len);
132 result.push_str(&comment);
133 position = end_pos;
134 continue;
135 }
136 result.push(chars[position]);
137 position += 1;
138 }
139 result
140}
141
142fn is_euv_macro_start(chars: &[char], pos: usize, len: usize) -> bool {
153 for name in EUV_MACRO_NAMES {
154 let name_len: usize = name.len();
155 if pos + name_len > len {
156 continue;
157 }
158 let candidate: String = chars[pos..pos + name_len].iter().collect::<String>();
159 if candidate != *name {
160 continue;
161 }
162 if pos > 0 && is_ident_char(chars[pos - 1]) {
163 continue;
164 }
165 if pos + name_len < len && is_ident_char(chars[pos + name_len]) {
166 continue;
167 }
168 return true;
169 }
170 false
171}
172
173fn find_macro_name_end(chars: &[char], pos: usize, _len: usize) -> usize {
184 let mut end: usize = pos;
185 while end < chars.len() && is_ident_char(chars[end]) {
186 end += 1;
187 }
188 end
189}
190
191fn is_raw_prefix(chars: &[char], pos: usize) -> bool {
203 if pos < 2 {
204 return false;
205 }
206 chars[pos - 2] == CHAR_LETTER_R && chars[pos - 1] == CHAR_HASH
207}
208
209fn is_ident_char(character: char) -> bool {
219 character.is_alphanumeric() || character == CHAR_UNDERSCORE
220}
221
222fn skip_whitespace_and_comments(
234 chars: &[char],
235 mut pos: usize,
236 len: usize,
237 result: &mut String,
238) -> usize {
239 while pos < len {
240 if chars[pos].is_whitespace() {
241 result.push(chars[pos]);
242 pos += 1;
243 } else if pos + 1 < len
244 && chars[pos] == CHAR_SLASH_FORWARD
245 && chars[pos + 1] == CHAR_SLASH_FORWARD
246 {
247 let (comment, end_pos) = extract_line_comment(chars, pos, len);
248 result.push_str(&comment);
249 pos = end_pos;
250 } else if pos + 1 < len
251 && chars[pos] == CHAR_SLASH_FORWARD
252 && chars[pos + 1] == CHAR_ASTERISK
253 {
254 let (comment, end_pos) = extract_block_comment(chars, pos, len);
255 result.push_str(&comment);
256 pos = end_pos;
257 } else {
258 break;
259 }
260 }
261 pos
262}
263
264fn extract_brace_block(chars: &[char], start: usize) -> (String, usize) {
278 let mut depth: i32 = 0;
279 let mut position: usize = start;
280 while position < chars.len() {
281 if chars[position] == CHAR_DOUBLE_QUOTE || chars[position] == CHAR_SINGLE_QUOTE {
282 let (_, end) = extract_string_literal(chars, position, chars.len());
283 position = end;
284 continue;
285 }
286 if position + 1 < chars.len()
287 && chars[position] == CHAR_SLASH_FORWARD
288 && chars[position + 1] == CHAR_SLASH_FORWARD
289 {
290 let (_, end) = extract_line_comment(chars, position, chars.len());
291 position = end;
292 continue;
293 }
294 if position + 1 < chars.len()
295 && chars[position] == CHAR_SLASH_FORWARD
296 && chars[position + 1] == CHAR_ASTERISK
297 {
298 let (_, end) = extract_block_comment(chars, position, chars.len());
299 position = end;
300 continue;
301 }
302 if chars[position] == CHAR_BRACE_LEFT {
303 depth += 1;
304 } else if chars[position] == CHAR_BRACE_RIGHT {
305 depth -= 1;
306 if depth == 0 {
307 let content: String = chars[start..=position].iter().collect();
308 return (content, position + 1);
309 }
310 }
311 position += 1;
312 }
313 let content: String = chars[start..].iter().collect();
314 (content, chars.len())
315}
316
317fn extract_brace_content(chars: &[char], start: usize) -> (String, usize) {
331 let mut depth: i32 = 0;
332 let mut position: usize = start;
333 let mut content_start: usize = start + 1;
334 while position < chars.len() {
335 if chars[position] == CHAR_DOUBLE_QUOTE || chars[position] == CHAR_SINGLE_QUOTE {
336 let (_, end) = extract_string_literal(chars, position, chars.len());
337 position = end;
338 continue;
339 }
340 if position + 1 < chars.len()
341 && chars[position] == CHAR_SLASH_FORWARD
342 && chars[position + 1] == CHAR_SLASH_FORWARD
343 {
344 let (_, end) = extract_line_comment(chars, position, chars.len());
345 position = end;
346 continue;
347 }
348 if position + 1 < chars.len()
349 && chars[position] == CHAR_SLASH_FORWARD
350 && chars[position + 1] == CHAR_ASTERISK
351 {
352 let (_, end) = extract_block_comment(chars, position, chars.len());
353 position = end;
354 continue;
355 }
356 if chars[position] == CHAR_BRACE_LEFT {
357 if depth == 0 {
358 content_start = position + 1;
359 }
360 depth += 1;
361 } else if chars[position] == CHAR_BRACE_RIGHT {
362 depth -= 1;
363 if depth == 0 {
364 let content: String = chars[content_start..position].iter().collect();
365 return (content, position + 1);
366 }
367 }
368 position += 1;
369 }
370 let content: String = chars[content_start..].iter().collect();
371 (content, chars.len())
372}
373
374fn extract_string_literal(chars: &[char], start: usize, len: usize) -> (String, usize) {
388 let quote: char = chars[start];
389 let mut position: usize = start + 1;
390 let mut result: String = String::new();
391 result.push(quote);
392 while position < len {
393 if chars[position] == CHAR_SLASH_BACK && position + 1 < len {
394 result.push(chars[position]);
395 result.push(chars[position + 1]);
396 position += 2;
397 continue;
398 }
399 result.push(chars[position]);
400 if chars[position] == quote {
401 return (result, position + 1);
402 }
403 position += 1;
404 }
405 (result, position)
406}
407
408fn extract_line_comment(chars: &[char], start: usize, len: usize) -> (String, usize) {
420 let mut position: usize = start;
421 let mut result: String = String::new();
422 while position < len && chars[position] != CHAR_NEWLINE {
423 result.push(chars[position]);
424 position += 1;
425 }
426 if position < len {
427 result.push(CHAR_NEWLINE);
428 position += 1;
429 }
430 (result, position)
431}
432
433fn extract_block_comment(chars: &[char], start: usize, len: usize) -> (String, usize) {
445 let mut position: usize = start + 2;
446 let mut result: String = String::from(BLOCK_COMMENT_START);
447 while position + 1 < len {
448 result.push(chars[position]);
449 if chars[position] == CHAR_ASTERISK && chars[position + 1] == CHAR_SLASH_FORWARD {
450 result.push(CHAR_SLASH_FORWARD);
451 return (result, position + 2);
452 }
453 position += 1;
454 }
455 while position < len {
456 result.push(chars[position]);
457 position += 1;
458 }
459 (result, position)
460}
461
462pub fn format_macro_body<B>(body: B) -> String
474where
475 B: AsRef<str>,
476{
477 let raw: String = format_macro_body_raw(body);
478 add_indentation(&raw)
479}
480
481fn format_macro_body_raw<B>(body: B) -> String
501where
502 B: AsRef<str>,
503{
504 let body: &str = body.as_ref();
505 let chars: Vec<char> = body.chars().collect();
506 let len: usize = chars.len();
507 let mut result: String = String::new();
508 let mut position: usize = 0;
509 while position < len {
510 if chars[position] == CHAR_DOUBLE_QUOTE || chars[position] == CHAR_SINGLE_QUOTE {
511 let (literal, end) = extract_string_literal(&chars, position, len);
512 result.push_str(&literal);
513 position = end;
514 continue;
515 }
516 if position + 1 < len
517 && chars[position] == CHAR_SLASH_FORWARD
518 && chars[position + 1] == CHAR_SLASH_FORWARD
519 {
520 let (comment, end) = extract_line_comment(&chars, position, len);
521 result.push_str(&comment);
522 position = end;
523 continue;
524 }
525 if position + 1 < len
526 && chars[position] == CHAR_SLASH_FORWARD
527 && chars[position + 1] == CHAR_ASTERISK
528 {
529 let (comment, end) = extract_block_comment(&chars, position, len);
530 result.push_str(&comment);
531 position = end;
532 continue;
533 }
534 if is_if_keyword(&chars, position, len) {
535 let after_if_colon: usize = skip_spaces_on_same_line(&chars, position + 2, len);
536 if after_if_colon < len
537 && chars[after_if_colon] == CHAR_COLON
538 && (after_if_colon + 1 >= len || chars[after_if_colon + 1] != CHAR_COLON)
539 {
540 result.push_str(KEYWORD_IF);
541 position += 2;
542 if position < len && chars[position] == CHAR_SPACE {
543 result.push(CHAR_SPACE);
544 position += 1;
545 }
546 continue;
547 }
548 result.push_str(KEYWORD_IF);
549 position += 2;
550 let after_if: usize = skip_spaces_on_same_line(&chars, position, len);
551 if position < len && chars[after_if] == CHAR_BRACE_LEFT {
552 result.push(CHAR_SPACE);
553 let (block, end) = extract_brace_block(&chars, after_if);
554 result.push_str(&format_brace_block(&block));
555 position = end;
556 position = skip_spaces_on_same_line(&chars, position, len);
557 if position < len && chars[position] == CHAR_BRACE_LEFT {
558 result.push(CHAR_SPACE);
559 }
560 } else {
561 result.push(CHAR_SPACE);
562 position = after_if;
563 }
564 continue;
565 }
566 if is_else_keyword(&chars, position, len) {
567 if !result.ends_with(CHAR_SPACE)
568 && !result.ends_with(CHAR_NEWLINE)
569 && !result.ends_with(CHAR_TAB)
570 {
571 result.push(CHAR_SPACE);
572 }
573 result.push_str(KEYWORD_ELSE);
574 position += 4;
575 position = skip_spaces_on_same_line(&chars, position, len);
576 if is_if_keyword(&chars, position, len) {
577 result.push(CHAR_SPACE);
578 continue;
579 }
580 if position < len && chars[position] == CHAR_BRACE_LEFT {
581 result.push(CHAR_SPACE);
582 }
583 continue;
584 }
585 if is_match_keyword(&chars, position, len) {
586 let after_match_colon: usize = skip_spaces_on_same_line(&chars, position + 5, len);
587 if after_match_colon < len
588 && chars[after_match_colon] == CHAR_COLON
589 && (after_match_colon + 1 >= len || chars[after_match_colon + 1] != CHAR_COLON)
590 {
591 result.push_str(KEYWORD_MATCH);
592 position += 5;
593 if position < len && chars[position] == CHAR_SPACE {
594 result.push(CHAR_SPACE);
595 position += 1;
596 }
597 continue;
598 }
599 result.push_str(KEYWORD_MATCH);
600 position += 5;
601 let after_match: usize = skip_spaces_on_same_line(&chars, position, len);
602 if after_match < len && chars[after_match] == CHAR_BRACE_LEFT {
603 result.push(CHAR_SPACE);
604 let (block, end) = extract_brace_block(&chars, after_match);
605 result.push_str(&format_brace_block(&block));
606 position = end;
607 position = skip_spaces_on_same_line(&chars, position, len);
608 if position < len && chars[position] == CHAR_BRACE_LEFT {
609 result.push(CHAR_SPACE);
610 }
611 } else {
612 result.push(CHAR_SPACE);
613 position = after_match;
614 }
615 continue;
616 }
617 if is_for_keyword(&chars, position, len) {
618 let after_for: usize = skip_spaces_on_same_line(&chars, position + 3, len);
619 if after_for < len
620 && chars[after_for] == CHAR_COLON
621 && (after_for + 1 >= len || chars[after_for + 1] != CHAR_COLON)
622 {
623 result.push_str(KEYWORD_FOR);
624 position += 3;
625 if position < len && chars[position] == CHAR_SPACE {
626 result.push(CHAR_SPACE);
627 position += 1;
628 }
629 continue;
630 }
631 result.push_str(KEYWORD_FOR);
632 position += 3;
633 position = skip_spaces_on_same_line(&chars, position, len);
634 if position < len && !is_in_keyword(&chars, position, len) {
635 result.push(CHAR_SPACE);
636 }
637 while position < len && !is_in_keyword(&chars, position, len) {
638 if chars[position] == CHAR_BRACE_LEFT {
639 let (block, end) = extract_brace_block(&chars, position);
640 result.push_str(&format_brace_block(&block));
641 position = end;
642 continue;
643 }
644 if chars[position] == CHAR_DOUBLE_QUOTE || chars[position] == CHAR_SINGLE_QUOTE {
645 let (literal, end) = extract_string_literal(&chars, position, len);
646 result.push_str(&literal);
647 position = end;
648 continue;
649 }
650 result.push(chars[position]);
651 position += 1;
652 }
653 if result.ends_with(CHAR_SPACE) {
654 result.truncate(result.len() - 1);
655 }
656 position = skip_spaces_on_same_line(&chars, position, len);
657 if is_in_keyword(&chars, position, len) {
658 result.push(CHAR_SPACE);
659 result.push_str(KEYWORD_IN);
660 position += 2;
661 position = skip_spaces_on_same_line(&chars, position, len);
662 if position < len && chars[position] == CHAR_BRACE_LEFT {
663 result.push(CHAR_SPACE);
664 let (block, end) = extract_brace_block(&chars, position);
665 result.push_str(&format_brace_block(&block));
666 position = end;
667 position = skip_spaces_on_same_line(&chars, position, len);
668 if position < len && chars[position] == CHAR_BRACE_LEFT {
669 result.push(CHAR_SPACE);
670 }
671 } else {
672 result.push(CHAR_SPACE);
673 while position < len && chars[position] != CHAR_BRACE_LEFT {
674 result.push(chars[position]);
675 position += 1;
676 }
677 if result.ends_with(CHAR_SPACE) {
678 result.truncate(result.len() - 1);
679 }
680 if position < len && chars[position] == CHAR_BRACE_LEFT {
681 result.push(CHAR_SPACE);
682 }
683 }
684 }
685 continue;
686 }
687 if chars[position] == CHAR_COLON && position + 1 < len && chars[position + 1] != CHAR_COLON
688 {
689 if result.ends_with(CHAR_COLON) {
690 result.push(CHAR_COLON);
691 position += 1;
692 continue;
693 }
694 let colon_prefix: String = find_ident_before(&result);
695 if is_raw_ident_before(&result, &colon_prefix) {
696 result.push(CHAR_COLON);
697 position += 1;
698 continue;
699 }
700 if !colon_prefix.is_empty() {
701 let before_colon: String = remove_trailing_spaces(&result, colon_prefix.len());
702 result = before_colon;
703 result.push_str(&colon_prefix);
704 }
705 result.push(CHAR_COLON);
706 position += 1;
707 while position < len && (chars[position] == CHAR_SPACE || chars[position] == CHAR_TAB) {
708 position += 1;
709 }
710 if position < len
711 && chars[position] != CHAR_NEWLINE
712 && chars[position] != CHAR_CARRIAGE_RETURN
713 && !is_pseudo_selector_after_colon(&chars, position, len)
714 {
715 result.push(CHAR_SPACE);
716 }
717 continue;
718 }
719 if position + 1 < len
720 && chars[position] == CHAR_EQUALS
721 && chars[position + 1] == CHAR_GREATER_THAN
722 {
723 let trailing: String = find_trailing_spaces(&result);
724 if !trailing.is_empty() {
725 result.truncate(result.len() - trailing.len());
726 }
727 result.push(CHAR_SPACE);
728 result.push_str(ARROW_FAT);
729 position += 2;
730 while position < len && (chars[position] == CHAR_SPACE || chars[position] == CHAR_TAB) {
731 position += 1;
732 }
733 result.push(CHAR_SPACE);
734 continue;
735 }
736 if chars[position] == CHAR_BRACE_LEFT {
737 let (inner, end) = extract_brace_content(&chars, position);
738 let trimmed_inner: &str = inner.trim();
739 if trimmed_inner.is_empty() {
740 result.push(CHAR_BRACE_LEFT);
741 result.push(CHAR_BRACE_RIGHT);
742 } else {
743 let formatted_inner: String = format_macro_body_raw(trimmed_inner);
744 result.push(CHAR_BRACE_LEFT);
745 result.push(CHAR_NEWLINE);
746 result.push_str(&formatted_inner);
747 result.push(CHAR_NEWLINE);
748 result.push(CHAR_BRACE_RIGHT);
749 }
750 position = end;
751 continue;
752 }
753 if is_ident_char(chars[position]) {
754 let start: usize = position;
755 while position < len && is_ident_char(chars[position]) {
756 position += 1;
757 }
758 let ident: String = chars[start..position].iter().collect();
759 result.push_str(&ident);
760 let ws_start: usize = position;
761 while position < len && (chars[position] == CHAR_SPACE || chars[position] == CHAR_TAB) {
762 position += 1;
763 }
764 let had_whitespace: bool = position > ws_start;
765 if position < len && chars[position] == CHAR_BRACE_LEFT {
766 result.push(CHAR_SPACE);
767 } else if had_whitespace {
768 let next_pos: usize = position;
769 if next_pos < len && is_ident_char(chars[next_pos]) {
770 let mut ident_end: usize = next_pos;
771 while ident_end < len && is_ident_char(chars[ident_end]) {
772 ident_end += 1;
773 }
774 let after_ident: usize = skip_spaces_on_same_line(&chars, ident_end, len);
775 if after_ident < len
776 && chars[after_ident] == CHAR_COLON
777 && (after_ident + 1 >= len || chars[after_ident + 1] != CHAR_COLON)
778 {
779 result.push(CHAR_NEWLINE);
780 continue;
781 }
782 }
783 let ws: String = chars[ws_start..position].iter().collect();
784 result.push_str(&ws);
785 }
786 continue;
787 }
788 result.push(chars[position]);
789 position += 1;
790 }
791 result
792}
793
794fn add_indentation(body: &str) -> String {
807 let chars: Vec<char> = body.chars().collect();
808 let len: usize = chars.len();
809 let mut result: String = String::new();
810 let mut depth: i32 = 0;
811 let mut index: usize = 0;
812 while index < len {
813 if chars[index] == CHAR_DOUBLE_QUOTE || chars[index] == CHAR_SINGLE_QUOTE {
814 let quote: char = chars[index];
815 result.push(chars[index]);
816 index += 1;
817 while index < len {
818 if chars[index] == CHAR_SLASH_BACK && index + 1 < len {
819 result.push(chars[index]);
820 result.push(chars[index + 1]);
821 index += 2;
822 continue;
823 }
824 result.push(chars[index]);
825 if chars[index] == quote {
826 index += 1;
827 break;
828 }
829 index += 1;
830 }
831 continue;
832 }
833 if index + 1 < len
834 && chars[index] == CHAR_SLASH_FORWARD
835 && chars[index + 1] == CHAR_SLASH_FORWARD
836 {
837 while index < len && chars[index] != CHAR_NEWLINE {
838 result.push(chars[index]);
839 index += 1;
840 }
841 continue;
842 }
843 if index + 1 < len
844 && chars[index] == CHAR_SLASH_FORWARD
845 && chars[index + 1] == CHAR_ASTERISK
846 {
847 result.push(chars[index]);
848 result.push(chars[index + 1]);
849 index += 2;
850 while index + 1 < len {
851 if chars[index] == CHAR_ASTERISK && chars[index + 1] == CHAR_SLASH_FORWARD {
852 result.push(chars[index]);
853 result.push(chars[index + 1]);
854 index += 2;
855 break;
856 }
857 result.push(chars[index]);
858 index += 1;
859 }
860 continue;
861 }
862 if chars[index] == CHAR_NEWLINE {
863 result.push(CHAR_NEWLINE);
864 index += 1;
865 while index < len && (chars[index] == CHAR_SPACE || chars[index] == CHAR_TAB) {
866 index += 1;
867 }
868 if index >= len || chars[index] == CHAR_NEWLINE || chars[index] == CHAR_CARRIAGE_RETURN
869 {
870 continue;
871 }
872 let mut closing_count: i32 = 0;
873 let mut peek: usize = index;
874 while peek < len && chars[peek] == CHAR_BRACE_RIGHT {
875 closing_count += 1;
876 peek += 1;
877 }
878 let indent_depth: i32 = (depth - closing_count).max(0);
879 for _ in 0..indent_depth * 4 {
880 result.push(CHAR_SPACE);
881 }
882 continue;
883 }
884 if chars[index] == CHAR_BRACE_LEFT {
885 depth += 1;
886 result.push(CHAR_BRACE_LEFT);
887 index += 1;
888 continue;
889 }
890 if chars[index] == CHAR_BRACE_RIGHT {
891 depth -= 1;
892 result.push(CHAR_BRACE_RIGHT);
893 index += 1;
894 let mut peek: usize = index;
895 while peek < len && (chars[peek] == CHAR_SPACE || chars[peek] == CHAR_TAB) {
896 peek += 1;
897 }
898 if peek < len
899 && chars[peek] != CHAR_BRACE_RIGHT
900 && chars[peek] != CHAR_BRACE_LEFT
901 && chars[peek] != CHAR_NEWLINE
902 && chars[peek] != CHAR_CARRIAGE_RETURN
903 && chars[peek] != CHAR_RIGHT_PAREN
904 && chars[peek] != CHAR_COMMA
905 && chars[peek] != CHAR_SEMICOLON
906 {
907 let next_chars: String = chars[peek..(peek + 4).min(len)].iter().collect();
908 if next_chars != KEYWORD_ELSE {
909 result.push(CHAR_NEWLINE);
910 let indent_depth: i32 = depth.max(0);
911 for _ in 0..indent_depth * 4 {
912 result.push(CHAR_SPACE);
913 }
914 index = peek;
915 }
916 }
917 continue;
918 }
919 result.push(chars[index]);
920 index += 1;
921 }
922 result
923}
924
925fn indented_body_skipping_block_comments(body: &str, indent_str: &str) -> String {
947 let chars: Vec<char> = body.chars().collect();
948 let len: usize = chars.len();
949 let mut in_block_comment: bool = false;
950 let mut line_starts_in_comment: bool = false;
951 let mut result_lines: Vec<String> = Vec::new();
952 let mut current_line: String = String::new();
953 let mut index: usize = 0;
954 while index < len {
955 if !in_block_comment
956 && index + 1 < len
957 && chars[index] == CHAR_SLASH_FORWARD
958 && chars[index + 1] == CHAR_ASTERISK
959 {
960 in_block_comment = true;
961 line_starts_in_comment = current_line.is_empty()
962 || current_line
963 .chars()
964 .all(|c: char| c == CHAR_SPACE || c == CHAR_TAB);
965 current_line.push(chars[index]);
966 current_line.push(chars[index + 1]);
967 index += 2;
968 continue;
969 }
970 if in_block_comment {
971 current_line.push(chars[index]);
972 if chars[index] == CHAR_ASTERISK
973 && index + 1 < len
974 && chars[index + 1] == CHAR_SLASH_FORWARD
975 {
976 current_line.push(chars[index + 1]);
977 index += 2;
978 in_block_comment = false;
979 continue;
980 }
981 index += 1;
982 continue;
983 }
984 if chars[index] == CHAR_NEWLINE {
985 current_line.push(CHAR_NEWLINE);
986 let line_owned: String = take(&mut current_line);
987 let line_ref: &str = line_owned.as_str();
988 let indented: String = if line_starts_in_comment || line_ref.trim().is_empty() {
989 line_owned
990 } else {
991 let mut s: String = String::with_capacity(indent_str.len() + line_owned.len());
992 s.push_str(indent_str);
993 s.push_str(line_owned.as_str());
994 s
995 };
996 result_lines.push(indented);
997 line_starts_in_comment = in_block_comment;
1003 index += 1;
1004 continue;
1005 }
1006 current_line.push(chars[index]);
1007 index += 1;
1008 }
1009 let line_owned: String = current_line;
1010 let indented: String = if line_starts_in_comment || line_owned.trim().is_empty() {
1011 line_owned
1012 } else {
1013 let mut s: String = String::with_capacity(indent_str.len() + line_owned.len());
1014 s.push_str(indent_str);
1015 s.push_str(line_owned.as_str());
1016 s
1017 };
1018 result_lines.push(indented);
1019 result_lines.join("")
1020}
1021
1022fn is_if_keyword(chars: &[char], pos: usize, len: usize) -> bool {
1034 if pos + 2 > len {
1035 return false;
1036 }
1037 chars[pos] == CHAR_LETTER_I
1038 && chars[pos + 1] == CHAR_LETTER_F
1039 && (pos + 2 >= len || !is_ident_char(chars[pos + 2]))
1040 && (pos == 0 || !is_ident_char(chars[pos - 1]))
1041 && !is_raw_prefix(chars, pos)
1042}
1043
1044fn is_else_keyword(chars: &[char], pos: usize, len: usize) -> bool {
1056 if pos + 4 > len {
1057 return false;
1058 }
1059 chars[pos] == CHAR_LETTER_E
1060 && chars[pos + 1] == CHAR_LETTER_L
1061 && chars[pos + 2] == CHAR_LETTER_S
1062 && chars[pos + 3] == CHAR_LETTER_E
1063 && (pos + 4 >= len || !is_ident_char(chars[pos + 4]))
1064 && (pos == 0 || !is_ident_char(chars[pos - 1]))
1065 && !is_raw_prefix(chars, pos)
1066}
1067
1068fn is_match_keyword(chars: &[char], pos: usize, len: usize) -> bool {
1080 if pos + 5 > len {
1081 return false;
1082 }
1083 chars[pos] == CHAR_LETTER_M
1084 && chars[pos + 1] == CHAR_LETTER_A
1085 && chars[pos + 2] == CHAR_LETTER_T
1086 && chars[pos + 3] == CHAR_LETTER_C
1087 && chars[pos + 4] == CHAR_LETTER_H
1088 && (pos + 5 >= len || !is_ident_char(chars[pos + 5]))
1089 && (pos == 0 || !is_ident_char(chars[pos - 1]))
1090 && !is_raw_prefix(chars, pos)
1091}
1092
1093fn is_for_keyword(chars: &[char], pos: usize, len: usize) -> bool {
1105 if pos + 3 > len {
1106 return false;
1107 }
1108 chars[pos] == CHAR_LETTER_F
1109 && chars[pos + 1] == CHAR_LETTER_O
1110 && chars[pos + 2] == CHAR_LETTER_R
1111 && (pos + 3 >= len || !is_ident_char(chars[pos + 3]))
1112 && (pos == 0 || !is_ident_char(chars[pos - 1]))
1113 && !is_raw_prefix(chars, pos)
1114}
1115
1116fn is_in_keyword(chars: &[char], pos: usize, len: usize) -> bool {
1128 if pos + 2 > len {
1129 return false;
1130 }
1131 chars[pos] == CHAR_LETTER_I
1132 && chars[pos + 1] == CHAR_LETTER_N
1133 && (pos + 2 >= len || !is_ident_char(chars[pos + 2]))
1134 && (pos == 0 || !is_ident_char(chars[pos - 1]))
1135 && !is_raw_prefix(chars, pos)
1136}
1137
1138fn format_brace_block(block: &str) -> String {
1152 let block_chars: Vec<char> = block.chars().collect();
1153 if block_chars.len() < 2
1154 || block_chars[0] != CHAR_BRACE_LEFT
1155 || block_chars.last() != Some(&CHAR_BRACE_RIGHT)
1156 {
1157 return block.to_string();
1158 }
1159 let inner: String = block_chars[1..block_chars.len() - 1].iter().collect();
1160 if inner.contains(CHAR_NEWLINE) {
1161 return block.to_string();
1162 }
1163 let trimmed_inner: &str = inner.trim();
1164 if trimmed_inner.is_empty() {
1165 let mut empty_result: String = String::new();
1166 empty_result.push(CHAR_BRACE_LEFT);
1167 empty_result.push(CHAR_BRACE_RIGHT);
1168 return empty_result;
1169 }
1170 let mut formatted: String = String::new();
1171 formatted.push(CHAR_BRACE_LEFT);
1172 formatted.push(CHAR_SPACE);
1173 formatted.push_str(trimmed_inner);
1174 formatted.push(CHAR_SPACE);
1175 formatted.push(CHAR_BRACE_RIGHT);
1176 formatted
1177}
1178
1179fn skip_spaces_on_same_line(chars: &[char], mut pos: usize, len: usize) -> usize {
1191 while pos < len && (chars[pos] == CHAR_SPACE || chars[pos] == CHAR_TAB) {
1192 pos += 1;
1193 }
1194 pos
1195}
1196
1197fn is_pseudo_selector_after_colon(chars: &[char], mut pos: usize, len: usize) -> bool {
1222 if pos >= len || !is_ident_char(chars[pos]) {
1223 return false;
1224 }
1225 let ident_start: usize = pos;
1226 while pos < len && is_ident_char(chars[pos]) {
1227 pos += 1;
1228 }
1229 let first_ident: String = chars[ident_start..pos].iter().collect();
1230 if is_rust_keyword(&first_ident) {
1231 return false;
1232 }
1233 while pos < len && chars[pos] == CHAR_HYPHEN && pos + 1 < len && is_ident_char(chars[pos + 1]) {
1234 pos += 1;
1235 while pos < len && is_ident_char(chars[pos]) {
1236 pos += 1;
1237 }
1238 }
1239 let after_ident: usize = skip_spaces_on_same_line(chars, pos, len);
1240 if after_ident < len && chars[after_ident] == CHAR_BRACE_LEFT {
1241 return true;
1242 }
1243 if after_ident < len && chars[after_ident] == CHAR_LEFT_PAREN {
1244 let mut depth: i32 = 0;
1245 let mut paren_pos: usize = after_ident;
1246 while paren_pos < len {
1247 if chars[paren_pos] == CHAR_LEFT_PAREN {
1248 depth += 1;
1249 } else if chars[paren_pos] == CHAR_RIGHT_PAREN {
1250 depth -= 1;
1251 if depth == 0 {
1252 let after_paren: usize = skip_spaces_on_same_line(chars, paren_pos + 1, len);
1253 return after_paren < len && chars[after_paren] == CHAR_BRACE_LEFT;
1254 }
1255 }
1256 paren_pos += 1;
1257 }
1258 }
1259 false
1260}
1261
1262fn is_rust_keyword(ident: &str) -> bool {
1275 ATTRIBUTE_VALUE_KEYWORDS.contains(&ident)
1276}
1277
1278fn is_raw_ident_before(result: &str, ident: &str) -> bool {
1292 result
1293 .trim_end()
1294 .ends_with(&format!("{RAW_IDENT_PREFIX}{ident}"))
1295}
1296
1297fn find_ident_before(result: &str) -> String {
1307 let chars: Vec<char> = result.chars().collect();
1308 let mut end: usize = chars.len();
1309 while end > 0 && chars[end - 1] == CHAR_SPACE {
1310 end -= 1;
1311 }
1312 let mut start: usize = end;
1313 while start > 0 && is_ident_char(chars[start - 1]) {
1314 start -= 1;
1315 }
1316 if start < end {
1317 chars[start..end].iter().collect()
1318 } else {
1319 String::new()
1320 }
1321}
1322
1323fn remove_trailing_spaces(result: &str, prefix_len: usize) -> String {
1334 let chars: Vec<char> = result.chars().collect();
1335 let total_len: usize = chars.len();
1336 let mut end: usize = total_len;
1337 while end > 0 && chars[end - 1] == CHAR_SPACE {
1338 end -= 1;
1339 }
1340 if prefix_len > end {
1341 return result.to_string();
1342 }
1343 let new_end: usize = end - prefix_len;
1344 chars[..new_end].iter().collect()
1345}
1346
1347fn find_trailing_spaces(result: &str) -> String {
1357 let mut spaces: String = String::new();
1358 for ch in result.chars().rev() {
1359 if ch == CHAR_SPACE || ch == CHAR_TAB {
1360 spaces.push(ch);
1361 } else {
1362 break;
1363 }
1364 }
1365 spaces.chars().rev().collect()
1366}
1367
1368pub async fn format_dir(path: &Path, mode: FmtMode) -> Result<(), EuvError> {
1382 if path.is_file() {
1383 let changed: bool = format_file(path, &mode).await?;
1384 match mode {
1385 FmtMode::Check => {
1386 if changed {
1387 return Err(EuvError::Message(format!(
1388 "{} needs formatting.",
1389 path.display()
1390 )));
1391 }
1392 log::info!("{} is properly formatted.", path.display());
1393 }
1394 FmtMode::Write => {
1395 if changed {
1396 log::info!("Formatted: {}", path.display());
1397 } else {
1398 log::info!("Already formatted: {}", path.display());
1399 }
1400 }
1401 }
1402 return Ok(());
1403 }
1404 let mut entries: Vec<PathBuf> = collect_rs_files(path).await?;
1405 entries.sort();
1406 let mut changed_count: usize = 0;
1407 let mut unchanged_count: usize = 0;
1408 for entry in entries {
1409 match format_file(&entry, &mode).await {
1410 Ok(changed) => {
1411 if changed {
1412 changed_count += 1;
1413 } else {
1414 unchanged_count += 1;
1415 }
1416 }
1417 Err(error) => {
1418 log::warn!("Failed to format {}: {error}", entry.display());
1419 }
1420 }
1421 }
1422 match mode {
1423 FmtMode::Check => {
1424 if changed_count > 0 {
1425 return Err(EuvError::Message(format!(
1426 "{} file(s) need formatting. Run `euv fmt` to fix.",
1427 changed_count
1428 )));
1429 }
1430 log::info!("All {} file(s) are properly formatted.", unchanged_count);
1431 }
1432 FmtMode::Write => {
1433 log::info!(
1434 "Formatted {} file(s), {} unchanged.",
1435 changed_count,
1436 unchanged_count
1437 );
1438 }
1439 }
1440 Ok(())
1441}
1442
1443async fn collect_rs_files(path: &Path) -> Result<Vec<PathBuf>, EuvError> {
1453 let mut result: Vec<PathBuf> = Vec::new();
1454 let mut stack: Vec<PathBuf> = vec![path.to_path_buf()];
1455 while let Some(dir) = stack.pop() {
1456 let mut entries: ReadDir =
1457 read_dir(&dir)
1458 .await
1459 .map_err(|error: io::Error| EuvError::IoPath {
1460 message: ERROR_READ_DIRECTORY.to_string(),
1461 path: dir.clone(),
1462 error,
1463 })?;
1464 while let Some(entry) =
1465 entries
1466 .next_entry()
1467 .await
1468 .map_err(|error: io::Error| EuvError::IoPath {
1469 message: ERROR_READ_ENTRY_IN_DIRECTORY.to_string(),
1470 path: dir.clone(),
1471 error,
1472 })?
1473 {
1474 let entry_path: PathBuf = entry.path();
1475 if entry_path.is_dir() {
1476 let file_name: String = entry_path
1477 .file_name()
1478 .unwrap_or_default()
1479 .to_string_lossy()
1480 .to_string();
1481 if file_name != TARGET_DIR_NAME && file_name != NODE_MODULES_DIR_NAME {
1482 stack.push(entry_path);
1483 }
1484 } else if entry_path
1485 .extension()
1486 .is_some_and(|ext: &OsStr| ext == RS_EXTENSION)
1487 {
1488 result.push(entry_path);
1489 }
1490 }
1491 }
1492 Ok(result)
1493}
1494
1495async fn format_file(path: &Path, mode: &FmtMode) -> Result<bool, EuvError> {
1506 let content: String =
1507 read_to_string(path)
1508 .await
1509 .map_err(|error: io::Error| EuvError::IoPath {
1510 message: ERROR_READ_FILE.to_string(),
1511 path: path.to_path_buf(),
1512 error,
1513 })?;
1514 let fmt_result: FmtResult = format_source(&content);
1515 if fmt_result.get_changed() {
1516 match mode {
1517 FmtMode::Write => {
1518 write(path, fmt_result.get_output())
1519 .await
1520 .map_err(|error: io::Error| EuvError::IoPath {
1521 message: ERROR_WRITE_FILE.to_string(),
1522 path: path.to_path_buf(),
1523 error,
1524 })?;
1525 log::info!("Formatted: {}", path.display());
1526 }
1527 FmtMode::Check => {
1528 log::warn!("Needs formatting: {}", path.display());
1529 }
1530 }
1531 }
1532 Ok(fmt_result.get_changed())
1533}