From 4b0fa5f2709b56b7c76b9cb864f250f54c1fe963 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:47:26 -0400 Subject: [PATCH] Render a diagnostic for syntax errors introduced in formatter tests (#21021) ## Summary I spun this out from #21005 because I thought it might be helpful separately. It just renders a nice `Diagnostic` for syntax errors pointing to the source of the error. This seemed a bit more helpful to me than just the byte offset when working on #21005, and we had most of the code around after #20443 anyway. ## Test Plan This doesn't actually affect any passing tests, but here's an example of the additional output I got when I broke the spacing after the `in` token: ``` error[internal-error]: Expected 'in', found name --> /home/brent/astral/ruff/crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py:50:79 | 48 | need_more_to_make_the_line_long_enough, 49 | ) 50 | del ([], name_1, name_2), [(), [], name_4, name_3], name_1[[name_2 for name_1 inname_0]] | ^^^^^^^^ 51 | del () | ``` I just appended this to the other existing output for now. --- crates/ruff/src/commands/format.rs | 14 +-------- crates/ruff_python_formatter/src/lib.rs | 30 ++++++++++++++++++- .../ruff_python_formatter/tests/fixtures.rs | 12 ++++++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index 20c000a89d..1f79e59339 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -879,19 +879,7 @@ impl From<&FormatCommandError> for Diagnostic { | FormatCommandError::Write(_, source_error) => { Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error) } - FormatCommandError::Format(_, format_module_error) => match format_module_error { - FormatModuleError::ParseError(parse_error) => Diagnostic::new( - DiagnosticId::InternalError, - Severity::Error, - &parse_error.error, - ), - FormatModuleError::FormatError(format_error) => { - Diagnostic::new(DiagnosticId::InternalError, Severity::Error, format_error) - } - FormatModuleError::PrintError(print_error) => { - Diagnostic::new(DiagnosticId::InternalError, Severity::Error, print_error) - } - }, + FormatCommandError::Format(_, format_module_error) => format_module_error.into(), FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new( DiagnosticId::InvalidCliOption, Severity::Error, diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 3dbef73807..e6b2f9e7b8 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -1,3 +1,4 @@ +use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity}; use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_db::source::source_text; @@ -10,7 +11,7 @@ use ruff_formatter::{FormatError, Formatted, PrintError, Printed, SourceCode, fo use ruff_python_ast::{AnyNodeRef, Mod}; use ruff_python_parser::{ParseError, ParseOptions, Parsed, parse}; use ruff_python_trivia::CommentRanges; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::comments::{ Comments, SourceComment, has_skip_comment, leading_comments, trailing_comments, @@ -117,6 +118,33 @@ pub enum FormatModuleError { PrintError(#[from] PrintError), } +impl FormatModuleError { + pub fn range(&self) -> Option { + match self { + FormatModuleError::ParseError(parse_error) => Some(parse_error.range()), + FormatModuleError::FormatError(_) | FormatModuleError::PrintError(_) => None, + } + } +} + +impl From<&FormatModuleError> for Diagnostic { + fn from(error: &FormatModuleError) -> Self { + match error { + FormatModuleError::ParseError(parse_error) => Diagnostic::new( + DiagnosticId::InternalError, + Severity::Error, + &parse_error.error, + ), + FormatModuleError::FormatError(format_error) => { + Diagnostic::new(DiagnosticId::InternalError, Severity::Error, format_error) + } + FormatModuleError::PrintError(print_error) => { + Diagnostic::new(DiagnosticId::InternalError, Severity::Error, print_error) + } + } + } +} + #[tracing::instrument(name = "format", level = Level::TRACE, skip_all)] pub fn format_module_source( source: &str, diff --git a/crates/ruff_python_formatter/tests/fixtures.rs b/crates/ruff_python_formatter/tests/fixtures.rs index 776b272d21..33741bd744 100644 --- a/crates/ruff_python_formatter/tests/fixtures.rs +++ b/crates/ruff_python_formatter/tests/fixtures.rs @@ -404,10 +404,18 @@ fn ensure_stability_when_formatting_twice( let reformatted = match format_module_source(formatted_code, options.clone()) { Ok(reformatted) => reformatted, Err(err) => { + let mut diag = Diagnostic::from(&err); + if let Some(range) = err.range() { + let file = + SourceFileBuilder::new(input_path.to_string_lossy(), formatted_code).finish(); + let span = Span::from(file).with_range(range); + diag.annotate(Annotation::primary(span)); + } panic!( "Expected formatted code of {} to be valid syntax: {err}:\ - \n---\n{formatted_code}---\n", - input_path.display() + \n---\n{formatted_code}---\n{}", + input_path.display(), + diag.display(&DummyFileResolver, &DisplayDiagnosticConfig::default()), ); } };