diff --git a/crates/ruff/src/logging.rs b/crates/ruff/src/logging.rs index 1607508ead203..7ed9974042eae 100644 --- a/crates/ruff/src/logging.rs +++ b/crates/ruff/src/logging.rs @@ -1,4 +1,4 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::{Display, Formatter, Write}; use std::path::Path; use std::sync::Mutex; @@ -9,7 +9,7 @@ use fern; use log::Level; use once_cell::sync::Lazy; use ruff_python_ast::source_code::SourceCode; -use rustpython_parser::ParseError; +use rustpython_parser::{ParseError, ParseErrorType}; pub(crate) static WARNINGS: Lazy>> = Lazy::new(Mutex::default); @@ -150,12 +150,84 @@ impl Display for DisplayParseError<'_> { write!( f, "{header} {path}{colon}{row}{colon}{column}{colon} {inner}", - header = "Failed to parse ".bold(), + header = "Failed to parse".bold(), path = fs::relativize_path(Path::new(&self.error.source_path)).bold(), row = source_location.row, column = source_location.column, colon = ":".cyan(), - inner = &self.error.error + inner = &DisplayParseErrorType(&self.error.error) + ) + } +} + +pub(crate) struct DisplayParseErrorType<'a>(&'a ParseErrorType); + +impl<'a> DisplayParseErrorType<'a> { + pub(crate) fn new(error: &'a ParseErrorType) -> Self { + Self(error) + } +} + +impl Display for DisplayParseErrorType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.0 { + ParseErrorType::Eof => write!(f, "Expected token but reached end of file."), + ParseErrorType::ExtraToken(ref tok) => write!( + f, + "Got extraneous token: {tok}", + tok = TruncateAtNewline(&tok) + ), + ParseErrorType::InvalidToken => write!(f, "Got invalid token"), + ParseErrorType::UnrecognizedToken(ref tok, ref expected) => { + if let Some(expected) = expected.as_ref() { + write!( + f, + "expected '{expected}', but got {tok}", + tok = TruncateAtNewline(&tok) + ) + } else { + write!(f, "unexpected token {tok}", tok = TruncateAtNewline(&tok)) + } + } + ParseErrorType::Lexical(ref error) => write!(f, "{error}"), + } + } +} + +/// Truncates the display text before the first newline character to avoid line breaks. +struct TruncateAtNewline<'a>(&'a dyn Display); + +impl Display for TruncateAtNewline<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + struct TruncateAdapter<'a> { + inner: &'a mut dyn std::fmt::Write, + after_new_line: bool, + } + + impl std::fmt::Write for TruncateAdapter<'_> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + if self.after_new_line { + Ok(()) + } else { + if let Some(end) = s.find(['\n', '\r']) { + self.inner.write_str(&s[..end])?; + self.inner.write_str("\u{23ce}...")?; + self.after_new_line = true; + Ok(()) + } else { + self.inner.write_str(s) + } + } + } + } + + write!( + TruncateAdapter { + inner: f, + after_new_line: false, + }, + "{}", + self.0 ) } } diff --git a/crates/ruff/src/rules/pycodestyle/rules/errors.rs b/crates/ruff/src/rules/pycodestyle/rules/errors.rs index 7816752162e2c..365ab8a4adfa6 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/errors.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/errors.rs @@ -1,6 +1,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; use rustpython_parser::ParseError; +use crate::logging::DisplayParseErrorType; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::source_code::Locator; @@ -50,7 +51,7 @@ pub fn syntax_error( diagnostics.push(Diagnostic::new( SyntaxError { - message: parse_error.error.to_string(), + message: format!("{}", DisplayParseErrorType::new(&parse_error.error)), }, TextRange::at(parse_error.location, len), ));