mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
Add a --check flag to the formatter CLI (#6982)
## Summary Returns an exit code of 1 if any files would be reformatted: ``` ruff on charlie/format-check:main [$?⇡] is 📦 v0.0.286 via 🐍 v3.11.2 via 🦀 v1.72.0 ❯ cargo run -p ruff_cli -- format foo.py --check Compiling ruff_cli v0.0.286 (/Users/crmarsh/workspace/ruff/crates/ruff_cli) Finished dev [unoptimized + debuginfo] target(s) in 1.69s Running `target/debug/ruff format foo.py --check` warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation. 1 file would be reformatted ruff on charlie/format-check:main [$?⇡] is 📦 v0.0.286 via 🐍 v3.11.2 via 🦀 v1.72.0 took 2s ❯ echo $? 1 ``` Closes #6966.
This commit is contained in:
parent
25c374856a
commit
fad23bbe60
5 changed files with 84 additions and 16 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2210,6 +2210,7 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"ignore",
|
"ignore",
|
||||||
"insta",
|
"insta",
|
||||||
|
"is-macro",
|
||||||
"itertools",
|
"itertools",
|
||||||
"itoa",
|
"itoa",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -47,6 +47,7 @@ colored = { workspace = true }
|
||||||
filetime = { workspace = true }
|
filetime = { workspace = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
ignore = { workspace = true }
|
ignore = { workspace = true }
|
||||||
|
is-macro = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
itoa = { version = "1.0.6" }
|
itoa = { version = "1.0.6" }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
|
|
@ -329,6 +329,10 @@ pub struct CheckCommand {
|
||||||
pub struct FormatCommand {
|
pub struct FormatCommand {
|
||||||
/// List of files or directories to format.
|
/// List of files or directories to format.
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
|
/// Avoid writing any formatted files back; instead, exit with a non-zero status code if any
|
||||||
|
/// files would have been modified, and zero otherwise.
|
||||||
|
#[arg(long)]
|
||||||
|
pub check: bool,
|
||||||
/// Specify file to write the linter output to (default: stdout).
|
/// Specify file to write the linter output to (default: stdout).
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub output_file: Option<PathBuf>,
|
pub output_file: Option<PathBuf>,
|
||||||
|
@ -480,6 +484,7 @@ impl FormatCommand {
|
||||||
pub fn partition(self) -> (FormatArguments, Overrides) {
|
pub fn partition(self) -> (FormatArguments, Overrides) {
|
||||||
(
|
(
|
||||||
FormatArguments {
|
FormatArguments {
|
||||||
|
check: self.check,
|
||||||
config: self.config,
|
config: self.config,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
isolated: self.isolated,
|
isolated: self.isolated,
|
||||||
|
@ -541,6 +546,7 @@ pub struct CheckArguments {
|
||||||
/// etc.).
|
/// etc.).
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct FormatArguments {
|
pub struct FormatArguments {
|
||||||
|
pub check: bool,
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub isolated: bool,
|
pub isolated: bool,
|
||||||
|
|
|
@ -25,6 +25,14 @@ use crate::args::{FormatArguments, Overrides};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, is_macro::Is)]
|
||||||
|
pub(crate) enum FormatMode {
|
||||||
|
/// Write the formatted contents back to the file.
|
||||||
|
Write,
|
||||||
|
/// Check if the file is formatted, but do not write the formatted contents back.
|
||||||
|
Check,
|
||||||
|
}
|
||||||
|
|
||||||
/// Format a set of files, and return the exit status.
|
/// Format a set of files, and return the exit status.
|
||||||
pub(crate) fn format(
|
pub(crate) fn format(
|
||||||
cli: &FormatArguments,
|
cli: &FormatArguments,
|
||||||
|
@ -37,6 +45,11 @@ pub(crate) fn format(
|
||||||
overrides,
|
overrides,
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
let mode = if cli.check {
|
||||||
|
FormatMode::Check
|
||||||
|
} else {
|
||||||
|
FormatMode::Write
|
||||||
|
};
|
||||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
|
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
|
@ -56,7 +69,7 @@ pub(crate) fn format(
|
||||||
let line_length = resolver.resolve(path, &pyproject_config).line_length;
|
let line_length = resolver.resolve(path, &pyproject_config).line_length;
|
||||||
let options = PyFormatOptions::from_source_type(source_type)
|
let options = PyFormatOptions::from_source_type(source_type)
|
||||||
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)));
|
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)));
|
||||||
format_path(path, options)
|
format_path(path, options, mode)
|
||||||
} else {
|
} else {
|
||||||
Ok(FormatResult::Skipped)
|
Ok(FormatResult::Skipped)
|
||||||
}
|
}
|
||||||
|
@ -68,6 +81,8 @@ pub(crate) fn format(
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
debug!("Formatted files in: {:?}", duration);
|
debug!("Formatted files in: {:?}", duration);
|
||||||
|
|
||||||
|
let summary = FormatResultSummary::from(results);
|
||||||
|
|
||||||
// Report on any errors.
|
// Report on any errors.
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
warn!("Encountered {} errors while formatting:", errors.len());
|
warn!("Encountered {} errors while formatting:", errors.len());
|
||||||
|
@ -86,14 +101,28 @@ pub(crate) fn format(
|
||||||
}
|
}
|
||||||
_ => Box::new(BufWriter::new(io::stdout())),
|
_ => Box::new(BufWriter::new(io::stdout())),
|
||||||
};
|
};
|
||||||
let summary = FormatResultSummary::from(results);
|
summary.show_user(&mut writer, mode)?;
|
||||||
summary.show_user(&mut writer)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.is_empty() {
|
match mode {
|
||||||
Ok(ExitStatus::Success)
|
FormatMode::Write => {
|
||||||
} else {
|
if errors.is_empty() {
|
||||||
Ok(ExitStatus::Error)
|
Ok(ExitStatus::Success)
|
||||||
|
} else {
|
||||||
|
Ok(ExitStatus::Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormatMode::Check => {
|
||||||
|
if errors.is_empty() {
|
||||||
|
if summary.formatted > 0 {
|
||||||
|
Ok(ExitStatus::Failure)
|
||||||
|
} else {
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(ExitStatus::Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +130,7 @@ pub(crate) fn format(
|
||||||
fn format_path(
|
fn format_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
options: PyFormatOptions,
|
options: PyFormatOptions,
|
||||||
|
mode: FormatMode,
|
||||||
) -> Result<FormatResult, FormatterIterationError> {
|
) -> Result<FormatResult, FormatterIterationError> {
|
||||||
let unformatted = std::fs::read_to_string(path)
|
let unformatted = std::fs::read_to_string(path)
|
||||||
.map_err(|err| FormatterIterationError::Read(path.to_path_buf(), err))?;
|
.map_err(|err| FormatterIterationError::Read(path.to_path_buf(), err))?;
|
||||||
|
@ -114,8 +144,10 @@ fn format_path(
|
||||||
if formatted.len() == unformatted.len() && formatted == unformatted {
|
if formatted.len() == unformatted.len() && formatted == unformatted {
|
||||||
Ok(FormatResult::Unchanged)
|
Ok(FormatResult::Unchanged)
|
||||||
} else {
|
} else {
|
||||||
std::fs::write(path, formatted.as_bytes())
|
if mode.is_write() {
|
||||||
.map_err(|err| FormatterIterationError::Write(path.to_path_buf(), err))?;
|
std::fs::write(path, formatted.as_bytes())
|
||||||
|
.map_err(|err| FormatterIterationError::Write(path.to_path_buf(), err))?;
|
||||||
|
}
|
||||||
Ok(FormatResult::Formatted)
|
Ok(FormatResult::Formatted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,22 +188,30 @@ impl From<Vec<FormatResult>> for FormatResultSummary {
|
||||||
|
|
||||||
impl FormatResultSummary {
|
impl FormatResultSummary {
|
||||||
/// Pretty-print a [`FormatResultSummary`] for user-facing display.
|
/// Pretty-print a [`FormatResultSummary`] for user-facing display.
|
||||||
fn show_user(&self, writer: &mut dyn Write) -> Result<(), io::Error> {
|
fn show_user(&self, writer: &mut dyn Write, mode: FormatMode) -> Result<(), io::Error> {
|
||||||
if self.formatted > 0 && self.unchanged > 0 {
|
if self.formatted > 0 && self.unchanged > 0 {
|
||||||
writeln!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"{} file{} reformatted, {} file{} left unchanged",
|
"{} file{} {}, {} file{} left unchanged",
|
||||||
self.formatted,
|
self.formatted,
|
||||||
if self.formatted == 1 { "" } else { "s" },
|
if self.formatted == 1 { "" } else { "s" },
|
||||||
|
match mode {
|
||||||
|
FormatMode::Write => "reformatted",
|
||||||
|
FormatMode::Check => "would be reformatted",
|
||||||
|
},
|
||||||
self.unchanged,
|
self.unchanged,
|
||||||
if self.unchanged == 1 { "" } else { "s" },
|
if self.unchanged == 1 { "" } else { "s" },
|
||||||
)
|
)
|
||||||
} else if self.formatted > 0 {
|
} else if self.formatted > 0 {
|
||||||
writeln!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"{} file{} reformatted",
|
"{} file{} {}",
|
||||||
self.formatted,
|
self.formatted,
|
||||||
if self.formatted == 1 { "" } else { "s" },
|
if self.formatted == 1 { "" } else { "s" },
|
||||||
|
match mode {
|
||||||
|
FormatMode::Write => "reformatted",
|
||||||
|
FormatMode::Check => "would be reformatted",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} else if self.unchanged > 0 {
|
} else if self.unchanged > 0 {
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
|
@ -6,6 +6,7 @@ use ruff_python_formatter::{format_module, PyFormatOptions};
|
||||||
use ruff_workspace::resolver::python_file_at_path;
|
use ruff_workspace::resolver::python_file_at_path;
|
||||||
|
|
||||||
use crate::args::{FormatArguments, Overrides};
|
use crate::args::{FormatArguments, Overrides};
|
||||||
|
use crate::commands::format::FormatMode;
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::stdin::read_from_stdin;
|
use crate::stdin::read_from_stdin;
|
||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
|
@ -18,6 +19,11 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &Overrides) -> Resu
|
||||||
overrides,
|
overrides,
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
let mode = if cli.check {
|
||||||
|
FormatMode::Check
|
||||||
|
} else {
|
||||||
|
FormatMode::Write
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||||
|
@ -25,13 +31,27 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &Overrides) -> Resu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdin = read_from_stdin()?;
|
// Format the file.
|
||||||
|
let unformatted = read_from_stdin()?;
|
||||||
let options = cli
|
let options = cli
|
||||||
.stdin_filename
|
.stdin_filename
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(PyFormatOptions::from_extension)
|
.map(PyFormatOptions::from_extension)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let formatted = format_module(&stdin, options)?;
|
let formatted = format_module(&unformatted, options)?;
|
||||||
stdout().lock().write_all(formatted.as_code().as_bytes())?;
|
|
||||||
Ok(ExitStatus::Success)
|
match mode {
|
||||||
|
FormatMode::Write => {
|
||||||
|
stdout().lock().write_all(formatted.as_code().as_bytes())?;
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
||||||
|
FormatMode::Check => {
|
||||||
|
if formatted.as_code().len() == unformatted.len() && formatted.as_code() == unformatted
|
||||||
|
{
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
} else {
|
||||||
|
Ok(ExitStatus::Failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue