Make DisplayParseError an error type (#9325)

## Summary

This is a non-behavior-changing refactor to follow-up
https://github.com/astral-sh/ruff/pull/9321 by modifying
`DisplayParseError` to use owned data and make it useable as a
standalone error type (rather than using references and implementing
`Display`). I don't feel very strongly either way. I thought it was
awkward that the `FormatCommandError` had two branches in the display
path, and wanted to represent the `Parse` vs. other cases as a separate
enum, so here we are.
This commit is contained in:
Charlie Marsh 2023-12-31 11:46:29 -04:00 committed by GitHub
parent b3789cd9e9
commit da8a3af524
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 91 deletions

View file

@ -1,5 +1,5 @@
use std::fmt::{Display, Formatter, Write};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use anyhow::Result;
@ -9,7 +9,7 @@ use log::Level;
use once_cell::sync::Lazy;
use ruff_python_parser::{ParseError, ParseErrorType};
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
use crate::fs;
use crate::source_kind::SourceKind;
@ -138,32 +138,76 @@ pub fn set_up_logging(level: &LogLevel) -> Result<()> {
/// A wrapper around [`ParseError`] to translate byte offsets to user-facing
/// source code locations (typically, line and column numbers).
pub struct DisplayParseError<'a> {
error: &'a ParseError,
source_code: &'a SourceCode<'a, 'a>,
source_kind: &'a SourceKind,
path: Option<&'a Path>,
#[derive(Debug)]
pub struct DisplayParseError {
error: ParseError,
path: Option<PathBuf>,
location: ErrorLocation,
}
impl<'a> DisplayParseError<'a> {
pub fn new(
error: &'a ParseError,
source_code: &'a SourceCode<'a, 'a>,
source_kind: &'a SourceKind,
path: Option<&'a Path>,
impl DisplayParseError {
/// Create a [`DisplayParseError`] from a [`ParseError`] and a [`SourceKind`].
pub fn from_source_kind(
error: ParseError,
path: Option<PathBuf>,
source_kind: &SourceKind,
) -> Self {
Self::from_source_code(
error,
path,
&SourceCode::new(
source_kind.source_code(),
&LineIndex::from_source_text(source_kind.source_code()),
),
source_kind,
)
}
/// Create a [`DisplayParseError`] from a [`ParseError`] and a [`SourceCode`].
pub fn from_source_code(
error: ParseError,
path: Option<PathBuf>,
source_code: &SourceCode,
source_kind: &SourceKind,
) -> Self {
// Translate the byte offset to a location in the originating source.
let location =
if let Some(jupyter_index) = source_kind.as_ipy_notebook().map(Notebook::index) {
let source_location = source_code.source_location(error.offset);
ErrorLocation::Cell(
jupyter_index
.cell(source_location.row)
.unwrap_or(OneIndexed::MIN),
SourceLocation {
row: jupyter_index
.cell_row(source_location.row)
.unwrap_or(OneIndexed::MIN),
column: source_location.column,
},
)
} else {
ErrorLocation::File(source_code.source_location(error.offset))
};
Self {
error,
source_code,
source_kind,
path,
location,
}
}
/// Return the path of the file in which the error occurred.
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
}
impl Display for DisplayParseError<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(path) = self.path {
impl std::error::Error for DisplayParseError {}
impl Display for DisplayParseError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
if let Some(path) = self.path.as_ref() {
write!(
f,
"{header} {path}{colon}",
@ -179,41 +223,29 @@ impl Display for DisplayParseError<'_> {
colon = ":".cyan(),
)?;
}
let source_location = self.source_code.source_location(self.error.offset);
// If we're working on a Jupyter notebook, translate the positions
// with respect to the cell and row in the cell. This is the same
// format as the `TextEmitter`.
let error_location =
if let Some(jupyter_index) = self.source_kind.as_ipy_notebook().map(Notebook::index) {
match &self.location {
ErrorLocation::File(location) => {
write!(
f,
"cell {cell}{colon}",
cell = jupyter_index
.cell(source_location.row)
.unwrap_or(OneIndexed::MIN),
"{row}{colon}{column}{colon} {inner}",
row = location.row,
column = location.column,
colon = ":".cyan(),
)?;
SourceLocation {
row: jupyter_index
.cell_row(source_location.row)
.unwrap_or(OneIndexed::MIN),
column: source_location.column,
}
} else {
source_location
};
write!(
f,
"{row}{colon}{column}{colon} {inner}",
row = error_location.row,
column = error_location.column,
colon = ":".cyan(),
inner = &DisplayParseErrorType(&self.error.error)
)
inner = &DisplayParseErrorType(&self.error.error)
)
}
ErrorLocation::Cell(cell, location) => {
write!(
f,
"{cell}{colon}{row}{colon}{column}{colon} {inner}",
cell = cell,
row = location.row,
column = location.column,
colon = ":".cyan(),
inner = &DisplayParseErrorType(&self.error.error)
)
}
}
}
}
@ -251,6 +283,14 @@ impl Display for DisplayParseErrorType<'_> {
}
}
#[derive(Debug)]
enum ErrorLocation {
/// The error occurred in a Python file.
File(SourceLocation),
/// The error occurred in a Jupyter cell.
Cell(OneIndexed, SourceLocation),
}
/// Truncates the display text before the first newline character to avoid line breaks.
struct TruncateAtNewline<'a>(&'a dyn Display);