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

Commit e3b06b0

Browse files
authored
Merge pull request #185 from ehuss/fix-edge-cases
Fix some panics in edge cases.
2 parents 9986e9a + df8401b commit e3b06b0

File tree

7 files changed

+107
-27
lines changed

7 files changed

+107
-27
lines changed

src/lib.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
108108
let text_slice = span.text[0].text.chars().collect::<Vec<char>>();
109109

110110
// We subtract `1` because these highlights are 1-based
111-
let start = span.text[0].highlight_start - 1;
112-
let end = span.text[0].highlight_end - 1;
111+
// Check the `min` so that it doesn't attempt to index out-of-bounds when
112+
// the span points to the "end" of the line. For example, a line of
113+
// "foo\n" with a highlight_start of 5 is intended to highlight *after*
114+
// the line. This needs to compensate since the newline has been removed
115+
// from the text slice.
116+
let start = (span.text[0].highlight_start - 1).min(text_slice.len());
117+
let end = (span.text[0].highlight_end - 1).min(text_slice.len());
113118
let lead = text_slice[indent..start].iter().collect();
114119
let mut body: String = text_slice[start..end].iter().collect();
115120

@@ -122,7 +127,8 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
122127

123128
// If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
124129
// bounds' access by making sure the index is within the array bounds.
125-
let last_tail_index = last.highlight_end.min(last.text.len()) - 1;
130+
// `saturating_sub` is used in case of an empty file
131+
let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1);
126132
let last_slice = last.text.chars().collect::<Vec<char>>();
127133

128134
if span.text.len() > 1 {

tests/edge-cases/empty.json

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"message": "`main` function not found in crate `empty`",
3+
"code": {
4+
"code": "E0601",
5+
"explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
6+
},
7+
"level": "error",
8+
"spans": [
9+
{
10+
"file_name": "empty.rs",
11+
"byte_start": 0,
12+
"byte_end": 0,
13+
"line_start": 0,
14+
"line_end": 0,
15+
"column_start": 1,
16+
"column_end": 1,
17+
"is_primary": true,
18+
"text": [
19+
{
20+
"text": "",
21+
"highlight_start": 1,
22+
"highlight_end": 1
23+
}
24+
],
25+
"label": null,
26+
"suggested_replacement": null,
27+
"suggestion_applicability": null,
28+
"expansion": null
29+
}
30+
],
31+
"children": [
32+
{
33+
"message": "consider adding a `main` function to `empty.rs`",
34+
"code": null,
35+
"level": "note",
36+
"spans": [],
37+
"children": [],
38+
"rendered": null
39+
}
40+
],
41+
"rendered": "error[E0601]: `main` function not found in crate `empty`\n |\n = note: consider adding a `main` function to `empty.rs`\n\n"
42+
}

tests/edge-cases/empty.rs

Whitespace-only changes.

tests/edge-cases/no_main.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"message": "`main` function not found in crate `no_main`",
3+
"code": {
4+
"code": "E0601",
5+
"explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
6+
},
7+
"level": "error",
8+
"spans": [
9+
{
10+
"file_name": "no_main.rs",
11+
"byte_start": 26,
12+
"byte_end": 26,
13+
"line_start": 1,
14+
"line_end": 1,
15+
"column_start": 27,
16+
"column_end": 27,
17+
"is_primary": true,
18+
"text": [
19+
{
20+
"text": "// This file has no main.",
21+
"highlight_start": 27,
22+
"highlight_end": 27
23+
}
24+
],
25+
"label": "consider adding a `main` function to `no_main.rs`",
26+
"suggested_replacement": null,
27+
"suggestion_applicability": null,
28+
"expansion": null
29+
}
30+
],
31+
"children": [],
32+
"rendered": "error[E0601]: `main` function not found in crate `no_main`\n --> no_main.rs:1:27\n |\n1 | // This file has no main.\n | ^ consider adding a `main` function to `no_main.rs`\n\n"
33+
}

tests/edge-cases/no_main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// This file has no main.

tests/edge_cases.rs

+18-23
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,24 @@ use rustfix;
22
use std::collections::HashSet;
33
use std::fs;
44

5-
#[test]
6-
fn multiple_fix_options_yield_no_suggestions() {
7-
let json = fs::read_to_string("./tests/edge-cases/skip-multi-option-lints.json").unwrap();
8-
let expected_suggestions =
9-
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
5+
macro_rules! expect_empty_json_test {
6+
($name:ident, $file:expr) => {
7+
#[test]
8+
fn $name() {
9+
let json = fs::read_to_string(concat!("./tests/edge-cases/", $file)).unwrap();
10+
let expected_suggestions = rustfix::get_suggestions_from_json(
11+
&json,
12+
&HashSet::new(),
13+
rustfix::Filter::Everything,
14+
)
1015
.unwrap();
11-
assert!(expected_suggestions.is_empty());
16+
assert!(expected_suggestions.is_empty());
17+
}
18+
};
1219
}
1320

14-
#[test]
15-
fn out_of_bounds_test() {
16-
let json = fs::read_to_string("./tests/edge-cases/out_of_bounds.recorded.json").unwrap();
17-
let expected_suggestions =
18-
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
19-
.unwrap();
20-
assert!(expected_suggestions.is_empty());
21-
}
22-
23-
#[test]
24-
fn utf8_identifiers_test() {
25-
let json = fs::read_to_string("./tests/edge-cases/utf8_idents.recorded.json").unwrap();
26-
let expected_suggestions =
27-
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
28-
.unwrap();
29-
assert!(expected_suggestions.is_empty());
30-
}
21+
expect_empty_json_test! {multiple_fix_options_yield_no_suggestions, "skip-multi-option-lints.json"}
22+
expect_empty_json_test! {out_of_bounds_test, "out_of_bounds.recorded.json"}
23+
expect_empty_json_test! {utf8_identifiers_test, "utf8_idents.recorded.json"}
24+
expect_empty_json_test! {empty, "empty.json"}
25+
expect_empty_json_test! {no_main, "no_main.json"}

tests/parse_and_replace.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ fn compile(file: &Path, mode: &str) -> Result<Output, Error> {
5454
fn compile_and_get_json_errors(file: &Path, mode: &str) -> Result<String, Error> {
5555
let res = compile(file, mode)?;
5656
let stderr = String::from_utf8(res.stderr)?;
57+
if stderr.contains("is only accepted on the nightly compiler") {
58+
panic!("rustfix tests require a nightly compiler");
59+
}
5760

5861
match res.status.code() {
5962
Some(0) | Some(1) | Some(101) => Ok(stderr),
@@ -160,7 +163,7 @@ fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) -> Result<(), Err
160163
))?;
161164
let expected_suggestions =
162165
rustfix::get_suggestions_from_json(&expected_json, &HashSet::new(), filter_suggestions)
163-
.context("could not load expected suggesitons")?;
166+
.context("could not load expected suggestions")?;
164167

165168
ensure!(
166169
expected_suggestions == suggestions,

0 commit comments

Comments
 (0)