diff --git a/src/format_report_formatter.rs b/src/format_report_formatter.rs index 08889c712a5..4d77ec9fa2c 100644 --- a/src/format_report_formatter.rs +++ b/src/format_report_formatter.rs @@ -106,7 +106,7 @@ fn annotation(error: &FormattingError) -> Option> { fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level { match error_kind { - ErrorKind::LineOverflow(..) + ErrorKind::LineOverflow { .. } | ErrorKind::TrailingWhitespace | ErrorKind::IoError(_) | ErrorKind::ModuleResolutionError(_) diff --git a/src/formatting.rs b/src/formatting.rs index 62876841813..38238e2226c 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -333,7 +333,7 @@ impl FormattingError { pub(crate) fn is_internal(&self) -> bool { match self.kind { - ErrorKind::LineOverflow(..) + ErrorKind::LineOverflow { .. } | ErrorKind::TrailingWhitespace | ErrorKind::IoError(_) | ErrorKind::ParseError @@ -354,7 +354,13 @@ impl FormattingError { // (space, target) pub(crate) fn format_len(&self) -> (usize, usize) { match self.kind { - ErrorKind::LineOverflow(found, max) => (max, found - max), + ErrorKind::LineOverflow { + overflow_start_byte, + .. + } => ( + overflow_start_byte, + self.line_buffer.len() - overflow_start_byte, + ), ErrorKind::TrailingWhitespace | ErrorKind::DeprecatedAttr | ErrorKind::BadAttr @@ -564,8 +570,13 @@ impl<'a> FormatLines<'a> { } // Check for any line width errors we couldn't correct. - let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width()); - if self.line_len > self.config.max_width() + let max_width = self.config.max_width(); + let error_kind = ErrorKind::LineOverflow { + total_line_width: self.line_len, + max_width, + overflow_start_byte: self.byte_offset_at_col(max_width), + }; + if self.line_len > max_width && !self.is_skipped_line() && self.should_report_error(kind, &error_kind) { @@ -600,6 +611,22 @@ impl<'a> FormatLines<'a> { } } + /// Inverse of `Self::char`'s column accounting: walk `line_buffer` with + /// the same rule (tab = `tab_spaces` cols, every other char = 1 col) and + /// return the byte offset where the accumulated column count first reaches + /// `target_col`. Returns `line_buffer.len()` if the line is shorter. + fn byte_offset_at_col(&self, target_col: usize) -> usize { + let tab_spaces = self.config.tab_spaces(); + let mut col = 0; + for (idx, ch) in self.line_buffer.char_indices() { + if col >= target_col { + return idx; + } + col += if ch == '\t' { tab_spaces } else { 1 }; + } + self.line_buffer.len() + } + fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) { self.errors.push(FormattingError { line: self.cur_line, @@ -621,7 +648,7 @@ impl<'a> FormatLines<'a> { }; match error_kind { - ErrorKind::LineOverflow(..) => { + ErrorKind::LineOverflow { .. } => { self.config.error_on_line_overflow() && allow_error_report } ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report, diff --git a/src/lib.rs b/src/lib.rs index d3379a4564b..bc551fccdf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,12 +106,16 @@ pub(crate) mod visitor; /// these can currently be propagated to clients. #[derive(Error, Debug)] pub enum ErrorKind { - /// Line has exceeded character limit (found, maximum). + /// A line exceeded the configured maximum width. #[error( "line formatted, but exceeded maximum width \ - (maximum: {1} (see `max_width` option), found: {0})" + (maximum: {max_width} (see `max_width` option), found: {total_line_width})" )] - LineOverflow(usize, usize), + LineOverflow { + total_line_width: usize, + max_width: usize, + overflow_start_byte: usize, + }, /// Line ends in whitespace. #[error("left behind trailing whitespace")] TrailingWhitespace, @@ -225,7 +229,7 @@ impl FormatReport { } for err in new_errors { match err.kind { - ErrorKind::LineOverflow(..) => { + ErrorKind::LineOverflow { .. } => { errs.has_operational_errors = true; } ErrorKind::TrailingWhitespace => { diff --git a/tests/cargo-fmt/source/issue_6850/Cargo.toml b/tests/cargo-fmt/source/issue_6850/Cargo.toml new file mode 100644 index 00000000000..cdd951d3a2a --- /dev/null +++ b/tests/cargo-fmt/source/issue_6850/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "issue_6850" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/cargo-fmt/source/issue_6850/src/main.rs b/tests/cargo-fmt/source/issue_6850/src/main.rs new file mode 100644 index 00000000000..d9c02ff351d --- /dev/null +++ b/tests/cargo-fmt/source/issue_6850/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃" +} diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 4008c8d1435..6fe9993b26b 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -203,6 +203,27 @@ fn dont_emit_ICE() { } } +#[test] +fn dont_panic_on_line_overflow_with_multibyte_chars() { + // See also https://github.com/rust-lang/rustfmt/issues/6850 + let args = [ + "--config", + "error_on_line_overflow=true,error_on_unformatted=true", + "tests/cargo-fmt/source/issue_6850/src/main.rs", + ]; + + let (_stdout, stderr) = rustfmt(&args); + assert!(stderr.contains( + "line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 126)" + )); + + let panic_re = regex::Regex::new("thread.*panicked").unwrap(); + assert!( + !panic_re.is_match(&stderr), + "rustfmt panicked instead of reporting line overflow:\n{stderr}" + ); +} + #[test] fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() { // See also https://github.com/rust-lang/rustfmt/issues/5912