Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Commit 18f0217

Browse files
authored
Merge pull request #67 from rust-lang-nursery/cool-replacements
Use a more clever way to replace parts of a file
2 parents 82fb067 + f9b8801 commit 18f0217

File tree

9 files changed

+306
-54
lines changed

9 files changed

+306
-54
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ rust:
33
- nightly
44
script:
55
- cargo test --all
6+
- cargo test --all -- --ignored
67
notifications:
78
email:
89
on_success: never

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ exclude = [
2020
serde = "1.0"
2121
serde_json = "1.0"
2222
serde_derive = "1.0"
23+
failure = "0.1.1"
2324

2425
[dev-dependencies]
2526
duct = "0.8.2"
2627
env_logger = "0.5.0-rc.1"
2728
log = "0.4.1"
2829
pretty_assertions = "0.4.1"
2930
tempdir = "0.3.5"
31+
proptest = "0.7.0"
3032

3133
[workspace]
3234
members = [

cargo-fix/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ fn rustfix_crate(rustc: &Path, filename: &str) -> Result<(), Error> {
177177
}
178178
debug!("applying {} fixes to {}", suggestions.len(), file);
179179

180-
let new_code = rustfix::apply_suggestions(&code, &suggestions);
180+
let new_code = rustfix::apply_suggestions(&code, &suggestions)?;
181181
File::create(&file)
182182
.and_then(|mut f| f.write_all(new_code.as_bytes()))
183183
.with_context(|_| format!("failed to write file `{}`", file))?;

cargo-fix/tests/all/main.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ extern crate difference;
22
extern crate url;
33

44
use std::env;
5-
use std::fs;
6-
use std::io::Write;
5+
use std::fs::{self, File};
6+
use std::io::{Read, Write};
77
use std::path::{Path, PathBuf};
88
use std::process::Command;
99
use std::str;
@@ -84,6 +84,15 @@ impl Project {
8484
cwd: None,
8585
}
8686
}
87+
88+
fn read(&self, path: &str) -> String {
89+
let mut ret = String::new();
90+
File::open(self.root.join(path))
91+
.unwrap()
92+
.read_to_string(&mut ret)
93+
.unwrap();
94+
return ret
95+
}
8796
}
8897

8998
struct ExpectCmd<'a> {

cargo-fix/tests/all/smoke.rs

