rustfmt_nightly/formatting/
newline_style.rs

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