]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/formatting/newline_style.rs
Rollup merge of #90035 - SparrowLii:rfc2528, r=jackh726
[rust.git] / src / tools / rustfmt / src / formatting / newline_style.rs
1 use 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.
9 pub(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)]
21 enum EffectiveNewlineStyle {
22     Windows,
23     Unix,
24 }
25
26 fn 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
38 const LINE_FEED: char = '\n';
39 const CARRIAGE_RETURN: char = '\r';
40 const WINDOWS_NEWLINE: &str = "\r\n";
41 const UNIX_NEWLINE: &str = "\n";
42
43 fn 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
58 fn native_newline_style() -> EffectiveNewlineStyle {
59     if cfg!(windows) {
60         EffectiveNewlineStyle::Windows
61     } else {
62         EffectiveNewlineStyle::Unix
63     }
64 }
65
66 fn 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
80 fn convert_to_unix_newlines(formatted_text: &str) -> String {
81     formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE)
82 }
83
84 #[cfg(test)]
85 mod 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 }