From 04e8e744996016540855f905042ab6d796750daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Tue, 9 May 2023 19:22:52 +0300 Subject: [PATCH] Feat: detect changes also in configuration files (#4169) --- crates/ruff_cli/src/lib.rs | 153 +++++++++++++++++++++++++++++++++---- 1 file changed, 137 insertions(+), 16 deletions(-) diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index dc7a1ae7a2..c83132c27c 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -43,6 +43,36 @@ impl From for ExitCode { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum ChangeKind { + Configuration, + SourceFile, +} + +/// Return the [`ChangeKind`] based on the list of modified file paths. +/// +/// Returns `None` if no relevant changes were detected. +fn change_detected(paths: &[PathBuf]) -> Option { + // If any `.toml` files were modified, return `ChangeKind::Configuration`. Otherwise, return + // `ChangeKind::SourceFile` if any `.py`, `.pyi`, or `.pyw` files were modified. + let mut source_file = false; + for path in paths { + if let Some(suffix) = path.extension() { + match suffix.to_str() { + Some("toml") => { + return Some(ChangeKind::Configuration); + } + Some("py" | "pyi" | "pyw") => source_file = true, + _ => {} + } + } + } + if source_file { + return Some(ChangeKind::SourceFile); + } + None +} + pub fn run( Args { command, @@ -207,30 +237,41 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { for file in &cli.files { watcher.watch(file, RecursiveMode::Recursive)?; } + if let Some(file) = pyproject_config.path.as_ref() { + watcher.watch(file, RecursiveMode::Recursive)?; + } + + // In watch mode, we may need to re-resolve the configuration. + // TODO(charlie): Re-compute other derivative values, like the `printer`. + let mut pyproject_config = pyproject_config; loop { match rx.recv() { Ok(event) => { - let paths = event?.paths; - let py_changed = paths.iter().any(|path| { - path.extension() - .map(|ext| ext == "py" || ext == "pyi") - .unwrap_or_default() - }); - if py_changed { - Printer::clear_screen()?; - printer.write_to_user("File change detected...\n"); + let Some(change_kind) = change_detected(&event?.paths) else { + continue; + }; - let messages = commands::run::run( - &cli.files, - &pyproject_config, + if matches!(change_kind, ChangeKind::Configuration) { + pyproject_config = resolve::resolve( + cli.isolated, + cli.config.as_deref(), &overrides, - cache.into(), - noqa.into(), - autofix, + cli.stdin_filename.as_deref(), )?; - printer.write_continuously(&messages)?; } + Printer::clear_screen()?; + printer.write_to_user("File change detected...\n"); + + let messages = commands::run::run( + &cli.files, + &pyproject_config, + &overrides, + cache.into(), + noqa.into(), + autofix, + )?; + printer.write_continuously(&messages)?; } Err(err) => return Err(err.into()), } @@ -311,3 +352,83 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { } Ok(ExitStatus::Success) } + +#[cfg(test)] +mod test_file_change_detector { + use crate::{change_detected, ChangeKind}; + use std::path::PathBuf; + + #[test] + fn detect_correct_file_change() { + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("tmp/pyproject.toml"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("pyproject.toml"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("tmp1/tmp2/tmp3/pyproject.toml"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("tmp/ruff.toml"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("tmp/.ruff.toml"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::SourceFile), + change_detected(&[ + PathBuf::from("tmp/rule.py"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::SourceFile), + change_detected(&[ + PathBuf::from("tmp/rule.pyi"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("pyproject.toml"), + PathBuf::from("tmp/rule.py"), + ]), + ); + assert_eq!( + Some(ChangeKind::Configuration), + change_detected(&[ + PathBuf::from("tmp/rule.py"), + PathBuf::from("pyproject.toml"), + ]), + ); + assert_eq!( + None, + change_detected(&[ + PathBuf::from("tmp/rule.js"), + PathBuf::from("tmp/bin/ruff.rs"), + ]), + ); + } +}