Skip to content

Commit a21997f

Browse files
committed
rustfix: Support inserting new lines.
If rustfix received a suggestion which inserts new lines without replacing existing lines, it would ignore the suggestion. This is because `parse_snippet` would immediately return if the `lines` to replace was empty. The solution here is to just drop the code which messes with the original text line. `cargo fix` (and compiletest) currently do not use this. This was originally added back in the days when rustfix supported an interactive UI which showed color highlighting of what it looks like with the replacement. My feeling is that when we add something like this back in, I would prefer to instead use a real diff library and display instead of trying to do various text manipulation for display. This particular code has generally been buggy, and has been a problem several times. The included test fails without this fix because the changes do not apply, and the code cannot compile.
1 parent 029fe2b commit a21997f

File tree

7 files changed

+28
-63
lines changed

7 files changed

+28
-63
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pulldown-cmark = { version = "0.9.3", default-features = false }
7777
rand = "0.8.5"
7878
regex = "1.10.2"
7979
rusqlite = { version = "0.30.0", features = ["bundled"] }
80-
rustfix = { version = "0.7.0", path = "crates/rustfix" }
80+
rustfix = { version = "0.8.0", path = "crates/rustfix" }
8181
same-file = "1.0.6"
8282
security-framework = "2.9.2"
8383
semver = { version = "1.0.20", features = ["serde"] }

crates/rustfix/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rustfix"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
authors = [
55
"Pascal Hertleif <killercup@gmail.com>",
66
"Oliver Schneider <oli-obk@users.noreply.github.com>",

crates/rustfix/src/lib.rs

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ pub struct Snippet {
104104
pub file_name: String,
105105
pub line_range: LineRange,
106106
pub range: Range<usize>,
107-
/// leading surrounding text, text to replace, trailing surrounding text
108-
///
109-
/// This split is useful for highlighting the part that gets replaced
110-
pub text: (String, String, String),
111107
}
112108

113109
/// Represents a replacement of a `snippet`.
@@ -119,58 +115,9 @@ pub struct Replacement {
119115
pub replacement: String,
120116
}
121117

122-
/// Parses a [`Snippet`] from a diagnostic span item.
123-
fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
124-
// unindent the snippet
125-
let indent = span
126-
.text
127-
.iter()
128-
.map(|line| {
129-
let indent = line
130-
.text
131-
.chars()
132-
.take_while(|&c| char::is_whitespace(c))
133-
.count();
134-
std::cmp::min(indent, line.highlight_start - 1)
135-
})
136-
.min()?;
137-
138-
let text_slice = span.text[0].text.chars().collect::<Vec<char>>();
139-
140-
// We subtract `1` because these highlights are 1-based
141-
// Check the `min` so that it doesn't attempt to index out-of-bounds when
142-
// the span points to the "end" of the line. For example, a line of
143-
// "foo\n" with a highlight_start of 5 is intended to highlight *after*
144-
// the line. This needs to compensate since the newline has been removed
145-
// from the text slice.
146-
let start = (span.text[0].highlight_start - 1).min(text_slice.len());
147-
let end = (span.text[0].highlight_end - 1).min(text_slice.len());
148-
let lead = text_slice[indent..start].iter().collect();
149-
let mut body: String = text_slice[start..end].iter().collect();
150-
151-
for line in span.text.iter().take(span.text.len() - 1).skip(1) {
152-
body.push('\n');
153-
body.push_str(&line.text[indent..]);
154-
}
155-
let mut tail = String::new();
156-
let last = &span.text[span.text.len() - 1];
157-
158-
// If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
159-
// bounds' access by making sure the index is within the array bounds.
160-
// `saturating_sub` is used in case of an empty file
161-
let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1);
162-
let last_slice = last.text.chars().collect::<Vec<char>>();
163-
164-
if span.text.len() > 1 {
165-
body.push('\n');
166-
body.push_str(
167-
&last_slice[indent..last_tail_index]
168-
.iter()
169-
.collect::<String>(),
170-
);
171-
}
172-
tail.push_str(&last_slice[last_tail_index..].iter().collect::<String>());
173-
Some(Snippet {
118+
/// Converts a [`DiagnosticSpan`] to a [`Snippet`].
119+
fn span_to_snippet(span: &DiagnosticSpan) -> Snippet {
120+
Snippet {
174121
file_name: span.file_name.clone(),
175122
line_range: LineRange {
176123
start: LinePosition {
@@ -183,13 +130,12 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
183130
},
184131
},
185132
range: (span.byte_start as usize)..(span.byte_end as usize),
186-
text: (lead, body, tail),
187-
})
133+
}
188134
}
189135

190136
/// Converts a [`DiagnosticSpan`] into a [`Replacement`].
191137
fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
192-
let snippet = parse_snippet(span)?;
138+
let snippet = span_to_snippet(span);
193139
let replacement = span.suggested_replacement.clone()?;
194140
Some(Replacement {
195141
snippet,
@@ -217,7 +163,7 @@ pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
217163
}
218164
}
219165

220-
let snippets = diagnostic.spans.iter().filter_map(parse_snippet).collect();
166+
let snippets = diagnostic.spans.iter().map(span_to_snippet).collect();
221167

222168
let solutions: Vec<_> = diagnostic
223169
.children
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use a::f;
2+
3+
mod a {
4+
pub fn f() {}
5+
}
6+
7+
fn main() {
8+
f();
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"$message_type":"diagnostic","message":"cannot find function `f` in this scope","code":{"code":"E0425","explanation":"An unresolved name was used.\n\nErroneous code examples:\n\n```compile_fail,E0425\nsomething_that_doesnt_exist::foo;\n// error: unresolved name `something_that_doesnt_exist::foo`\n\n// or:\n\ntrait Foo {\n fn bar() {\n Self; // error: unresolved name `Self`\n }\n}\n\n// or:\n\nlet x = unknown_variable; // error: unresolved name `unknown_variable`\n```\n\nPlease verify that the name wasn't misspelled and ensure that the\nidentifier being referred to is valid for the given situation. Example:\n\n```\nenum something_that_does_exist {\n Foo,\n}\n```\n\nOr:\n\n```\nmod something_that_does_exist {\n pub static foo : i32 = 0i32;\n}\n\nsomething_that_does_exist::foo; // ok!\n```\n\nOr:\n\n```\nlet unknown_variable = 12u32;\nlet x = unknown_variable; // ok!\n```\n\nIf the item is not defined in the current module, it must be imported using a\n`use` statement, like so:\n\n```\n# mod foo { pub fn bar() {} }\n# fn main() {\nuse foo::bar;\nbar();\n# }\n```\n\nIf the item you are importing is not defined in some super-module of the\ncurrent module, then it must also be declared as public (e.g., `pub fn`).\n"},"level":"error","spans":[{"file_name":"./tests/everything/use-insert.rs","byte_start":45,"byte_end":46,"line_start":6,"line_end":6,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":" f();","highlight_start":5,"highlight_end":6}],"label":"not found in this scope","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider importing this function","code":null,"level":"help","spans":[{"file_name":"./tests/everything/use-insert.rs","byte_start":0,"byte_end":0,"line_start":1,"line_end":1,"column_start":1,"column_end":1,"is_primary":true,"text":[],"label":null,"suggested_replacement":"use a::f;\n\n","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"error[E0425]: cannot find function `f` in this scope\n --> ./tests/everything/use-insert.rs:6:5\n |\n6 | f();\n | ^ not found in this scope\n |\nhelp: consider importing this function\n |\n1 + use a::f;\n |\n\n"}
2+
{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 1 previous error\n\n"}
3+
{"$message_type":"diagnostic","message":"For more information about this error, try `rustc --explain E0425`.","code":null,"level":"failure-note","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0425`.\n"}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod a {
2+
pub fn f() {}
3+
}
4+
5+
fn main() {
6+
f();
7+
}

0 commit comments

Comments
 (0)