diff --git a/README.md b/README.md index 69ffe6dde20a7..4bc92a7445863 100644 --- a/README.md +++ b/README.md @@ -352,80 +352,71 @@ Arguments: [FILES]... Options: - --config - Path to the `pyproject.toml` or `ruff.toml` file to use for configuration - -v, --verbose - Enable verbose logging - -q, --quiet - Print lint violations, but nothing else - -s, --silent - Disable all logging (but still exit with status code "1" upon detecting lint violations) - -e, --exit-zero - Exit with status code "0", even upon detecting lint violations - -w, --watch - Run in watch mode by re-running whenever files change - --fix - Attempt to automatically fix lint violations - --fix-only - Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix` - --diff - Avoid writing any fixed files back; instead, output a diff for each changed file to stdout - -n, --no-cache - Disable cache reads - --isolated - Ignore all configuration files + --fix Attempt to automatically fix lint violations + --show-source Show violations with source code + --diff Avoid writing any fixed files back; instead, output a diff for each changed file to stdout + -w, --watch Run in watch mode by re-running whenever files change + --fix-only Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix` + --format Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint] + --config Path to the `pyproject.toml` or `ruff.toml` file to use for configuration + --add-noqa Enable automatic additions of `noqa` directives to failing lines + --show-files See the files Ruff will be run against with the current settings + --show-settings See the settings Ruff will use to lint a given Python file + -h, --help Print help + -V, --version Print version + +Rule selection: --select Comma-separated list of rule codes to enable (or ALL, to enable all rules) - --extend-select - Like --select, but adds additional rule codes on top of the selected ones --ignore Comma-separated list of rule codes to disable + --extend-select + Like --select, but adds additional rule codes on top of the selected ones --extend-ignore Like --ignore, but adds additional rule codes on top of the ignored ones - --exclude - List of paths, used to omit files and/or directories from analysis - --extend-exclude - Like --exclude, but adds additional files and directories on top of those already excluded + --per-file-ignores + List of mappings from file pattern to code to exclude --fixable List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`) --unfixable List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`) - --per-file-ignores - List of mappings from file pattern to code to exclude - --format - Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint] - --stdin-filename - The name of the file when passing it through stdin - --cache-dir - Path to the cache directory [env: RUFF_CACHE_DIR=] - --show-source - Show violations with source code - --respect-gitignore - Respect file exclusions via `.gitignore` and other standard ignore files - --force-exclude - Enforce exclusions, even for paths passed to Ruff directly on the command-line - --update-check - Enable or disable automatic update checks - --dummy-variable-rgx - Regular expression matching the name of dummy variables + +File selection: + --exclude List of paths, used to omit files and/or directories from analysis + --extend-exclude Like --exclude, but adds additional files and directories on top of those already excluded + --respect-gitignore Respect file exclusions via `.gitignore` and other standard ignore files + --force-exclude Enforce exclusions, even for paths passed to Ruff directly on the command-line + +Rule configuration: --target-version The minimum Python version that should be supported --line-length Set the line-length for length-associated rules and automatic formatting - --add-noqa - Enable automatic additions of `noqa` directives to failing lines - --clean - Clear any caches in the current directory or any subdirectories - --explain - Explain a rule - --show-files - See the files Ruff will be run against with the current settings - --show-settings - See the settings Ruff will use to lint a given Python file - -h, --help - Print help - -V, --version - Print version + --dummy-variable-rgx + Regular expression matching the name of dummy variables + +Miscellaneous: + -n, --no-cache + Disable cache reads + --isolated + Ignore all configuration files + --cache-dir + Path to the cache directory [env: RUFF_CACHE_DIR=] + --stdin-filename + The name of the file when passing it through stdin + -e, --exit-zero + Exit with status code "0", even upon detecting lint violations + --update-check + Enable or disable automatic update checks + +Subcommands: + --explain Explain a rule + --clean Clear any caches in the current directory or any subdirectories + +Log levels: + -v, --verbose Enable verbose logging + -q, --quiet Print lint violations, but nothing else + -s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations) ``` diff --git a/ruff_cli/src/args.rs b/ruff_cli/src/args.rs index 6a2052283bf30..45439c03830c1 100644 --- a/ruff_cli/src/args.rs +++ b/ruff_cli/src/args.rs @@ -22,121 +22,163 @@ use rustc_hash::FxHashMap; pub struct Args { #[arg(required_unless_present_any = ["clean", "explain", "generate_shell_completion"])] pub files: Vec, - /// Path to the `pyproject.toml` or `ruff.toml` file to use for - /// configuration. - #[arg(long, conflicts_with = "isolated")] - pub config: Option, - /// Enable verbose logging. - #[arg(short, long, group = "verbosity")] - pub verbose: bool, - /// Print lint violations, but nothing else. - #[arg(short, long, group = "verbosity")] - pub quiet: bool, - /// Disable all logging (but still exit with status code "1" upon detecting - /// lint violations). - #[arg(short, long, group = "verbosity")] - pub silent: bool, - /// Exit with status code "0", even upon detecting lint violations. - #[arg(short, long)] - pub exit_zero: bool, - /// Run in watch mode by re-running whenever files change. - #[arg(short, long)] - pub watch: bool, /// Attempt to automatically fix lint violations. #[arg(long, overrides_with("no_fix"))] fix: bool, #[clap(long, overrides_with("fix"), hide = true)] no_fix: bool, + /// Show violations with source code. + #[arg(long, overrides_with("no_show_source"))] + show_source: bool, + #[clap(long, overrides_with("show_source"), hide = true)] + no_show_source: bool, + /// Avoid writing any fixed files back; instead, output a diff for each + /// changed file to stdout. + #[arg(long)] + pub diff: bool, + /// Run in watch mode by re-running whenever files change. + #[arg(short, long)] + pub watch: bool, /// Fix any fixable lint violations, but don't report on leftover /// violations. Implies `--fix`. #[arg(long, overrides_with("no_fix_only"))] fix_only: bool, #[clap(long, overrides_with("fix_only"), hide = true)] no_fix_only: bool, - /// Avoid writing any fixed files back; instead, output a diff for each - /// changed file to stdout. - #[arg(long)] - pub diff: bool, - /// Disable cache reads. - #[arg(short, long)] - pub no_cache: bool, - /// Ignore all configuration files. - #[arg(long, conflicts_with = "config")] - pub isolated: bool, + /// Output serialization format for violations. + #[arg(long, value_enum, env = "RUFF_FORMAT")] + pub format: Option, + /// Path to the `pyproject.toml` or `ruff.toml` file to use for + /// configuration. + #[arg(long, conflicts_with = "isolated")] + pub config: Option, /// Comma-separated list of rule codes to enable (or ALL, to enable all /// rules). - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] pub select: Option>, + /// Comma-separated list of rule codes to disable. + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] + pub ignore: Option>, /// Like --select, but adds additional rule codes on top of the selected /// ones. - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] pub extend_select: Option>, - /// Comma-separated list of rule codes to disable. - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] - pub ignore: Option>, /// Like --ignore, but adds additional rule codes on top of the ignored /// ones. - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] pub extend_ignore: Option>, + /// List of mappings from file pattern to code to exclude + #[arg(long, value_delimiter = ',', help_heading = "Rule selection")] + pub per_file_ignores: Option>, /// List of paths, used to omit files and/or directories from analysis. - #[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")] + #[arg( + long, + value_delimiter = ',', + value_name = "FILE_PATTERN", + help_heading = "File selection" + )] pub exclude: Option>, /// Like --exclude, but adds additional files and directories on top of /// those already excluded. - #[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")] + #[arg( + long, + value_delimiter = ',', + value_name = "FILE_PATTERN", + help_heading = "File selection" + )] pub extend_exclude: Option>, /// List of rule codes to treat as eligible for autofix. Only applicable /// when autofix itself is enabled (e.g., via `--fix`). - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] pub fixable: Option>, /// List of rule codes to treat as ineligible for autofix. Only applicable /// when autofix itself is enabled (e.g., via `--fix`). - #[arg(long, value_delimiter = ',', value_name = "RULE_CODE")] + #[arg( + long, + value_delimiter = ',', + value_name = "RULE_CODE", + help_heading = "Rule selection" + )] pub unfixable: Option>, - /// List of mappings from file pattern to code to exclude - #[arg(long, value_delimiter = ',')] - pub per_file_ignores: Option>, - /// Output serialization format for violations. - #[arg(long, value_enum, env = "RUFF_FORMAT")] - pub format: Option, - /// The name of the file when passing it through stdin. - #[arg(long)] - pub stdin_filename: Option, - /// Path to the cache directory. - #[arg(long, env = "RUFF_CACHE_DIR")] - pub cache_dir: Option, - /// Show violations with source code. - #[arg(long, overrides_with("no_show_source"))] - show_source: bool, - #[clap(long, overrides_with("show_source"), hide = true)] - no_show_source: bool, /// Respect file exclusions via `.gitignore` and other standard ignore /// files. - #[arg(long, overrides_with("no_respect_gitignore"))] + #[arg( + long, + overrides_with("no_respect_gitignore"), + help_heading = "File selection" + )] respect_gitignore: bool, #[clap(long, overrides_with("respect_gitignore"), hide = true)] no_respect_gitignore: bool, /// Enforce exclusions, even for paths passed to Ruff directly on the /// command-line. - #[arg(long, overrides_with("no_force_exclude"))] + #[arg( + long, + overrides_with("no_force_exclude"), + help_heading = "File selection" + )] force_exclude: bool, #[clap(long, overrides_with("force_exclude"), hide = true)] no_force_exclude: bool, - /// Enable or disable automatic update checks. - #[arg(long, overrides_with("no_update_check"))] - update_check: bool, - #[clap(long, overrides_with("update_check"), hide = true)] - no_update_check: bool, - /// Regular expression matching the name of dummy variables. - #[arg(long)] - pub dummy_variable_rgx: Option, /// The minimum Python version that should be supported. - #[arg(long)] + #[arg(long, help_heading = "Rule configuration")] pub target_version: Option, /// Set the line-length for length-associated rules and automatic /// formatting. - #[arg(long)] + #[arg(long, help_heading = "Rule configuration")] pub line_length: Option, + /// Regular expression matching the name of dummy variables. + #[arg(long, help_heading = "Rule configuration")] + pub dummy_variable_rgx: Option, + /// Disable cache reads. + #[arg(short, long, help_heading = "Miscellaneous")] + pub no_cache: bool, + /// Ignore all configuration files. + #[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")] + pub isolated: bool, + /// Path to the cache directory. + #[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")] + pub cache_dir: Option, + /// The name of the file when passing it through stdin. + #[arg(long, help_heading = "Miscellaneous")] + pub stdin_filename: Option, + /// Exit with status code "0", even upon detecting lint violations. + #[arg(short, long, help_heading = "Miscellaneous")] + pub exit_zero: bool, + /// Enable or disable automatic update checks. + #[arg( + long, + overrides_with("no_update_check"), + help_heading = "Miscellaneous" + )] + update_check: bool, + #[clap(long, overrides_with("update_check"), hide = true)] + no_update_check: bool, /// Enable automatic additions of `noqa` directives to failing lines. #[arg( long, @@ -151,13 +193,15 @@ pub struct Args { conflicts_with = "watch", )] pub add_noqa: bool, - /// Clear any caches in the current directory or any subdirectories. + /// Explain a rule. #[arg( long, + value_parser=Rule::from_code, + help_heading="Subcommands", // Fake subcommands. conflicts_with = "add_noqa", - // conflicts_with = "clean", - conflicts_with = "explain", + conflicts_with = "clean", + // conflicts_with = "explain", conflicts_with = "generate_shell_completion", conflicts_with = "show_files", conflicts_with = "show_settings", @@ -165,15 +209,15 @@ pub struct Args { conflicts_with = "stdin_filename", conflicts_with = "watch", )] - pub clean: bool, - /// Explain a rule. + pub explain: Option<&'static Rule>, + /// Clear any caches in the current directory or any subdirectories. #[arg( long, - value_parser=Rule::from_code, + help_heading="Subcommands", // Fake subcommands. conflicts_with = "add_noqa", - conflicts_with = "clean", - // conflicts_with = "explain", + // conflicts_with = "clean", + conflicts_with = "explain", conflicts_with = "generate_shell_completion", conflicts_with = "show_files", conflicts_with = "show_settings", @@ -181,7 +225,7 @@ pub struct Args { conflicts_with = "stdin_filename", conflicts_with = "watch", )] - pub explain: Option<&'static Rule>, + pub clean: bool, /// Generate shell completion #[arg( long, @@ -229,6 +273,37 @@ pub struct Args { conflicts_with = "watch", )] pub show_settings: bool, + #[clap(flatten)] + pub log_level_args: LogLevelArgs, +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, clap::Args)] +pub struct LogLevelArgs { + /// Enable verbose logging. + #[arg(short, long, group = "verbosity", help_heading = "Log levels")] + pub verbose: bool, + /// Print lint violations, but nothing else. + #[arg(short, long, group = "verbosity", help_heading = "Log levels")] + pub quiet: bool, + /// Disable all logging (but still exit with status code "1" upon detecting + /// lint violations). + #[arg(short, long, group = "verbosity", help_heading = "Log levels")] + pub silent: bool, +} + +impl From<&LogLevelArgs> for LogLevel { + fn from(args: &LogLevelArgs) -> Self { + if args.silent { + LogLevel::Silent + } else if args.quiet { + LogLevel::Quiet + } else if args.verbose { + LogLevel::Verbose + } else { + LogLevel::Default + } + } } impl Args { @@ -247,12 +322,9 @@ impl Args { generate_shell_completion: self.generate_shell_completion, isolated: self.isolated, no_cache: self.no_cache, - quiet: self.quiet, show_files: self.show_files, show_settings: self.show_settings, - silent: self.silent, stdin_filename: self.stdin_filename, - verbose: self.verbose, watch: self.watch, }, Overrides { @@ -308,12 +380,9 @@ pub struct Arguments { pub generate_shell_completion: Option, pub isolated: bool, pub no_cache: bool, - pub quiet: bool, pub show_files: bool, pub show_settings: bool, - pub silent: bool, pub stdin_filename: Option, - pub verbose: bool, pub watch: bool, } @@ -420,19 +489,6 @@ impl ConfigProcessor for &Overrides { } } -/// Map the CLI settings to a `LogLevel`. -pub fn extract_log_level(args: &Arguments) -> LogLevel { - if args.silent { - LogLevel::Silent - } else if args.quiet { - LogLevel::Quiet - } else if args.verbose { - LogLevel::Verbose - } else { - LogLevel::Default - } -} - /// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`. pub fn collect_per_file_ignores(pairs: Vec) -> Vec { let mut per_file_ignores: FxHashMap> = FxHashMap::default(); diff --git a/ruff_cli/src/commands.rs b/ruff_cli/src/commands.rs index badc4523ec3ed..811176b148eaa 100644 --- a/ruff_cli/src/commands.rs +++ b/ruff_cli/src/commands.rs @@ -16,7 +16,7 @@ use ruff::linter::add_noqa_to_path; use ruff::logging::LogLevel; use ruff::message::{Location, Message}; use ruff::registry::{Linter, Rule, RuleNamespace}; -use ruff::resolver::{FileDiscovery, PyprojectDiscovery}; +use ruff::resolver::PyprojectDiscovery; use ruff::settings::flags; use ruff::settings::types::SerializationFormat; use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError}; @@ -32,15 +32,13 @@ use crate::iterators::par_iter; pub fn run( files: &[PathBuf], pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, overrides: &Overrides, cache: flags::Cache, autofix: fix::FixMode, ) -> Result { // Collect all the Python files to check. let start = Instant::now(); - let (paths, resolver) = - resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; let duration = start.elapsed(); debug!("Identified files to lint in: {:?}", duration); @@ -49,9 +47,6 @@ pub fn run( return Ok(Diagnostics::default()); } - // Validate the `Settings` and return any errors. - resolver.validate(pyproject_strategy)?; - // Initialize the cache. if matches!(cache, flags::Cache::Enabled) { match &pyproject_strategy { @@ -156,12 +151,11 @@ fn read_from_stdin() -> Result { pub fn run_stdin( filename: Option<&Path>, pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, overrides: &Overrides, autofix: fix::FixMode, ) -> Result { if let Some(filename) = filename { - if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? { + if !resolver::python_file_at_path(filename, pyproject_strategy, overrides)? { return Ok(Diagnostics::default()); } } @@ -182,13 +176,11 @@ pub fn run_stdin( pub fn add_noqa( files: &[PathBuf], pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, overrides: &Overrides, ) -> Result { // Collect all the files to check. let start = Instant::now(); - let (paths, resolver) = - resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; let duration = start.elapsed(); debug!("Identified files to lint in: {:?}", duration); @@ -197,9 +189,6 @@ pub fn add_noqa( return Ok(0); } - // Validate the `Settings` and return any errors. - resolver.validate(pyproject_strategy)?; - let start = Instant::now(); let modifications: usize = par_iter(&paths) .flatten() @@ -226,15 +215,10 @@ pub fn add_noqa( pub fn show_settings( files: &[PathBuf], pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, overrides: &Overrides, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, resolver) = - resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?; - - // Validate the `Settings` and return any errors. - resolver.validate(pyproject_strategy)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; // Print the list of files. let Some(entry) = paths @@ -255,21 +239,16 @@ pub fn show_settings( pub fn show_files( files: &[PathBuf], pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, overrides: &Overrides, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, resolver) = - resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?; + let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); return Ok(()); } - // Validate the `Settings` and return any errors. - resolver.validate(pyproject_strategy)?; - // Print the list of files. for entry in paths .iter() diff --git a/ruff_cli/src/diagnostics.rs b/ruff_cli/src/diagnostics.rs index d68329976c453..597f255852758 100644 --- a/ruff_cli/src/diagnostics.rs +++ b/ruff_cli/src/diagnostics.rs @@ -42,9 +42,6 @@ pub fn lint_path( cache: flags::Cache, autofix: fix::FixMode, ) -> Result { - // Validate the `Settings` and return any errors. - settings.lib.validate()?; - // Check the cache. // TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have // side-effects that aren't captured in the cache. (In practice, it's fine @@ -116,9 +113,6 @@ pub fn lint_stdin( settings: &Settings, autofix: fix::FixMode, ) -> Result { - // Validate the `Settings` and return any errors. - settings.validate()?; - // Lint the inputs. let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) { let (transformed, fixed, messages) = lint_fix( diff --git a/ruff_cli/src/main.rs b/ruff_cli/src/main.rs index 587449b6c5b0b..52b876c5d0c4e 100644 --- a/ruff_cli/src/main.rs +++ b/ruff_cli/src/main.rs @@ -13,11 +13,11 @@ use std::process::ExitCode; use std::sync::mpsc::channel; use ::ruff::logging::{set_up_logging, LogLevel}; -use ::ruff::resolver::{FileDiscovery, PyprojectDiscovery}; +use ::ruff::resolver::PyprojectDiscovery; use ::ruff::settings::types::SerializationFormat; use ::ruff::{fix, fs, warn_user_once}; use anyhow::Result; -use args::{extract_log_level, Args}; +use args::Args; use clap::{CommandFactory, Parser}; use colored::Colorize; use notify::{recommended_watcher, RecursiveMode, Watcher}; @@ -36,7 +36,7 @@ pub mod updates; fn inner_main() -> Result { // Extract command-line arguments. - let (cli, overrides) = Args::parse().partition(); + let args = Args::parse(); let default_panic_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { @@ -53,9 +53,11 @@ quoting the executed command, along with the relevant file contents and `pyproje default_panic_hook(info); })); - let log_level = extract_log_level(&cli); + let log_level: LogLevel = (&args.log_level_args).into(); set_up_logging(&log_level)?; + let (cli, overrides) = args.partition(); + if let Some(shell) = cli.generate_shell_completion { shell.generate(&mut Args::command(), &mut io::stdout()); return Ok(ExitCode::SUCCESS); @@ -74,24 +76,8 @@ quoting the executed command, along with the relevant file contents and `pyproje cli.stdin_filename.as_deref(), )?; - // Validate the `Settings` and return any errors. - match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => settings.lib.validate()?, - PyprojectDiscovery::Hierarchical(settings) => settings.lib.validate()?, - }; - // Extract options that are included in `Settings`, but only apply at the top // level. - let file_strategy = FileDiscovery { - force_exclude: match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => settings.lib.force_exclude, - PyprojectDiscovery::Hierarchical(settings) => settings.lib.force_exclude, - }, - respect_gitignore: match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => settings.lib.respect_gitignore, - PyprojectDiscovery::Hierarchical(settings) => settings.lib.respect_gitignore, - }, - }; let CliSettings { fix, fix_only, @@ -108,11 +94,11 @@ quoting the executed command, along with the relevant file contents and `pyproje return Ok(ExitCode::SUCCESS); } if cli.show_settings { - commands::show_settings(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?; + commands::show_settings(&cli.files, &pyproject_strategy, &overrides)?; return Ok(ExitCode::SUCCESS); } if cli.show_files { - commands::show_files(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?; + commands::show_files(&cli.files, &pyproject_strategy, &overrides)?; return Ok(ExitCode::SUCCESS); } @@ -150,7 +136,13 @@ quoting the executed command, along with the relevant file contents and `pyproje } let printer = Printer::new(&format, &log_level, &autofix, &violations); - if cli.watch { + + if cli.add_noqa { + let modifications = commands::add_noqa(&cli.files, &pyproject_strategy, &overrides)?; + if modifications > 0 && log_level >= LogLevel::Default { + println!("Added {modifications} noqa directives."); + } + } else if cli.watch { if !matches!(autofix, fix::FixMode::None) { warn_user_once!("--fix is not enabled in watch mode."); } @@ -165,7 +157,6 @@ quoting the executed command, along with the relevant file contents and `pyproje let messages = commands::run( &cli.files, &pyproject_strategy, - &file_strategy, &overrides, cache.into(), fix::FixMode::None, @@ -195,7 +186,6 @@ quoting the executed command, along with the relevant file contents and `pyproje let messages = commands::run( &cli.files, &pyproject_strategy, - &file_strategy, &overrides, cache.into(), fix::FixMode::None, @@ -206,12 +196,6 @@ quoting the executed command, along with the relevant file contents and `pyproje Err(err) => return Err(err.into()), } } - } else if cli.add_noqa { - let modifications = - commands::add_noqa(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?; - if modifications > 0 && log_level >= LogLevel::Default { - println!("Added {modifications} noqa directives."); - } } else { let is_stdin = cli.files == vec![PathBuf::from("-")]; @@ -220,7 +204,6 @@ quoting the executed command, along with the relevant file contents and `pyproje commands::run_stdin( cli.stdin_filename.map(fs::normalize_path).as_deref(), &pyproject_strategy, - &file_strategy, &overrides, autofix, )? @@ -228,7 +211,6 @@ quoting the executed command, along with the relevant file contents and `pyproje commands::run( &cli.files, &pyproject_strategy, - &file_strategy, &overrides, cache.into(), autofix, diff --git a/src/lib_native.rs b/src/lib_native.rs index eea1fcb96d977..f8c381ceac7eb 100644 --- a/src/lib_native.rs +++ b/src/lib_native.rs @@ -32,9 +32,6 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result = tokenize(contents); diff --git a/src/linter.rs b/src/linter.rs index 073bc039f2d51..7bdadd864f6a5 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -42,9 +42,6 @@ pub fn check_path( autofix: flags::Autofix, noqa: flags::Noqa, ) -> Result> { - // Validate the `Settings` and return any errors. - settings.validate()?; - // Aggregate all diagnostics. let mut diagnostics: Vec = vec![]; @@ -187,9 +184,6 @@ const MAX_ITERATIONS: usize = 100; /// Add any missing `#noqa` pragmas to the source code at the given `Path`. pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result { - // Validate the `Settings` and return any errors. - settings.validate()?; - // Read the file from disk. let contents = fs::read_file(path)?; diff --git a/src/resolver.rs b/src/resolver.rs index 4d046f2258f66..d7d5c1c9fa8af 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -16,13 +16,6 @@ use crate::settings::configuration::Configuration; use crate::settings::pyproject::settings_toml; use crate::settings::{pyproject, AllSettings, Settings}; -/// The strategy used to discover Python files in the filesystem.. -#[derive(Debug)] -pub struct FileDiscovery { - pub force_exclude: bool, - pub respect_gitignore: bool, -} - /// The strategy used to discover the relevant `pyproject.toml` file for each /// Python file. #[derive(Debug)] @@ -35,6 +28,15 @@ pub enum PyprojectDiscovery { Hierarchical(AllSettings), } +impl PyprojectDiscovery { + fn top_level_settings(&self) -> &AllSettings { + match self { + PyprojectDiscovery::Fixed(settings) => settings, + PyprojectDiscovery::Hierarchical(settings) => settings, + } + } +} + /// The strategy for resolving file paths in a `pyproject.toml`. pub enum Relativity { /// Resolve file paths relative to the current working directory. @@ -98,26 +100,6 @@ impl Resolver { pub fn iter(&self) -> impl Iterator { self.settings.values() } - - /// Validate all resolved `Settings` in this `Resolver`. - pub fn validate(&self, strategy: &PyprojectDiscovery) -> Result<()> { - // TODO(charlie): This risks false positives (but not false negatives), since - // some of the `Settings` in the path may ultimately be unused (or, e.g., they - // could have their `required_version` overridden by other `Settings` in - // the path). It'd be preferable to validate once we've determined the - // `Settings` for each path, but that's more expensive. - match &strategy { - PyprojectDiscovery::Fixed(settings) => { - settings.lib.validate()?; - } - PyprojectDiscovery::Hierarchical(default) => { - for settings in std::iter::once(default).chain(self.iter()) { - settings.lib.validate()?; - } - } - } - Ok(()) - } } pub trait ConfigProcessor: Copy + Send + Sync { @@ -232,7 +214,6 @@ pub fn is_python_entry(entry: &DirEntry) -> bool { pub fn python_files_in_path( paths: &[PathBuf], pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, processor: impl ConfigProcessor, ) -> Result<(Vec>, Resolver)> { // Normalize every path (e.g., convert from relative to absolute). @@ -256,7 +237,7 @@ pub fn python_files_in_path( } // Check if the paths themselves are excluded. - if file_strategy.force_exclude { + if pyproject_strategy.top_level_settings().lib.force_exclude { paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy)); if paths.is_empty() { return Ok((vec![], resolver)); @@ -272,7 +253,12 @@ pub fn python_files_in_path( for path in &paths[1..] { builder.add(path); } - builder.standard_filters(file_strategy.respect_gitignore); + builder.standard_filters( + pyproject_strategy + .top_level_settings() + .lib + .respect_gitignore, + ); builder.hidden(false); let walker = builder.build_parallel(); @@ -369,10 +355,9 @@ pub fn python_files_in_path( pub fn python_file_at_path( path: &Path, pyproject_strategy: &PyprojectDiscovery, - file_strategy: &FileDiscovery, processor: impl ConfigProcessor, ) -> Result { - if !file_strategy.force_exclude { + if !pyproject_strategy.top_level_settings().lib.force_exclude { return Ok(true); } diff --git a/src/settings/defaults.rs b/src/settings/defaults.rs index 403a179d9d8e6..abf0c6b4f5d18 100644 --- a/src/settings/defaults.rs +++ b/src/settings/defaults.rs @@ -68,7 +68,6 @@ impl Default for Settings { line_length: LINE_LENGTH, namespace_packages: vec![], per_file_ignores: vec![], - required_version: None, respect_gitignore: true, show_source: false, src: vec![path_dedot::CWD.clone()], diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 06d50d7937a04..9fcdc53c01931 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -20,7 +20,7 @@ use crate::rules::{ pydocstyle, pylint, pyupgrade, }; use crate::settings::configuration::Configuration; -use crate::settings::types::{PerFileIgnore, PythonVersion, SerializationFormat, Version}; +use crate::settings::types::{PerFileIgnore, PythonVersion, SerializationFormat}; use crate::warn_user_once; pub mod configuration; @@ -88,7 +88,6 @@ pub struct Settings { pub extend_exclude: HashableGlobSet, pub force_exclude: bool, pub respect_gitignore: bool, - pub required_version: Option, // Rule-specific settings pub allowed_confusables: HashableHashSet, @@ -124,6 +123,16 @@ pub struct Settings { impl Settings { pub fn from_configuration(config: Configuration, project_root: &Path) -> Result { + if let Some(required_version) = &config.required_version { + if &**required_version != CARGO_PKG_VERSION { + return Err(anyhow!( + "Required version `{}` does not match the running version `{}`", + &**required_version, + CARGO_PKG_VERSION + )); + } + } + Ok(Self { rules: (&config).into(), allowed_confusables: config @@ -151,7 +160,6 @@ impl Settings { config.per_file_ignores.unwrap_or_default(), )?, respect_gitignore: config.respect_gitignore.unwrap_or(true), - required_version: config.required_version, show_source: config.show_source.unwrap_or_default(), src: config .src @@ -219,19 +227,6 @@ impl Settings { ..Settings::default() } } - - pub fn validate(&self) -> Result<()> { - if let Some(required_version) = &self.required_version { - if &**required_version != CARGO_PKG_VERSION { - return Err(anyhow!( - "Required version `{}` does not match the running version `{}`", - &**required_version, - CARGO_PKG_VERSION - )); - } - } - Ok(()) - } } impl From<&Configuration> for RuleTable {