mirror of https://github.com/astral-sh/ruff
Read black options in format_dev script (#5827)
## Summary Comparing repos with black requires that we use the settings as black, notably line length and magic trailing comma behaviour. Excludes and preserving quotes (vs. a preference for either quote style) is not yet implemented because they weren't needed for the test projects. In the other two commits i fixed the output when the progress bar is hidden (this way is recommonded in the indicatif docs), added a `scratch.pyi` file to gitignore because black formats stub files differently and also updated the ecosystem readme with the projects json without forks. ## Test Plan I added a `line-length` vs `line_length` test. Otherwise only my personal usage atm, a PR to integrate the script into the CI to check some projects will follow.
This commit is contained in:
parent
21063544f7
commit
7dd30f0270
|
|
@ -10,7 +10,7 @@ schemastore
|
||||||
# `maturin develop` and ecosystem_all_check.sh
|
# `maturin develop` and ecosystem_all_check.sh
|
||||||
.venv*
|
.venv*
|
||||||
# Formatter debugging (crates/ruff_python_formatter/README.md)
|
# Formatter debugging (crates/ruff_python_formatter/README.md)
|
||||||
scratch.py
|
scratch.*
|
||||||
# Created by `perf` (CONTRIBUTING.md)
|
# Created by `perf` (CONTRIBUTING.md)
|
||||||
perf.data
|
perf.data
|
||||||
perf.data.old
|
perf.data.old
|
||||||
|
|
|
||||||
|
|
@ -963,6 +963,12 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
|
@ -1987,6 +1993,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ignore",
|
"ignore",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"libcst",
|
"libcst",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -2004,11 +2011,13 @@ dependencies = [
|
||||||
"rustpython-format",
|
"rustpython-format",
|
||||||
"rustpython-parser",
|
"rustpython-parser",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"similar",
|
"similar",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2783,9 +2792,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.5"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
|
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
|
|
@ -2804,9 +2813,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.11"
|
version = "0.19.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
|
checksum = "5f8751d9c1b03c6500c387e96f81f815a4f8e72d142d2d4a9ffa6fedd51ddee7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -3339,9 +3348,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.4.7"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,18 @@ libcst = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
pretty_assertions = { version = "1.3.0" }
|
pretty_assertions = { version = "1.3.0" }
|
||||||
regex = { workspace = true }
|
|
||||||
rayon = "1.7.0"
|
rayon = "1.7.0"
|
||||||
|
regex = { workspace = true }
|
||||||
rustpython-format = { workspace = true }
|
rustpython-format = { workspace = true }
|
||||||
rustpython-parser = { workspace = true }
|
rustpython-parser = { workspace = true }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
similar = { workspace = true }
|
similar = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.6.0"
|
||||||
|
toml = { workspace = true, features = ["parse"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
indoc = "2.0.3"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, format_err, Context};
|
||||||
use clap::{CommandFactory, FromArgMatches};
|
use clap::{CommandFactory, FromArgMatches};
|
||||||
use ignore::DirEntry;
|
use ignore::DirEntry;
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
use log::debug;
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use ruff::resolver::python_files_in_path;
|
use ruff::resolver::python_files_in_path;
|
||||||
use ruff::settings::types::{FilePattern, FilePatternSet};
|
use ruff::settings::types::{FilePattern, FilePatternSet};
|
||||||
use ruff_cli::args::CheckArgs;
|
use ruff_cli::args::CheckArgs;
|
||||||
use ruff_cli::resolve::resolve;
|
use ruff_cli::resolve::resolve;
|
||||||
use ruff_formatter::{FormatError, PrintError};
|
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||||
use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions};
|
use ruff_python_formatter::{
|
||||||
|
format_module, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use similar::{ChangeTag, TextDiff};
|
use similar::{ChangeTag, TextDiff};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
@ -225,6 +228,7 @@ enum Message {
|
||||||
fn format_dev_multi_project(args: &Args) -> bool {
|
fn format_dev_multi_project(args: &Args) -> bool {
|
||||||
let mut total_errors = 0;
|
let mut total_errors = 0;
|
||||||
let mut total_files = 0;
|
let mut total_files = 0;
|
||||||
|
let mut num_projects: usize = 0;
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
rayon::scope(|scope| {
|
rayon::scope(|scope| {
|
||||||
|
|
@ -233,6 +237,7 @@ fn format_dev_multi_project(args: &Args) -> bool {
|
||||||
// Workers, to check is subdirectory in parallel
|
// Workers, to check is subdirectory in parallel
|
||||||
for base_dir in &args.files {
|
for base_dir in &args.files {
|
||||||
for dir in base_dir.read_dir().unwrap() {
|
for dir in base_dir.read_dir().unwrap() {
|
||||||
|
num_projects += 1;
|
||||||
let path = dir.unwrap().path().clone();
|
let path = dir.unwrap().path().clone();
|
||||||
|
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
|
|
@ -255,36 +260,39 @@ fn format_dev_multi_project(args: &Args) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main thread, writing to stdout
|
// Main thread, writing to stdout
|
||||||
|
#[allow(clippy::print_stdout)]
|
||||||
scope.spawn(|_| {
|
scope.spawn(|_| {
|
||||||
let mut error_file = args.error_file.as_ref().map(|error_file| {
|
let mut error_file = args.error_file.as_ref().map(|error_file| {
|
||||||
BufWriter::new(File::create(error_file).expect("Couldn't open error file"))
|
BufWriter::new(File::create(error_file).expect("Couldn't open error file"))
|
||||||
});
|
});
|
||||||
|
|
||||||
let bar = ProgressBar::new(args.files.len() as u64);
|
let bar = ProgressBar::new(num_projects as u64);
|
||||||
for message in receiver {
|
for message in receiver {
|
||||||
match message {
|
match message {
|
||||||
Message::Start { path } => {
|
Message::Start { path } => {
|
||||||
bar.println(path.display().to_string());
|
bar.suspend(|| println!("Starting {}", path.display()));
|
||||||
}
|
}
|
||||||
Message::Finished { path, result } => {
|
Message::Finished { path, result } => {
|
||||||
total_errors += result.error_count();
|
total_errors += result.error_count();
|
||||||
total_files += result.file_count;
|
total_files += result.file_count;
|
||||||
|
|
||||||
bar.println(format!(
|
bar.suspend(|| {
|
||||||
"Finished {} with {} files (similarity index {:.3}) in {:.2}s",
|
println!(
|
||||||
path.display(),
|
"Finished {} with {} files (similarity index {:.3}) in {:.2}s",
|
||||||
result.file_count,
|
path.display(),
|
||||||
result.statistics.similarity_index(),
|
result.file_count,
|
||||||
result.duration.as_secs_f32(),
|
result.statistics.similarity_index(),
|
||||||
));
|
result.duration.as_secs_f32(),
|
||||||
bar.println(result.display(args.format).to_string().trim_end());
|
);
|
||||||
|
});
|
||||||
|
bar.suspend(|| print!("{}", result.display(args.format)));
|
||||||
if let Some(error_file) = &mut error_file {
|
if let Some(error_file) = &mut error_file {
|
||||||
write!(error_file, "{}", result.display(args.format)).unwrap();
|
write!(error_file, "{}", result.display(args.format)).unwrap();
|
||||||
}
|
}
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
}
|
}
|
||||||
Message::Failed { path, error } => {
|
Message::Failed { path, error } => {
|
||||||
bar.println(format!("Failed {}: {}", path.display(), error));
|
bar.suspend(|| println!("Failed {}: {}", path.display(), error));
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,6 +322,13 @@ fn format_dev_project(
|
||||||
) -> anyhow::Result<CheckRepoResult> {
|
) -> anyhow::Result<CheckRepoResult> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
|
// TODO(konstin): The assumptions between this script (one repo) and ruff (pass in a bunch of
|
||||||
|
// files) mismatch.
|
||||||
|
let options = BlackOptions::from_file(&files[0])?.to_py_format_options();
|
||||||
|
debug!("Options for {}: {options:?}", files[0].display());
|
||||||
|
|
||||||
|
// TODO(konstin): black excludes
|
||||||
|
|
||||||
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
||||||
// First argument is ignored
|
// First argument is ignored
|
||||||
let paths = ruff_check_paths(files)?;
|
let paths = ruff_check_paths(files)?;
|
||||||
|
|
@ -335,7 +350,9 @@ fn format_dev_project(
|
||||||
|
|
||||||
let file = dir_entry.path().to_path_buf();
|
let file = dir_entry.path().to_path_buf();
|
||||||
// Handle panics (mostly in `debug_assert!`)
|
// Handle panics (mostly in `debug_assert!`)
|
||||||
let result = match catch_unwind(|| format_dev_file(&file, stability_check, write)) {
|
let result = match catch_unwind(|| {
|
||||||
|
format_dev_file(&file, stability_check, write, options.clone())
|
||||||
|
}) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(panic) => {
|
Err(panic) => {
|
||||||
if let Some(message) = panic.downcast_ref::<String>() {
|
if let Some(message) = panic.downcast_ref::<String>() {
|
||||||
|
|
@ -600,11 +617,12 @@ fn format_dev_file(
|
||||||
input_path: &Path,
|
input_path: &Path,
|
||||||
stability_check: bool,
|
stability_check: bool,
|
||||||
write: bool,
|
write: bool,
|
||||||
|
options: PyFormatOptions,
|
||||||
) -> Result<Statistics, CheckFileError> {
|
) -> Result<Statistics, CheckFileError> {
|
||||||
let content = fs::read_to_string(input_path)?;
|
let content = fs::read_to_string(input_path)?;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let printed = match format_module(&content, PyFormatOptions::default()) {
|
let printed = match format_module(&content, options.clone()) {
|
||||||
Ok(printed) => printed,
|
Ok(printed) => printed,
|
||||||
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
||||||
return Err(CheckFileError::SyntaxErrorInInput(err));
|
return Err(CheckFileError::SyntaxErrorInInput(err));
|
||||||
|
|
@ -631,7 +649,7 @@ fn format_dev_file(
|
||||||
}
|
}
|
||||||
|
|
||||||
if stability_check {
|
if stability_check {
|
||||||
let reformatted = match format_module(formatted, PyFormatOptions::default()) {
|
let reformatted = match format_module(formatted, options) {
|
||||||
Ok(reformatted) => reformatted,
|
Ok(reformatted) => reformatted,
|
||||||
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
||||||
return Err(CheckFileError::SyntaxErrorInOutput {
|
return Err(CheckFileError::SyntaxErrorInOutput {
|
||||||
|
|
@ -662,3 +680,131 @@ fn format_dev_file(
|
||||||
|
|
||||||
Ok(Statistics::from_versions(&content, formatted))
|
Ok(Statistics::from_versions(&content, formatted))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default)]
|
||||||
|
struct PyprojectToml {
|
||||||
|
tool: Option<PyprojectTomlTool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default)]
|
||||||
|
struct PyprojectTomlTool {
|
||||||
|
black: Option<BlackOptions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
|
struct BlackOptions {
|
||||||
|
// Black actually allows both snake case and kebab case
|
||||||
|
#[serde(alias = "line-length")]
|
||||||
|
line_length: u16,
|
||||||
|
#[serde(alias = "skip-magic-trailing-comma")]
|
||||||
|
skip_magic_trailing_comma: bool,
|
||||||
|
#[allow(unused)]
|
||||||
|
#[serde(alias = "force-exclude")]
|
||||||
|
force_exclude: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BlackOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
line_length: 88,
|
||||||
|
skip_magic_trailing_comma: false,
|
||||||
|
force_exclude: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlackOptions {
|
||||||
|
/// TODO(konstin): For the real version, fix printing of error chains and remove the path
|
||||||
|
/// argument
|
||||||
|
fn from_toml(toml: &str, path: &Path) -> anyhow::Result<Self> {
|
||||||
|
let pyproject_toml: PyprojectToml = toml::from_str(toml).map_err(|e| {
|
||||||
|
format_err!(
|
||||||
|
"Not a valid pyproject.toml toml file at {}: {e}",
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let black_options = pyproject_toml
|
||||||
|
.tool
|
||||||
|
.unwrap_or_default()
|
||||||
|
.black
|
||||||
|
.unwrap_or_default();
|
||||||
|
debug!(
|
||||||
|
"Found {}, setting black options: {:?}",
|
||||||
|
path.display(),
|
||||||
|
&black_options
|
||||||
|
);
|
||||||
|
Ok(black_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_file(repo: &Path) -> anyhow::Result<Self> {
|
||||||
|
let path = repo.join("pyproject.toml");
|
||||||
|
if !path.is_file() {
|
||||||
|
debug!(
|
||||||
|
"No pyproject.toml at {}, using black option defaults",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
return Ok(Self::default());
|
||||||
|
}
|
||||||
|
Self::from_toml(&fs::read_to_string(&path)?, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_py_format_options(&self) -> PyFormatOptions {
|
||||||
|
let mut options = PyFormatOptions::default();
|
||||||
|
options
|
||||||
|
.with_line_width(
|
||||||
|
LineWidth::try_from(self.line_length).expect("Invalid line length limit"),
|
||||||
|
)
|
||||||
|
.with_magic_trailing_comma(if self.skip_magic_trailing_comma {
|
||||||
|
MagicTrailingComma::Ignore
|
||||||
|
} else {
|
||||||
|
MagicTrailingComma::Respect
|
||||||
|
});
|
||||||
|
options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::format_dev::BlackOptions;
|
||||||
|
use indoc::indoc;
|
||||||
|
use ruff_formatter::{FormatOptions, LineWidth};
|
||||||
|
use ruff_python_formatter::MagicTrailingComma;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transformers() {
|
||||||
|
let toml = indoc! {"
|
||||||
|
[tool.black]
|
||||||
|
line-length = 119
|
||||||
|
target-version = ['py37']
|
||||||
|
"};
|
||||||
|
let options = BlackOptions::from_toml(toml, Path::new("pyproject.toml"))
|
||||||
|
.unwrap()
|
||||||
|
.to_py_format_options();
|
||||||
|
assert_eq!(options.line_width(), LineWidth::try_from(119).unwrap());
|
||||||
|
assert!(matches!(
|
||||||
|
options.magic_trailing_comma(),
|
||||||
|
MagicTrailingComma::Respect
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_typeshed() {
|
||||||
|
let toml = indoc! {r#"
|
||||||
|
[tool.black]
|
||||||
|
line_length = 130
|
||||||
|
target_version = ["py310"]
|
||||||
|
skip_magic_trailing_comma = true
|
||||||
|
force-exclude = ".*_pb2.pyi"
|
||||||
|
"#};
|
||||||
|
let options = BlackOptions::from_toml(toml, Path::new("pyproject.toml"))
|
||||||
|
.unwrap()
|
||||||
|
.to_py_format_options();
|
||||||
|
assert_eq!(options.line_width(), LineWidth::try_from(130).unwrap());
|
||||||
|
assert!(matches!(
|
||||||
|
options.magic_trailing_comma(),
|
||||||
|
MagicTrailingComma::Ignore
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ It is also possible large number of repositories using ruff. This dataset is lar
|
||||||
only do this occasionally:
|
only do this occasionally:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl https://raw.githubusercontent.com/akx/ruff-usage-aggregate/master/data/known-github-tomls.jsonl > github_search.jsonl
|
curl https://raw.githubusercontent.com/akx/ruff-usage-aggregate/master/data/known-github-tomls-clean.jsonl> github_search.jsonl
|
||||||
python scripts/check_ecosystem.py --checkouts target/checkouts --projects github_search.jsonl -v $(which true) $(which true)
|
python scripts/check_ecosystem.py --checkouts target/checkouts --projects github_search.jsonl -v $(which true) $(which true)
|
||||||
cargo run --bin ruff_dev -- format-dev --stability-check --multi-project target/checkouts
|
cargo run --bin ruff_dev -- format-dev --stability-check --multi-project target/checkouts
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue