Skip to content

Commit 4ec7c28

Browse files
committed
Backport 4236
Read the original file for comparing/detecting newline (4236) Closes 4097 The code base diverged enough where a simple cherry-pick wasn't possible, but implementing the logic to read the input file when `NewlineStyle::Auto` was simple enough to implement. New tests were added to hopefully prevent regressions.
1 parent 2403f82 commit 4ec7c28

File tree

5 files changed

+187
-11
lines changed

5 files changed

+187
-11
lines changed

Cargo.lock

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ rustc-workspace-hack = "1.0.0"
6666

6767
# Rustc dependencies are loaded from the sysroot, Cargo doesn't know about them.
6868

69+
[dev-dependencies]
70+
tempdir = "0.3.7"
71+
6972
[package.metadata.rust-analyzer]
7073
# This package uses #[feature(rustc_private)]
7174
rustc_private = true

src/config/file_lines.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use itertools::Itertools;
44
use std::collections::HashMap;
5-
use std::path::PathBuf;
5+
use std::path::{Path, PathBuf};
66
use std::{cmp, fmt, iter, str};
77

88
use rustc_data_structures::sync::Lrc;
@@ -25,6 +25,15 @@ pub enum FileName {
2525
Stdin,
2626
}
2727

28+
impl FileName {
29+
pub(crate) fn as_path(&self) -> Option<&Path> {
30+
match self {
31+
FileName::Real(ref path) => Some(path),
32+
_ => None,
33+
}
34+
}
35+
}
36+
2837
impl From<rustc_span::FileName> for FileName {
2938
fn from(name: rustc_span::FileName) -> FileName {
3039
match name {

src/formatting.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
230230

231231
apply_newline_style(
232232
self.config.newline_style(),
233+
&path,
233234
&mut visitor.buffer,
234235
snippet_provider.entire_snippet(),
235236
);

src/formatting/newline_style.rs

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
use crate::NewlineStyle;
1+
use crate::{FileName, NewlineStyle};
2+
use std::borrow::Cow;
3+
use std::fs;
24

35
/// 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+
/// to `Auto`, the `path` and `raw_input_text` are used to detect the existing line
7+
/// endings. The `path` is prefered when present, and the `raw_input_text` is used
8+
/// when the input was passed from stdin, or we fail to read the file content from the `path`.
69
///
7-
/// If the style is set to `Auto` and `raw_input_text` contains no
10+
/// If the style is set to `Auto` and `path` or `raw_input_text` contain no
811
/// newlines, the `Native` style will be used.
912
pub(crate) fn apply_newline_style(
1013
newline_style: NewlineStyle,
14+
path: &FileName,
1115
formatted_text: &mut String,
1216
raw_input_text: &str,
1317
) {
14-
*formatted_text = match effective_newline_style(newline_style, raw_input_text) {
18+
*formatted_text = match effective_newline_style(newline_style, path, raw_input_text) {
1519
EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text),
1620
EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text),
1721
}
@@ -25,10 +29,19 @@ enum EffectiveNewlineStyle {
2529

2630
fn effective_newline_style(
2731
newline_style: NewlineStyle,
32+
path: &FileName,
2833
raw_input_text: &str,
2934
) -> EffectiveNewlineStyle {
3035
match newline_style {
31-
NewlineStyle::Auto => auto_detect_newline_style(raw_input_text),
36+
NewlineStyle::Auto => match path.as_path() {
37+
Some(path) => auto_detect_newline_style(
38+
&fs::read_to_string(path)
39+
.map(|s| Cow::Owned(s))
40+
.unwrap_or_else(|_| Cow::Borrowed(raw_input_text)),
41+
),
42+
None => auto_detect_newline_style(raw_input_text),
43+
},
44+
3245
NewlineStyle::Native => native_newline_style(),
3346
NewlineStyle::Windows => EffectiveNewlineStyle::Windows,
3447
NewlineStyle::Unix => EffectiveNewlineStyle::Unix,
@@ -84,6 +97,8 @@ fn convert_to_unix_newlines(formatted_text: &str) -> String {
8497
#[cfg(test)]
8598
mod tests {
8699
use super::*;
100+
use std::fs;
101+
use tempdir::TempDir;
87102

88103
#[test]
89104
fn auto_detects_unix_newlines() {
@@ -128,7 +143,28 @@ mod tests {
128143
let raw_input_text = "One\nTwo\nThree";
129144

130145
let mut out = String::from(formatted_text);
131-
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
146+
apply_newline_style(
147+
NewlineStyle::Auto,
148+
&FileName::Stdin,
149+
&mut out,
150+
raw_input_text,
151+
);
152+
assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
153+
}
154+
155+
#[test]
156+
fn auto_detects_and_applies_unix_newlines_from_real_file() {
157+
let formatted_text = "One\nTwo\nThree";
158+
let raw_input_text = "One\nTwo\nThree";
159+
160+
let tmpdir = TempDir::new("unix_newlines_from_real_file").unwrap();
161+
let path = tmpdir.path().join("test.rs");
162+
fs::write(&path, raw_input_text).unwrap();
163+
164+
let path = FileName::Real(path);
165+
166+
let mut out = String::from(formatted_text);
167+
apply_newline_style(NewlineStyle::Auto, &path, &mut out, raw_input_text);
132168
assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
133169
}
134170

@@ -138,17 +174,81 @@ mod tests {
138174
let raw_input_text = "One\r\nTwo\r\nThree";
139175

140176
let mut out = String::from(formatted_text);
141-
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
177+
apply_newline_style(
178+
NewlineStyle::Auto,
179+
&FileName::Stdin,
180+
&mut out,
181+
raw_input_text,
182+
);
142183
assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
143184
}
144185

186+
#[test]
187+
fn auto_detects_and_applies_windows_newlines_from_real_file() {
188+
let formatted_text = "One\nTwo\nThree";
189+
let raw_input_text = "One\r\nTwo\r\nThree";
190+
191+
let tmpdir = TempDir::new("windows_newlines_from_real_file").unwrap();
192+
let path = tmpdir.path().join("test.rs");
193+
fs::write(&path, raw_input_text).unwrap();
194+
195+
let path = FileName::Real(path);
196+
197+
let mut out = String::from(formatted_text);
198+
apply_newline_style(NewlineStyle::Auto, &path, &mut out, raw_input_text);
199+
assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
200+
}
201+
202+
#[test]
203+
fn auto_detect_and_applies_windows_newlines_if_windows_newlines_read_from_file() {
204+
// Even if the `raw_input_text` does not contain crlf chars, we should still apply
205+
// Them if we can read crlf from the input `FileName::Real(path)`.
206+
// see issue https://github.com/rust-lang/rustfmt/issues/4097
207+
let text = "One\nTwo\nThree";
208+
209+
let tmpdir = TempDir::new("windows_newlines_if_windows_newlines_read_from_file").unwrap();
210+
let path = tmpdir.path().join("test.rs");
211+
fs::write(&path, "One\r\nTwo\r\nThree").unwrap();
212+
213+
let path = FileName::Real(path);
214+
215+
let mut out = String::from(text);
216+
// Note that the source file contains `crlf`, while the `raw_input_text` contains `lf`
217+
apply_newline_style(NewlineStyle::Auto, &path, &mut out, text);
218+
assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
219+
}
220+
221+
#[test]
222+
fn auto_detect_and_applies_unix_newlines_if_unix_newlines_read_from_file() {
223+
// This following test is unlikely to happen in practice, but it is meant to illustrate
224+
// that line endings found in the source files take precedence over the `raw_input_text`
225+
// passed to apply_newline_style.
226+
let text = "One\r\nTwo\r\nThree";
227+
228+
let tmpdir = TempDir::new("unix_newlines_if_unix_newlines_read_from_file").unwrap();
229+
let path = tmpdir.path().join("test.rs");
230+
fs::write(&path, "One\nTwo\nThree").unwrap();
231+
232+
let path = FileName::Real(path);
233+
234+
let mut out = String::from(text);
235+
// Note that the source file contains `lf`, while the `raw_input_text` contains `crlf`
236+
apply_newline_style(NewlineStyle::Auto, &path, &mut out, text);
237+
assert_eq!("One\nTwo\nThree", &out, "auto should detect 'crlf'");
238+
}
239+
145240
#[test]
146241
fn auto_detects_and_applies_native_newlines() {
147242
let formatted_text = "One\nTwo\nThree";
148243
let raw_input_text = "One Two Three";
149244

150245
let mut out = String::from(formatted_text);
151-
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
246+
apply_newline_style(
247+
NewlineStyle::Auto,
248+
&FileName::Stdin,
249+
&mut out,
250+
raw_input_text,
251+
);
152252

153253
if cfg!(windows) {
154254
assert_eq!(
@@ -244,7 +344,7 @@ mod tests {
244344
newline_style: NewlineStyle,
245345
) {
246346
let mut out = String::from(input);
247-
apply_newline_style(newline_style, &mut out, input);
347+
apply_newline_style(newline_style, &FileName::Stdin, &mut out, input);
248348
assert_eq!(expected, &out);
249349
}
250350
}

0 commit comments

Comments
 (0)