From 2e95475f577c101a1dbc0d79e0bfdc31b176c0a2 Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 26 Apr 2025 06:02:03 -0400 Subject: [PATCH] [red-knot] Add --respect-ignore-files flag (#17569) Co-authored-by: Micha Reiser --- crates/red_knot/src/args.rs | 21 +++++ crates/red_knot/tests/cli.rs | 93 +++++++++++++++++++ .../red_knot_project/src/metadata/options.rs | 5 +- .../red_knot_project/src/metadata/settings.rs | 9 +- crates/red_knot_project/src/walk.rs | 5 +- knot.schema.json | 6 ++ 6 files changed, 136 insertions(+), 3 deletions(-) diff --git a/crates/red_knot/src/args.rs b/crates/red_knot/src/args.rs index 233a081445..00b40dd5ff 100644 --- a/crates/red_knot/src/args.rs +++ b/crates/red_knot/src/args.rs @@ -105,6 +105,19 @@ pub(crate) struct CheckCommand { /// Watch files for changes and recheck files related to the changed files. #[arg(long, short = 'W')] pub(crate) watch: bool, + + /// Respect file exclusions via `.gitignore` and other standard ignore files. + /// Use `--no-respect-gitignore` to disable. + #[arg( + long, + overrides_with("no_respect_ignore_files"), + help_heading = "File selection", + default_missing_value = "true", + num_args = 0..1 + )] + respect_ignore_files: Option, + #[clap(long, overrides_with("respect_ignore_files"), hide = true)] + no_respect_ignore_files: bool, } impl CheckCommand { @@ -120,6 +133,13 @@ impl CheckCommand { ) }; + // --no-respect-gitignore defaults to false and is set true by CLI flag. If passed, override config file + // Otherwise, only pass this through if explicitly set (don't default to anything here to + // make sure that doesn't take precedence over an explicitly-set config file value) + let respect_ignore_files = self + .no_respect_ignore_files + .then_some(false) + .or(self.respect_ignore_files); Options { environment: Some(EnvironmentOptions { python_version: self @@ -144,6 +164,7 @@ impl CheckCommand { error_on_warning: self.error_on_warning, }), rules, + respect_ignore_files, ..Default::default() } } diff --git a/crates/red_knot/tests/cli.rs b/crates/red_knot/tests/cli.rs index 4509882e50..fdee22358a 100644 --- a/crates/red_knot/tests/cli.rs +++ b/crates/red_knot/tests/cli.rs @@ -5,6 +5,99 @@ use std::path::{Path, PathBuf}; use std::process::Command; use tempfile::TempDir; +#[test] +fn test_respect_ignore_files() -> anyhow::Result<()> { + // First test that the default option works correctly (the file is skipped) + let case = TestCase::with_files([(".ignore", "test.py"), ("test.py", "~")])?; + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN No python files found under the given path(s) + "); + + // Test that we can set to false via CLI + let case = TestCase::with_files([(".ignore", "test.py"), ("test.py", "~")])?; + assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error: invalid-syntax + --> /test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + "); + + // Test that we can set to false via config file + let case = TestCase::with_files([ + ("knot.toml", "respect-ignore-files = false"), + (".ignore", "test.py"), + ("test.py", "~"), + ])?; + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error: invalid-syntax + --> /test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + "); + + // Ensure CLI takes precedence + let case = TestCase::with_files([ + ("knot.toml", "respect-ignore-files = false"), + (".ignore", "test.py"), + ("test.py", "~"), + ])?; + assert_cmd_snapshot!(case.command().arg("--respect-ignore-files"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN No python files found under the given path(s) + "); + + // Ensure --no-respect-ignore-files takes precedence over config file + let case = TestCase::with_files([ + ("knot.toml", "respect-ignore-files = true"), + (".ignore", "test.py"), + ("test.py", "~"), + ])?; + assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error: invalid-syntax + --> /test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + "); + Ok(()) +} /// Specifying an option on the CLI should take precedence over the same setting in the /// project's configuration. Here, this is tested for the Python version. #[test] diff --git a/crates/red_knot_project/src/metadata/options.rs b/crates/red_knot_project/src/metadata/options.rs index d035f4cf7b..eeff138082 100644 --- a/crates/red_knot_project/src/metadata/options.rs +++ b/crates/red_knot_project/src/metadata/options.rs @@ -32,6 +32,9 @@ pub struct Options { #[serde(skip_serializing_if = "Option::is_none")] pub terminal: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub respect_ignore_files: Option, } impl Options { @@ -133,7 +136,7 @@ impl Options { pub(crate) fn to_settings(&self, db: &dyn Db) -> (Settings, Vec) { let (rules, diagnostics) = self.to_rule_selection(db); - let mut settings = Settings::new(rules); + let mut settings = Settings::new(rules, self.respect_ignore_files); if let Some(terminal) = self.terminal.as_ref() { settings.set_terminal(TerminalSettings { diff --git a/crates/red_knot_project/src/metadata/settings.rs b/crates/red_knot_project/src/metadata/settings.rs index e16a72d4a0..f5572f6657 100644 --- a/crates/red_knot_project/src/metadata/settings.rs +++ b/crates/red_knot_project/src/metadata/settings.rs @@ -21,13 +21,16 @@ pub struct Settings { rules: Arc, terminal: TerminalSettings, + + respect_ignore_files: bool, } impl Settings { - pub fn new(rules: RuleSelection) -> Self { + pub fn new(rules: RuleSelection, respect_ignore_files: Option) -> Self { Self { rules: Arc::new(rules), terminal: TerminalSettings::default(), + respect_ignore_files: respect_ignore_files.unwrap_or(true), } } @@ -35,6 +38,10 @@ impl Settings { &self.rules } + pub fn respect_ignore_files(&self) -> bool { + self.respect_ignore_files + } + pub fn to_rules(&self) -> Arc { self.rules.clone() } diff --git a/crates/red_knot_project/src/walk.rs b/crates/red_knot_project/src/walk.rs index 531c823a28..025566db38 100644 --- a/crates/red_knot_project/src/walk.rs +++ b/crates/red_knot_project/src/walk.rs @@ -129,7 +129,10 @@ impl<'a> ProjectFilesWalker<'a> { { let mut paths = paths.into_iter(); - let mut walker = db.system().walk_directory(paths.next()?.as_ref()); + let mut walker = db + .system() + .walk_directory(paths.next()?.as_ref()) + .standard_filters(db.project().settings(db).respect_ignore_files()); for path in paths { walker = walker.add(path); diff --git a/knot.schema.json b/knot.schema.json index 941eee5af9..7349373c0e 100644 --- a/knot.schema.json +++ b/knot.schema.json @@ -15,6 +15,12 @@ } ] }, + "respect-ignore-files": { + "type": [ + "boolean", + "null" + ] + }, "rules": { "description": "Configures the enabled lints and their severity.", "anyOf": [