diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 6e3be6de62..3362d0b031 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -14,8 +14,6 @@ license = "MIT" [[bin]] name = "ruff" -path = "src/main.rs" -doctest = false # Since the name of the binary is the same as the name of the `ruff` crate # running `cargo doc --no-deps --all` results in an `output filename collision` diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index a656a98981..335a393937 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use std::path::PathBuf; use std::str::FromStr; diff --git a/crates/ruff_cli/src/bin/ruff.rs b/crates/ruff_cli/src/bin/ruff.rs new file mode 100644 index 0000000000..bd2f535fa4 --- /dev/null +++ b/crates/ruff_cli/src/bin/ruff.rs @@ -0,0 +1,64 @@ +use clap::{Parser, Subcommand}; +use std::process::ExitCode; + +use colored::Colorize; + +use ruff_cli::args::{Args, Command}; +use ruff_cli::{run, ExitStatus}; + +#[cfg(target_os = "windows")] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +#[cfg(all( + not(target_os = "windows"), + not(target_os = "openbsd"), + any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64" + ) +))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +pub fn main() -> ExitCode { + let mut args: Vec<_> = std::env::args().collect(); + + // Clap doesn't support default subcommands but we want to run `check` by + // default for convenience and backwards-compatibility, so we just + // preprocess the arguments accordingly before passing them to Clap. + if let Some(arg) = args.get(1) { + if !Command::has_subcommand(rewrite_legacy_subcommand(arg)) + && arg != "-h" + && arg != "--help" + && arg != "-V" + && arg != "--version" + && arg != "help" + { + args.insert(1, "check".into()); + } + } + + let args = Args::parse_from(args); + + match run(args) { + Ok(code) => code.into(), + Err(err) => { + #[allow(clippy::print_stderr)] + { + eprintln!("{}{} {err:?}", "error".red().bold(), ":".bold()); + } + ExitStatus::Error.into() + } + } +} + +fn rewrite_legacy_subcommand(cmd: &str) -> &str { + match cmd { + "--explain" => "rule", + "--clean" => "clean", + "--generate-shell-completion" => "generate-shell-completion", + cmd => cmd, + } +} diff --git a/crates/ruff_cli/src/commands/config.rs b/crates/ruff_cli/src/commands/config.rs index 9e94d1ac1b..5520a75c25 100644 --- a/crates/ruff_cli/src/commands/config.rs +++ b/crates/ruff_cli/src/commands/config.rs @@ -1,10 +1,9 @@ +use crate::ExitStatus; use ruff::settings::{ options::Options, options_base::{ConfigurationOptions, OptionEntry, OptionField}, }; -use crate::ExitStatus; - #[allow(clippy::print_stdout)] pub(crate) fn config(key: Option<&str>) -> ExitStatus { let Some(entry) = Options::get(key) else { diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index 878aa05a1b..b8d5aa1db9 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -1,27 +1,288 @@ -//! This library only exists to enable the Ruff internal tooling (`ruff_dev`) -//! to automatically update the `ruff help` output in the `README.md`. -//! -//! For the actual Ruff library, see [`ruff`]. - -mod args; - +use anyhow::Result; use clap::CommandFactory; +use notify::{recommended_watcher, RecursiveMode, Watcher}; +use std::io; +use std::path::PathBuf; +use std::process::ExitCode; +use std::sync::mpsc::channel; -/// Returns the output of `ruff help`. -pub fn command_help() -> String { - args::Args::command().render_help().to_string() +use crate::args::{Args, CheckArgs, Command}; +use crate::printer::{Flags as PrinterFlags, Printer}; + +use ruff::logging::{set_up_logging, LogLevel}; +use ruff::settings::types::SerializationFormat; +use ruff::settings::CliSettings; +use ruff::{fix, fs, warn_user_once}; + +pub mod args; +mod cache; +mod commands; +mod diagnostics; +mod iterators; +mod printer; +mod resolve; + +pub enum ExitStatus { + /// Linting was successful and there were no linting errors. + Success, + /// Linting was successful but there were linting errors. + Failure, + /// Linting failed. + Error, } -/// Returns the output of `ruff help check`. -pub fn subcommand_help() -> String { - let mut cmd = args::Args::command(); - - // The build call is necessary for the help output to contain `Usage: ruff - // check` instead of `Usage: check` see https://github.com/clap-rs/clap/issues/4685 - cmd.build(); - - cmd.find_subcommand_mut("check") - .expect("`check` subcommand not found") - .render_help() - .to_string() +impl From for ExitCode { + fn from(status: ExitStatus) -> Self { + match status { + ExitStatus::Success => ExitCode::from(0), + ExitStatus::Failure => ExitCode::from(1), + ExitStatus::Error => ExitCode::from(2), + } + } +} + +pub fn run( + Args { + command, + log_level_args, + }: Args, +) -> Result { + #[cfg(not(debug_assertions))] + { + let default_panic_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + #[allow(clippy::print_stderr)] + { + eprintln!( + r#" +{}: `ruff` crashed. This indicates a bug in `ruff`. If you could open an issue at: + +https://github.com/charliermarsh/ruff/issues/new?title=%5BPanic%5D + +quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative! +"#, + "error".red().bold(), + ); + } + default_panic_hook(info); + })); + } + + let log_level: LogLevel = (&log_level_args).into(); + set_up_logging(&log_level)?; + + match command { + Command::Rule { rule, format } => commands::rule::rule(&rule, format)?, + Command::Config { option } => return Ok(commands::config::config(option.as_deref())), + Command::Linter { format } => commands::linter::linter(format)?, + Command::Clean => commands::clean::clean(log_level)?, + Command::GenerateShellCompletion { shell } => { + shell.generate(&mut Args::command(), &mut io::stdout()); + } + Command::Check(args) => return check(args, log_level), + } + + Ok(ExitStatus::Success) +} + +fn check(args: CheckArgs, log_level: LogLevel) -> Result { + let (cli, overrides) = args.partition(); + + // Construct the "default" settings. These are used when no `pyproject.toml` + // files are present, or files are injected from outside of the hierarchy. + let pyproject_strategy = resolve::resolve( + cli.isolated, + cli.config.as_deref(), + &overrides, + cli.stdin_filename.as_deref(), + )?; + + if cli.show_settings { + commands::show_settings::show_settings(&cli.files, &pyproject_strategy, &overrides)?; + return Ok(ExitStatus::Success); + } + if cli.show_files { + commands::show_files::show_files(&cli.files, &pyproject_strategy, &overrides)?; + return Ok(ExitStatus::Success); + } + + // Extract options that are included in `Settings`, but only apply at the top + // level. + let CliSettings { + fix, + fix_only, + format, + show_fixes, + update_check, + .. + } = pyproject_strategy.top_level_settings().cli.clone(); + + // Autofix rules are as follows: + // - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or + // print them to stdout, if we're reading from stdin). + // - Otherwise, if `--format json` is set, generate the fixes (so we print them + // out as part of the JSON payload), but don't write them to disk. + // - If `--diff` or `--fix-only` are set, don't print any violations (only + // fixes). + // TODO(charlie): Consider adding ESLint's `--fix-dry-run`, which would generate + // but not apply fixes. That would allow us to avoid special-casing JSON + // here. + let autofix = if cli.diff { + fix::FixMode::Diff + } else if fix || fix_only { + fix::FixMode::Apply + } else if matches!(format, SerializationFormat::Json) { + fix::FixMode::Generate + } else { + fix::FixMode::None + }; + let cache = !cli.no_cache; + let noqa = !cli.ignore_noqa; + let mut printer_flags = PrinterFlags::empty(); + if !(cli.diff || fix_only) { + printer_flags |= PrinterFlags::SHOW_VIOLATIONS; + } + if show_fixes { + printer_flags |= PrinterFlags::SHOW_FIXES; + } + + #[cfg(debug_assertions)] + if cache { + // `--no-cache` doesn't respect code changes, and so is often confusing during + // development. + warn_user_once!("Detected debug build without --no-cache."); + } + + if cli.add_noqa { + if !matches!(autofix, fix::FixMode::None) { + warn_user_once!("--fix is incompatible with --add-noqa."); + } + let modifications = + commands::add_noqa::add_noqa(&cli.files, &pyproject_strategy, &overrides)?; + if modifications > 0 && log_level >= LogLevel::Default { + let s = if modifications == 1 { "" } else { "s" }; + #[allow(clippy::print_stderr)] + { + eprintln!("Added {modifications} noqa directive{s}."); + } + } + return Ok(ExitStatus::Success); + } + + let printer = Printer::new(format, log_level, autofix, printer_flags); + + if cli.watch { + if !matches!(autofix, fix::FixMode::None) { + warn_user_once!("--fix is unsupported in watch mode."); + } + if format != SerializationFormat::Text { + warn_user_once!("--format 'text' is used in watch mode."); + } + + // Perform an initial run instantly. + Printer::clear_screen()?; + printer.write_to_user("Starting linter in watch mode...\n"); + + let messages = commands::run::run( + &cli.files, + &pyproject_strategy, + &overrides, + cache.into(), + noqa.into(), + fix::FixMode::None, + )?; + printer.write_continuously(&messages)?; + + // Configure the file watcher. + let (tx, rx) = channel(); + let mut watcher = recommended_watcher(tx)?; + for file in &cli.files { + watcher.watch(file, RecursiveMode::Recursive)?; + } + + 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 messages = commands::run::run( + &cli.files, + &pyproject_strategy, + &overrides, + cache.into(), + noqa.into(), + fix::FixMode::None, + )?; + printer.write_continuously(&messages)?; + } + } + Err(err) => return Err(err.into()), + } + } + } else { + let is_stdin = cli.files == vec![PathBuf::from("-")]; + + // Generate lint violations. + let diagnostics = if is_stdin { + commands::run_stdin::run_stdin( + cli.stdin_filename.map(fs::normalize_path).as_deref(), + &pyproject_strategy, + &overrides, + noqa.into(), + autofix, + )? + } else { + commands::run::run( + &cli.files, + &pyproject_strategy, + &overrides, + cache.into(), + noqa.into(), + autofix, + )? + }; + + // Always try to print violations (the printer itself may suppress output), + // unless we're writing fixes via stdin (in which case, the transformed + // source code goes to stdout). + if !(is_stdin && matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff)) { + if cli.statistics { + printer.write_statistics(&diagnostics)?; + } else { + printer.write_once(&diagnostics)?; + } + } + + if update_check { + warn_user_once!( + "update-check has been removed; setting it will cause an error in a future \ + version." + ); + } + + if !cli.exit_zero { + if cli.diff || fix_only { + if !diagnostics.fixed.is_empty() { + return Ok(ExitStatus::Failure); + } + } else if cli.exit_non_zero_on_fix { + if !diagnostics.fixed.is_empty() || !diagnostics.messages.is_empty() { + return Ok(ExitStatus::Failure); + } + } else { + if !diagnostics.messages.is_empty() { + return Ok(ExitStatus::Failure); + } + } + } + } + Ok(ExitStatus::Success) } diff --git a/crates/ruff_cli/src/main.rs b/crates/ruff_cli/src/main.rs deleted file mode 100644 index 882ed775d3..0000000000 --- a/crates/ruff_cli/src/main.rs +++ /dev/null @@ -1,346 +0,0 @@ -use std::io::{self}; -use std::path::PathBuf; -use std::process::ExitCode; -use std::sync::mpsc::channel; - -use anyhow::Result; -use clap::{CommandFactory, Parser, Subcommand}; -use colored::Colorize; -use notify::{recommended_watcher, RecursiveMode, Watcher}; - -use ::ruff::logging::{set_up_logging, LogLevel}; -use ::ruff::settings::types::SerializationFormat; -use ::ruff::settings::CliSettings; -use ::ruff::{fix, fs, warn_user_once}; -use args::{Args, CheckArgs, Command}; -use printer::{Flags as PrinterFlags, Printer}; - -pub(crate) mod args; -mod cache; -mod commands; -mod diagnostics; -mod iterators; -mod printer; -mod resolve; - -#[cfg(target_os = "windows")] -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -#[cfg(all( - not(target_os = "windows"), - not(target_os = "openbsd"), - any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "powerpc64" - ) -))] -#[global_allocator] -static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; - -enum ExitStatus { - /// Linting was successful and there were no linting errors. - Success, - /// Linting was successful but there were linting errors. - Failure, - /// Linting failed. - Error, -} - -impl From for ExitCode { - fn from(status: ExitStatus) -> Self { - match status { - ExitStatus::Success => ExitCode::from(0), - ExitStatus::Failure => ExitCode::from(1), - ExitStatus::Error => ExitCode::from(2), - } - } -} - -fn inner_main() -> Result { - let mut args: Vec<_> = std::env::args_os().collect(); - - // Clap doesn't support default subcommands but we want to run `check` by - // default for convenience and backwards-compatibility, so we just - // preprocess the arguments accordingly before passing them to Clap. - if let Some(arg) = args.get(1).and_then(|s| s.to_str()) { - if !Command::has_subcommand(rewrite_legacy_subcommand(arg)) - && arg != "-h" - && arg != "--help" - && arg != "-V" - && arg != "--version" - && arg != "help" - { - args.insert(1, "check".into()); - } - } - - // Extract command-line arguments. - let Args { - command, - log_level_args, - } = Args::parse_from(args); - - #[cfg(not(debug_assertions))] - { - let default_panic_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - #[allow(clippy::print_stderr)] - { - eprintln!( - r#" -{}: `ruff` crashed. This indicates a bug in `ruff`. If you could open an issue at: - -https://github.com/charliermarsh/ruff/issues/new?title=%5BPanic%5D - -quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative! -"#, - "error".red().bold(), - ); - } - default_panic_hook(info); - })); - } - - let log_level: LogLevel = (&log_level_args).into(); - set_up_logging(&log_level)?; - - match command { - Command::Rule { rule, format } => commands::rule::rule(&rule, format)?, - Command::Config { option } => return Ok(commands::config::config(option.as_deref())), - Command::Linter { format } => commands::linter::linter(format)?, - Command::Clean => commands::clean::clean(log_level)?, - Command::GenerateShellCompletion { shell } => { - shell.generate(&mut Args::command(), &mut io::stdout()); - } - Command::Check(args) => return check(args, log_level), - } - - Ok(ExitStatus::Success) -} - -fn check(args: CheckArgs, log_level: LogLevel) -> Result { - let (cli, overrides) = args.partition(); - - // Construct the "default" settings. These are used when no `pyproject.toml` - // files are present, or files are injected from outside of the hierarchy. - let pyproject_strategy = resolve::resolve( - cli.isolated, - cli.config.as_deref(), - &overrides, - cli.stdin_filename.as_deref(), - )?; - - if cli.show_settings { - commands::show_settings::show_settings(&cli.files, &pyproject_strategy, &overrides)?; - return Ok(ExitStatus::Success); - } - if cli.show_files { - commands::show_files::show_files(&cli.files, &pyproject_strategy, &overrides)?; - return Ok(ExitStatus::Success); - } - - // Extract options that are included in `Settings`, but only apply at the top - // level. - let CliSettings { - fix, - fix_only, - format, - show_fixes, - update_check, - .. - } = pyproject_strategy.top_level_settings().cli.clone(); - - // Autofix rules are as follows: - // - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or - // print them to stdout, if we're reading from stdin). - // - Otherwise, if `--format json` is set, generate the fixes (so we print them - // out as part of the JSON payload), but don't write them to disk. - // - If `--diff` or `--fix-only` are set, don't print any violations (only - // fixes). - // TODO(charlie): Consider adding ESLint's `--fix-dry-run`, which would generate - // but not apply fixes. That would allow us to avoid special-casing JSON - // here. - let autofix = if cli.diff { - fix::FixMode::Diff - } else if fix || fix_only { - fix::FixMode::Apply - } else if matches!(format, SerializationFormat::Json) { - fix::FixMode::Generate - } else { - fix::FixMode::None - }; - let cache = !cli.no_cache; - let noqa = !cli.ignore_noqa; - let mut printer_flags = PrinterFlags::empty(); - if !(cli.diff || fix_only) { - printer_flags |= PrinterFlags::SHOW_VIOLATIONS; - } - if show_fixes { - printer_flags |= PrinterFlags::SHOW_FIXES; - } - - #[cfg(debug_assertions)] - if cache { - // `--no-cache` doesn't respect code changes, and so is often confusing during - // development. - warn_user_once!("Detected debug build without --no-cache."); - } - - if cli.add_noqa { - if !matches!(autofix, fix::FixMode::None) { - warn_user_once!("--fix is incompatible with --add-noqa."); - } - let modifications = - commands::add_noqa::add_noqa(&cli.files, &pyproject_strategy, &overrides)?; - if modifications > 0 && log_level >= LogLevel::Default { - let s = if modifications == 1 { "" } else { "s" }; - #[allow(clippy::print_stderr)] - { - eprintln!("Added {modifications} noqa directive{s}."); - } - } - return Ok(ExitStatus::Success); - } - - let printer = Printer::new(format, log_level, autofix, printer_flags); - - if cli.watch { - if !matches!(autofix, fix::FixMode::None) { - warn_user_once!("--fix is unsupported in watch mode."); - } - if format != SerializationFormat::Text { - warn_user_once!("--format 'text' is used in watch mode."); - } - - // Perform an initial run instantly. - Printer::clear_screen()?; - printer.write_to_user("Starting linter in watch mode...\n"); - - let messages = commands::run::run( - &cli.files, - &pyproject_strategy, - &overrides, - cache.into(), - noqa.into(), - fix::FixMode::None, - )?; - printer.write_continuously(&messages)?; - - // Configure the file watcher. - let (tx, rx) = channel(); - let mut watcher = recommended_watcher(tx)?; - for file in &cli.files { - watcher.watch(file, RecursiveMode::Recursive)?; - } - - 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 messages = commands::run::run( - &cli.files, - &pyproject_strategy, - &overrides, - cache.into(), - noqa.into(), - fix::FixMode::None, - )?; - printer.write_continuously(&messages)?; - } - } - Err(err) => return Err(err.into()), - } - } - } else { - let is_stdin = cli.files == vec![PathBuf::from("-")]; - - // Generate lint violations. - let diagnostics = if is_stdin { - commands::run_stdin::run_stdin( - cli.stdin_filename.map(fs::normalize_path).as_deref(), - &pyproject_strategy, - &overrides, - noqa.into(), - autofix, - )? - } else { - commands::run::run( - &cli.files, - &pyproject_strategy, - &overrides, - cache.into(), - noqa.into(), - autofix, - )? - }; - - // Always try to print violations (the printer itself may suppress output), - // unless we're writing fixes via stdin (in which case, the transformed - // source code goes to stdout). - if !(is_stdin && matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff)) { - if cli.statistics { - printer.write_statistics(&diagnostics)?; - } else { - printer.write_once(&diagnostics)?; - } - } - - if update_check { - warn_user_once!( - "update-check has been removed; setting it will cause an error in a future \ - version." - ); - } - - if !cli.exit_zero { - if cli.diff || fix_only { - if !diagnostics.fixed.is_empty() { - return Ok(ExitStatus::Failure); - } - } else if cli.exit_non_zero_on_fix { - if !diagnostics.fixed.is_empty() || !diagnostics.messages.is_empty() { - return Ok(ExitStatus::Failure); - } - } else { - if !diagnostics.messages.is_empty() { - return Ok(ExitStatus::Failure); - } - } - } - } - Ok(ExitStatus::Success) -} - -fn rewrite_legacy_subcommand(cmd: &str) -> &str { - match cmd { - "--explain" => "rule", - "--clean" => "clean", - "--generate-shell-completion" => "generate-shell-completion", - cmd => cmd, - } -} - -#[must_use] -pub fn main() -> ExitCode { - match inner_main() { - Ok(code) => code.into(), - Err(err) => { - #[allow(clippy::print_stderr)] - { - eprintln!("{}{} {err:?}", "error".red().bold(), ":".bold()); - } - ExitStatus::Error.into() - } - } -} diff --git a/crates/ruff_dev/src/generate_all.rs b/crates/ruff_dev/src/generate_all.rs index 38d35ecb58..54280cb7af 100644 --- a/crates/ruff_dev/src/generate_all.rs +++ b/crates/ruff_dev/src/generate_all.rs @@ -8,28 +8,39 @@ pub const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all"; #[derive(clap::Args)] pub struct Args { - /// Write the generated artifacts to stdout (rather than to the filesystem). - #[arg(long)] - dry_run: bool, + #[arg(long, default_value_t, value_enum)] + mode: Mode, +} + +#[derive(Copy, Clone, PartialEq, Eq, clap::ValueEnum, Default)] +pub enum Mode { + /// Update the content in the `configuration.md` + #[default] + Write, + /// Don't write to the file, check if the file is up-to-date and error if not - #[arg(long)] - check: bool, + Check, + + /// Write the generated help to stdout (rather than to `docs/configuration.md`). + DryRun, +} + +impl Mode { + const fn is_check(self) -> bool { + matches!(self, Mode::Check) + } + + pub(crate) const fn is_dry_run(self) -> bool { + matches!(self, Mode::DryRun) + } } pub fn main(args: &Args) -> Result<()> { // Not checked in - if !args.check { - generate_docs::main(&generate_docs::Args { - dry_run: args.dry_run, - })?; + if !args.mode.is_check() { + generate_docs::main(&generate_docs::Args { dry_run: true })?; } - generate_json_schema::main(&generate_json_schema::Args { - dry_run: args.dry_run, - check: args.check, - })?; - generate_cli_help::main(&generate_cli_help::Args { - dry_run: args.dry_run, - check: args.check, - })?; + generate_json_schema::main(&generate_json_schema::Args { mode: args.mode })?; + generate_cli_help::main(&generate_cli_help::Args { mode: args.mode })?; Ok(()) } diff --git a/crates/ruff_dev/src/generate_cli_help.rs b/crates/ruff_dev/src/generate_cli_help.rs index 908a42d681..7b5b0d46bc 100644 --- a/crates/ruff_dev/src/generate_cli_help.rs +++ b/crates/ruff_dev/src/generate_cli_help.rs @@ -1,13 +1,15 @@ //! Generate CLI help. -#![allow(clippy::print_stdout, clippy::print_stderr)] +#![allow(clippy::print_stdout)] use std::path::PathBuf; use std::{fs, str}; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; +use clap::CommandFactory; use pretty_assertions::StrComparison; +use ruff_cli::args; -use crate::generate_all::REGENERATE_ALL_COMMAND; +use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND}; use crate::ROOT_DIR; const COMMAND_HELP_BEGIN_PRAGMA: &str = "\n"; @@ -18,12 +20,8 @@ const SUBCOMMAND_HELP_END_PRAGMA: &str = "