+41
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,44 @@ fn tricky_ampersand() {
8787
.stderr(stderr)
8888
.run();
8989
}
90+
91+
#[test]
92+
fn preserve_line_endings() {
93+
let p = project()
94+
.file("src/lib.rs", "\
95+
fn add(a: &u32) -> u32 { a + 1 }\r\n\
96+
pub fn foo() -> u32 { add(1) }\r\n\
97+
")
98+
.build();
99+
100+
p.expect_cmd("cargo fix").run();
101+
assert!(p.read("src/lib.rs").contains("\r\n"));
102+
}
103+
104+
#[test]
105+
fn multiple_suggestions_for_the_same_thing() {
106+
let p = project()
107+
.file("src/lib.rs", "\
108+
fn main() {
109+
let xs = vec![String::from(\"foo\")];
110+
// error: no diplay in scope, and there are two options
111+
// (std::path::Display and std::fmt::Display)
112+
let d: &Display = &xs;
113+
println!(\"{}\", d);
114+
}
115+
")
116+
.build();
117+
118+
let stderr = "\
119+
[CHECKING] foo v0.1.0 (CWD)
120+
error: Cannot replace slice of data that was already replaced
121+
error: Could not compile `foo`.
122+
123+
To learn more, run the command again with --verbose.
124+
";
125+
126+
p.expect_cmd("cargo fix")
127+
.stderr(stderr)
128+
.status(101)
129+
.run();
130+
}

src/diagnostics.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Rustc Diagnostic JSON Output
2-
//!
2+
//!
33
//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/de78655bca47cac8e783dbb563e7e5c25c1fae40/src/libsyntax/json.rs)
44
55
#[derive(Deserialize, Debug)]
@@ -21,8 +21,8 @@ pub struct Diagnostic {
2121
#[derive(Deserialize, Debug)]
2222
pub struct DiagnosticSpan {
2323
pub file_name: String,
24-
byte_start: u32,
25-
byte_end: u32,
24+
pub byte_start: u32,
25+
pub byte_end: u32,
2626
/// 1-based.
2727
pub line_start: usize,
2828
pub line_end: usize,

src/lib.rs

+20-45
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
#[macro_use]
22
extern crate serde_derive;
33
extern crate serde_json;
4+
#[macro_use]
5+
extern crate failure;
6+
#[cfg(test)]
7+
#[macro_use]
8+
extern crate proptest;
49

510
use std::collections::HashSet;
11+
use std::ops::Range;
12+
13+
use failure::Error;
614

715
pub mod diagnostics;
816
use diagnostics::{Diagnostic, DiagnosticSpan};
17+
mod replace;
918

1019
pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
1120
input: &str,
@@ -61,6 +70,7 @@ pub struct Solution {
6170
pub struct Snippet {
6271
pub file_name: String,
6372
pub line_range: LineRange,
73+
pub range: Range<usize>,
6474
/// leading surrounding text, text to replace, trailing surrounding text
6575
///
6676
/// This split is useful for higlighting the part that gets replaced
@@ -109,6 +119,7 @@ fn parse_snippet(span: &DiagnosticSpan) -> Snippet {
109119
column: span.column_end,
110120
},
111121
},
122+
range: (span.byte_start as usize)..(span.byte_end as usize),
112123
text: (lead, body, tail),
113124
}
114125
}
@@ -166,58 +177,22 @@ pub fn collect_suggestions<S: ::std::hash::BuildHasher>(diagnostic: &Diagnostic,
166177
}
167178
}
168179

169-
pub fn apply_suggestion(file_content: &mut String, suggestion: &Replacement) -> String {
170-
use std::cmp::max;
171-
172-
let mut new_content = String::new();
173-
174-
// Add the lines before the section we want to replace
175-
new_content.push_str(&file_content.lines()
176-
.take(max(suggestion.snippet.line_range.start.line - 1, 0) as usize)
177-
.collect::<Vec<_>>()
178-
.join("\n"));
179-
new_content.push_str("\n");
180-
181-
// Parts of line before replacement
182-
new_content.push_str(&file_content.lines()
183-
.nth(suggestion.snippet.line_range.start.line - 1)
184-
.unwrap_or("")
185-
.chars()
186-
.take(suggestion.snippet.line_range.start.column - 1)
187-
.collect::<String>());
188-
189-
// Insert new content! Finally!
190-
new_content.push_str(&suggestion.replacement);
191-
192-
// Parts of line after replacement
193-
new_content.push_str(&file_content.lines()
194-
.nth(suggestion.snippet.line_range.end.line - 1)
195-
.unwrap_or("")
196-
.chars()
197-
.skip(suggestion.snippet.line_range.end.column - 1)
198-
.collect::<String>());
199-
200-
// Add the lines after the section we want to replace
201-
new_content.push_str("\n");
202-
new_content.push_str(&file_content.lines()
203-
.skip(suggestion.snippet.line_range.end.line as usize)
204-
.collect::<Vec<_>>()
205-
.join("\n"));
206-
new_content.push_str("\n");
207-
208-
new_content
209-
}
180+
pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> {
181+
use replace::Data;
210182

211-
pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> String {
212-
let mut fixed = code.to_string();
183+
let mut fixed = Data::new(code.as_bytes());
213184

214185
for sug in suggestions.iter().rev() {
215186
for sol in &sug.solutions {
216187
for r in &sol.replacements {
217-
fixed = apply_suggestion(&mut fixed, r);
188+
fixed.replace_range(
189+
r.snippet.range.start,
190+
r.snippet.range.end.saturating_sub(1),
191+
r.replacement.as_bytes(),
192+
)?;
218193
}
219194
}
220195
}
221196

222-
fixed
197+
Ok(String::from_utf8(fixed.to_vec())?)
223198
}

0 commit comments

Comments
 (0)