cargo_cargofmt/formatting/
newline_style.rs1use crate::config::options::NewlineStyle;
2
3#[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}