mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 04:45:01 +00:00
Move diffing logic into SourceKind::diff
(#7813)
This commit is contained in:
parent
e674e87d1b
commit
bb87f75b0c
2 changed files with 85 additions and 143 deletions
|
@ -1,8 +1,7 @@
|
||||||
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||||
|
|
||||||
use std::fs::{write, File};
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufWriter, Write};
|
|
||||||
use std::ops::AddAssign;
|
use std::ops::AddAssign;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
@ -13,7 +12,6 @@ use colored::Colorize;
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use similar::TextDiff;
|
|
||||||
|
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
||||||
|
@ -25,7 +23,7 @@ use ruff_linter::settings::{flags, LinterSettings};
|
||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::{fs, IOError, SyntaxError};
|
use ruff_linter::{fs, IOError, SyntaxError};
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
use ruff_notebook::{Cell, Notebook, NotebookError, NotebookIndex};
|
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_python_ast::{SourceType, TomlSourceType};
|
use ruff_python_ast::{SourceType, TomlSourceType};
|
||||||
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
||||||
|
@ -250,72 +248,13 @@ pub(crate) fn lint_path(
|
||||||
{
|
{
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => match transformed.as_ref() {
|
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||||
SourceKind::Python(transformed) => {
|
|
||||||
write(path, transformed.as_bytes())?;
|
|
||||||
}
|
|
||||||
SourceKind::IpyNotebook(notebook) => {
|
|
||||||
let mut writer = BufWriter::new(File::create(path)?);
|
|
||||||
notebook.write(&mut writer)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
match transformed.as_ref() {
|
source_kind.diff(
|
||||||
SourceKind::Python(transformed) => {
|
transformed.as_ref(),
|
||||||
let mut stdout = io::stdout().lock();
|
Some(path),
|
||||||
TextDiff::from_lines(source_kind.source_code(), transformed)
|
&mut io::stdout().lock(),
|
||||||
.unified_diff()
|
)?;
|
||||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
|
||||||
.to_writer(&mut stdout)?;
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
SourceKind::IpyNotebook(dest_notebook) => {
|
|
||||||
// We need to load the notebook again, since we might've
|
|
||||||
// mutated it.
|
|
||||||
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
// Cell indices are 1-based.
|
|
||||||
for ((idx, src_cell), dest_cell) in (1u32..)
|
|
||||||
.zip(src_notebook.cells().iter())
|
|
||||||
.zip(dest_notebook.cells().iter())
|
|
||||||
{
|
|
||||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
|
||||||
(src_cell, dest_cell)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
TextDiff::from_lines(
|
|
||||||
&src_code_cell.source.to_string(),
|
|
||||||
&dest_code_cell.source.to_string(),
|
|
||||||
)
|
|
||||||
.unified_diff()
|
|
||||||
// Jupyter notebook cells don't necessarily have a newline
|
|
||||||
// at the end. For example,
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// For a cell containing the above code, there'll only be one line,
|
|
||||||
// and it won't have a newline at the end. If it did, there'd be
|
|
||||||
// two lines, and the second line would be empty:
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
.missing_newline_hint(false)
|
|
||||||
.header(
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
)
|
|
||||||
.to_writer(&mut stdout)?;
|
|
||||||
}
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
}
|
}
|
||||||
|
@ -428,78 +367,7 @@ pub(crate) fn lint_stdin(
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
// But only write a diff if it's non-empty.
|
// But only write a diff if it's non-empty.
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
match transformed.as_ref() {
|
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
|
||||||
SourceKind::Python(_) => {
|
|
||||||
let text_diff = TextDiff::from_lines(
|
|
||||||
source_kind.source_code(),
|
|
||||||
transformed.source_code(),
|
|
||||||
);
|
|
||||||
let mut unified_diff = text_diff.unified_diff();
|
|
||||||
if let Some(path) = path {
|
|
||||||
unified_diff.header(
|
|
||||||
&fs::relativize_path(path),
|
|
||||||
&fs::relativize_path(path),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
unified_diff.to_writer(&mut stdout)?;
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
SourceKind::IpyNotebook(dst_notebook) => {
|
|
||||||
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
// Cell indices are 1-based.
|
|
||||||
for ((idx, src_cell), dest_cell) in (1u32..)
|
|
||||||
.zip(src_notebook.cells().iter())
|
|
||||||
.zip(dst_notebook.cells().iter())
|
|
||||||
{
|
|
||||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
|
||||||
(src_cell, dest_cell)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let src_source_code = src_code_cell.source.to_string();
|
|
||||||
let dest_source_code = dest_code_cell.source.to_string();
|
|
||||||
let text_diff =
|
|
||||||
TextDiff::from_lines(&src_source_code, &dest_source_code);
|
|
||||||
let mut unified_diff = text_diff.unified_diff();
|
|
||||||
|
|
||||||
// Jupyter notebook cells don't necessarily have a newline
|
|
||||||
// at the end. For example,
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// For a cell containing the above code, there'll only be one line,
|
|
||||||
// and it won't have a newline at the end. If it did, there'd be
|
|
||||||
// two lines, and the second line would be empty:
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
unified_diff.missing_newline_hint(false);
|
|
||||||
|
|
||||||
if let Some(path) = path {
|
|
||||||
unified_diff.header(
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unified_diff
|
|
||||||
.header(&format!("cell {idx}"), &format!("cell {idx}"));
|
|
||||||
};
|
|
||||||
|
|
||||||
unified_diff.to_writer(&mut stdout)?;
|
|
||||||
}
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
|
|
|
@ -2,13 +2,16 @@ use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
|
use similar::TextDiff;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use ruff_diagnostics::SourceMap;
|
use ruff_diagnostics::SourceMap;
|
||||||
use ruff_notebook::{Notebook, NotebookError};
|
use ruff_notebook::{Cell, Notebook, NotebookError};
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::PySourceType;
|
||||||
|
|
||||||
|
use crate::fs;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
||||||
pub enum SourceKind {
|
pub enum SourceKind {
|
||||||
/// The source contains Python source code.
|
/// The source contains Python source code.
|
||||||
|
@ -83,6 +86,77 @@ impl SourceKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a diff of the transformed source file to `stdout`.
|
||||||
|
pub fn diff(&self, other: &Self, path: Option<&Path>, writer: &mut dyn Write) -> Result<()> {
|
||||||
|
match (self, other) {
|
||||||
|
(SourceKind::Python(src), SourceKind::Python(dst)) => {
|
||||||
|
let text_diff = TextDiff::from_lines(dst, src);
|
||||||
|
let mut unified_diff = text_diff.unified_diff();
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
unified_diff.to_writer(&mut *writer)?;
|
||||||
|
|
||||||
|
writer.write_all(b"\n")?;
|
||||||
|
writer.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(SourceKind::IpyNotebook(src), SourceKind::IpyNotebook(dst)) => {
|
||||||
|
// Cell indices are 1-based.
|
||||||
|
for ((idx, src_cell), dst_cell) in
|
||||||
|
(1u32..).zip(src.cells().iter()).zip(dst.cells().iter())
|
||||||
|
{
|
||||||
|
let (Cell::Code(src_cell), Cell::Code(dst_cell)) = (src_cell, dst_cell) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let src_source_code = src_cell.source.to_string();
|
||||||
|
let dst_source_code = dst_cell.source.to_string();
|
||||||
|
|
||||||
|
let text_diff = TextDiff::from_lines(&src_source_code, &dst_source_code);
|
||||||
|
let mut unified_diff = text_diff.unified_diff();
|
||||||
|
|
||||||
|
// Jupyter notebook cells don't necessarily have a newline
|
||||||
|
// at the end. For example,
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// print("hello")
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// For a cell containing the above code, there'll only be one line,
|
||||||
|
// and it won't have a newline at the end. If it did, there'd be
|
||||||
|
// two lines, and the second line would be empty:
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// print("hello")
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
unified_diff.missing_newline_hint(false);
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
unified_diff.header(
|
||||||
|
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
||||||
|
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
unified_diff.header(&format!("cell {idx}"), &format!("cell {idx}"));
|
||||||
|
};
|
||||||
|
|
||||||
|
unified_diff.to_writer(&mut *writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(b"\n")?;
|
||||||
|
writer.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => bail!("cannot diff Python source code with Jupyter notebook source code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue