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.
This commit is contained in:
Brent Westbrook 2025-10-21 13:47:26 -04:00 committed by GitHub
parent 2e13b13012
commit 4b0fa5f270
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 40 additions and 16 deletions

View file

@ -879,19 +879,7 @@ impl From<&FormatCommandError> for Diagnostic {
| FormatCommandError::Write(_, source_error) => { | FormatCommandError::Write(_, source_error) => {
Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error) Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error)
} }
FormatCommandError::Format(_, format_module_error) => match format_module_error { FormatCommandError::Format(_, format_module_error) => format_module_error.into(),
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::RangeFormatNotebook(_) => Diagnostic::new( FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new(
DiagnosticId::InvalidCliOption, DiagnosticId::InvalidCliOption,
Severity::Error, Severity::Error,

View file

@ -1,3 +1,4 @@
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::parsed::parsed_module; use ruff_db::parsed::parsed_module;
use ruff_db::source::source_text; 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_ast::{AnyNodeRef, Mod};
use ruff_python_parser::{ParseError, ParseOptions, Parsed, parse}; use ruff_python_parser::{ParseError, ParseOptions, Parsed, parse};
use ruff_python_trivia::CommentRanges; use ruff_python_trivia::CommentRanges;
use ruff_text_size::Ranged; use ruff_text_size::{Ranged, TextRange};
use crate::comments::{ use crate::comments::{
Comments, SourceComment, has_skip_comment, leading_comments, trailing_comments, Comments, SourceComment, has_skip_comment, leading_comments, trailing_comments,
@ -117,6 +118,33 @@ pub enum FormatModuleError {
PrintError(#[from] PrintError), PrintError(#[from] PrintError),
} }
impl FormatModuleError {
pub fn range(&self) -> Option<TextRange> {
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)] #[tracing::instrument(name = "format", level = Level::TRACE, skip_all)]
pub fn format_module_source( pub fn format_module_source(
source: &str, source: &str,

View file

@ -404,10 +404,18 @@ fn ensure_stability_when_formatting_twice(
let reformatted = match format_module_source(formatted_code, options.clone()) { let reformatted = match format_module_source(formatted_code, options.clone()) {
Ok(reformatted) => reformatted, Ok(reformatted) => reformatted,
Err(err) => { 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!( panic!(
"Expected formatted code of {} to be valid syntax: {err}:\ "Expected formatted code of {} to be valid syntax: {err}:\
\n---\n{formatted_code}---\n", \n---\n{formatted_code}---\n{}",
input_path.display() input_path.display(),
diag.display(&DummyFileResolver, &DisplayDiagnosticConfig::default()),
); );
} }
}; };