diff --git a/src/lib.rs b/src/lib.rs index 8026f00..8304047 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,8 +108,13 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option { let text_slice = span.text[0].text.chars().collect::>(); // We subtract `1` because these highlights are 1-based - let start = span.text[0].highlight_start - 1; - let end = span.text[0].highlight_end - 1; + // Check the `min` so that it doesn't attempt to index out-of-bounds when + // the span points to the "end" of the line. For example, a line of + // "foo\n" with a highlight_start of 5 is intended to highlight *after* + // the line. This needs to compensate since the newline has been removed + // from the text slice. + let start = (span.text[0].highlight_start - 1).min(text_slice.len()); + let end = (span.text[0].highlight_end - 1).min(text_slice.len()); let lead = text_slice[indent..start].iter().collect(); let mut body: String = text_slice[start..end].iter().collect(); @@ -122,7 +127,8 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option { // If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of // bounds' access by making sure the index is within the array bounds. - let last_tail_index = last.highlight_end.min(last.text.len()) - 1; + // `saturating_sub` is used in case of an empty file + let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1); let last_slice = last.text.chars().collect::>(); if span.text.len() > 1 { diff --git a/tests/edge-cases/empty.json b/tests/edge-cases/empty.json new file mode 100644 index 0000000..62df0b9 --- /dev/null +++ b/tests/edge-cases/empty.json @@ -0,0 +1,42 @@ +{ + "message": "`main` function not found in crate `empty`", + "code": { + "code": "E0601", + "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" + }, + "level": "error", + "spans": [ + { + "file_name": "empty.rs", + "byte_start": 0, + "byte_end": 0, + "line_start": 0, + "line_end": 0, + "column_start": 1, + "column_end": 1, + "is_primary": true, + "text": [ + { + "text": "", + "highlight_start": 1, + "highlight_end": 1 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "consider adding a `main` function to `empty.rs`", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + } + ], + "rendered": "error[E0601]: `main` function not found in crate `empty`\n |\n = note: consider adding a `main` function to `empty.rs`\n\n" +} diff --git a/tests/edge-cases/empty.rs b/tests/edge-cases/empty.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/edge-cases/no_main.json b/tests/edge-cases/no_main.json new file mode 100644 index 0000000..e4b1c8f --- /dev/null +++ b/tests/edge-cases/no_main.json @@ -0,0 +1,33 @@ +{ + "message": "`main` function not found in crate `no_main`", + "code": { + "code": "E0601", + "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" + }, + "level": "error", + "spans": [ + { + "file_name": "no_main.rs", + "byte_start": 26, + "byte_end": 26, + "line_start": 1, + "line_end": 1, + "column_start": 27, + "column_end": 27, + "is_primary": true, + "text": [ + { + "text": "// This file has no main.", + "highlight_start": 27, + "highlight_end": 27 + } + ], + "label": "consider adding a `main` function to `no_main.rs`", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "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" +} diff --git a/tests/edge-cases/no_main.rs b/tests/edge-cases/no_main.rs new file mode 100644 index 0000000..0147ba7 --- /dev/null +++ b/tests/edge-cases/no_main.rs @@ -0,0 +1 @@ +// This file has no main. diff --git a/tests/edge_cases.rs b/tests/edge_cases.rs index e1f380a..191713b 100644 --- a/tests/edge_cases.rs +++ b/tests/edge_cases.rs @@ -2,29 +2,24 @@ use rustfix; use std::collections::HashSet; use std::fs; -#[test] -fn multiple_fix_options_yield_no_suggestions() { - let json = fs::read_to_string("./tests/edge-cases/skip-multi-option-lints.json").unwrap(); - let expected_suggestions = - rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything) +macro_rules! expect_empty_json_test { + ($name:ident, $file:expr) => { + #[test] + fn $name() { + let json = fs::read_to_string(concat!("./tests/edge-cases/", $file)).unwrap(); + let expected_suggestions = rustfix::get_suggestions_from_json( + &json, + &HashSet::new(), + rustfix::Filter::Everything, + ) .unwrap(); - assert!(expected_suggestions.is_empty()); + assert!(expected_suggestions.is_empty()); + } + }; } -#[test] -fn out_of_bounds_test() { - let json = fs::read_to_string("./tests/edge-cases/out_of_bounds.recorded.json").unwrap(); - let expected_suggestions = - rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything) - .unwrap(); - assert!(expected_suggestions.is_empty()); -} - -#[test] -fn utf8_identifiers_test() { - let json = fs::read_to_string("./tests/edge-cases/utf8_idents.recorded.json").unwrap(); - let expected_suggestions = - rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything) - .unwrap(); - assert!(expected_suggestions.is_empty()); -} +expect_empty_json_test! {multiple_fix_options_yield_no_suggestions, "skip-multi-option-lints.json"} +expect_empty_json_test! {out_of_bounds_test, "out_of_bounds.recorded.json"} +expect_empty_json_test! {utf8_identifiers_test, "utf8_idents.recorded.json"} +expect_empty_json_test! {empty, "empty.json"} +expect_empty_json_test! {no_main, "no_main.json"} diff --git a/tests/parse_and_replace.rs b/tests/parse_and_replace.rs index c3f5701..1b6ffa9 100644 --- a/tests/parse_and_replace.rs +++ b/tests/parse_and_replace.rs @@ -54,6 +54,9 @@ fn compile(file: &Path, mode: &str) -> Result { fn compile_and_get_json_errors(file: &Path, mode: &str) -> Result { let res = compile(file, mode)?; let stderr = String::from_utf8(res.stderr)?; + if stderr.contains("is only accepted on the nightly compiler") { + panic!("rustfix tests require a nightly compiler"); + } match res.status.code() { Some(0) | Some(1) | Some(101) => Ok(stderr), @@ -160,7 +163,7 @@ fn test_rustfix_with_file>(file: P, mode: &str) -> Result<(), Err ))?; let expected_suggestions = rustfix::get_suggestions_from_json(&expected_json, &HashSet::new(), filter_suggestions) - .context("could not load expected suggesitons")?; + .context("could not load expected suggestions")?; ensure!( expected_suggestions == suggestions,