cargo_cargofmt/formatting/
newline_style.rs

1use crate::config::options::NewlineStyle;
2
3/// Apply this newline style to the formatted text. When the style is set
4/// to `Auto`, the `raw_input_text` is used to detect the existing line
5/// endings.
6///
7/// If the style is set to `Auto` and `raw_input_text` contains no
8/// newlines, the `Native` style will be used.
9#[tracing::instrument]
10pub fn apply_newline_style(
11    newline_style: NewlineStyle,
12    formatted_text: &mut String,
13    raw_input_text: &str,
14) {
15    *formatted_text = match effective_newline_style(newline_style, raw_input_text) {
16        EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text),
17        EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text),
18    }
19}
20
21#[derive(Debug, Copy, Clone, PartialEq, Eq)]
22enum EffectiveNewlineStyle {
23    Windows,
24    Unix,
25}
26
27fn effective_newline_style(
28    newline_style: NewlineStyle,
29    raw_input_text: &str,
30) -> EffectiveNewlineStyle {
31    match newline_style {
32        NewlineStyle::Auto => auto_detect_newline_style(raw_input_text),
33        NewlineStyle::Native => native_newline_style(),
34        NewlineStyle::Windows => EffectiveNewlineStyle::Windows,
35        NewlineStyle::Unix => EffectiveNewlineStyle::Unix,
36    }
37}
38
39const LINE_FEED: char = '\n';
40const CARRIAGE_RETURN: char = '\r';
41const WINDOWS_NEWLINE: &str = "\r\n";
42const UNIX_NEWLINE: &str = "\n";
43
44fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle {
45    let first_line_feed_pos = raw_input_text.chars().position(|ch| ch == LINE_FEED);
46    match first_line_feed_pos {
47        Some(first_line_feed_pos) => {
48            let char_before_line_feed_pos = first_line_feed_pos.saturating_sub(1);
49            let char_before_line_feed = raw_input_text.chars().nth(char_before_line_feed_pos);
50            match char_before_line_feed {
51                Some(CARRIAGE_RETURN) => EffectiveNewlineStyle::Windows,
52                _ => EffectiveNewlineStyle::Unix,
53            }
54        }
55        None => native_newline_style(),
56    }
57}
58
59fn native_newline_style() -> EffectiveNewlineStyle {
60    if cfg!(windows) {
61        EffectiveNewlineStyle::Windows
62    } else {
63        EffectiveNewlineStyle::Unix
64    }
65}
66
67fn convert_to_windows_newlines(formatted_text: &String) -> String {
68    let mut transformed = String::with_capacity(2 * formatted_text.capacity());
69    let mut chars = formatted_text.chars().peekable();
70    while let Some(current_char) = chars.next() {
71        let next_char = chars.peek();
72        match current_char {
73            LINE_FEED => transformed.push_str(WINDOWS_NEWLINE),
74            CARRIAGE_RETURN if next_char == Some(&LINE_FEED) => {}
75            current_char => transformed.push(current_char),
76        }
77    }
78    transformed
79}
80
81fn convert_to_unix_newlines(formatted_text: &str) -> String {
82    formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE)
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn auto_detects_unix_newlines() {
91        assert_eq!(
92            EffectiveNewlineStyle::Unix,
93            auto_detect_newline_style("One\nTwo\nThree")
94        );
95    }
96
97    #[test]
98    fn auto_detects_windows_newlines() {
99        assert_eq!(
100            EffectiveNewlineStyle::Windows,
101            auto_detect_newline_style("One\r\nTwo\r\nThree")
102        );
103    }
104
105    #[test]
106    fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() {
107        assert_eq!(
108            EffectiveNewlineStyle::Windows,
109            auto_detect_newline_style("A 🎢 of a first line\r\nTwo\r\nThree")
110        );
111    }
112
113    #[test]
114    fn falls_back_to_native_newlines_if_no_newlines_are_found() {
115        let expected_newline_style = if cfg!(windows) {
116            EffectiveNewlineStyle::Windows
117        } else {
118            EffectiveNewlineStyle::Unix
119        };
120        assert_eq!(
121            expected_newline_style,
122            auto_detect_newline_style("One Two Three")
123        );
124    }
125
126    #[test]
127    fn auto_detects_and_applies_unix_newlines() {
128        let formatted_text = "One\nTwo\nThree";
129        let raw_input_text = "One\nTwo\nThree";
130
131        let mut out = String::from(formatted_text);
132        apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
133        assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
134    }
135
136    #[test]
137    fn auto_detects_and_applies_windows_newlines() {
138        let formatted_text = "One\nTwo\nThree";
139        let raw_input_text = "One\r\nTwo\r\nThree";
140
141        let mut out = String::from(formatted_text);
142        apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
143        assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
144    }
145
146    #[test]
147    fn auto_detects_and_applies_native_newlines() {
148        let formatted_text = "One\nTwo\nThree";
149        let raw_input_text = "One Two Three";
150
151        let mut out = String::from(formatted_text);
152        apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
153
154        if cfg!(windows) {
155            assert_eq!(
156                "One\r\nTwo\r\nThree", &out,
157                "auto-native-windows should detect 'crlf'"
158            );
159        } else {
160            assert_eq!(
161                "One\nTwo\nThree", &out,
162                "auto-native-unix should detect 'lf'"
163            );
164        }
165    }
166
167    #[test]
168    fn applies_unix_newlines() {
169        test_newlines_are_applied_correctly(
170            "One\r\nTwo\nThree",
171            "One\nTwo\nThree",
172            NewlineStyle::Unix,
173        );
174    }
175
176    #[test]
177    fn applying_unix_newlines_changes_nothing_for_unix_newlines() {
178        let formatted_text = "One\nTwo\nThree";
179        test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Unix);
180    }
181
182    #[test]
183    fn applies_unix_newlines_to_string_with_unix_and_windows_newlines() {
184        test_newlines_are_applied_correctly(
185            "One\r\nTwo\r\nThree\nFour",
186            "One\nTwo\nThree\nFour",
187            NewlineStyle::Unix,
188        );
189    }
190
191    #[test]
192    fn applies_windows_newlines_to_string_with_unix_and_windows_newlines() {
193        test_newlines_are_applied_correctly(
194            "One\nTwo\nThree\r\nFour",
195            "One\r\nTwo\r\nThree\r\nFour",
196            NewlineStyle::Windows,
197        );
198    }
199
200    #[test]
201    fn applying_windows_newlines_changes_nothing_for_windows_newlines() {
202        let formatted_text = "One\r\nTwo\r\nThree";
203        test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Windows);
204    }
205
206    #[test]
207    fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_unix_newlines() {
208        test_newlines_are_applied_correctly(
209            "One\nTwo\nThree\rDrei",
210            "One\r\nTwo\r\nThree\rDrei",
211            NewlineStyle::Windows,
212        );
213    }
214
215    #[test]
216    fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_unix_newlines() {
217        test_newlines_are_applied_correctly(
218            "One\nTwo\nThree\rDrei",
219            "One\nTwo\nThree\rDrei",
220            NewlineStyle::Unix,
221        );
222    }
223
224    #[test]
225    fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_windows_newlines() {
226        test_newlines_are_applied_correctly(
227            "One\r\nTwo\r\nThree\rDrei",
228            "One\r\nTwo\r\nThree\rDrei",
229            NewlineStyle::Windows,
230        );
231    }
232
233    #[test]
234    fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_windows_newlines() {
235        test_newlines_are_applied_correctly(
236            "One\r\nTwo\r\nThree\rDrei",
237            "One\nTwo\nThree\rDrei",
238            NewlineStyle::Unix,
239        );
240    }
241
242    fn test_newlines_are_applied_correctly(
243        input: &str,
244        expected: &str,
245        newline_style: NewlineStyle,
246    ) {
247        let mut out = String::from(input);
248        apply_newline_style(newline_style, &mut out, input);
249        assert_eq!(expected, &out);
250    }
251}