From 9a6e2cac54f32b6c5472bb37facfedb3b1755b98 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 23 Jan 2025 15:08:47 +0100 Subject: [PATCH] Add `check` command --- crates/red_knot/src/args.rs | 65 +++++++++++-------- crates/red_knot/src/main.rs | 20 +++--- crates/red_knot/tests/cli.rs | 2 +- scripts/knot_benchmark/src/benchmark/cases.py | 2 +- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/crates/red_knot/src/args.rs b/crates/red_knot/src/args.rs index b2c30016b60f27..df07863dbd1e4e 100644 --- a/crates/red_knot/src/args.rs +++ b/crates/red_knot/src/args.rs @@ -1,6 +1,5 @@ use crate::logging::Verbosity; use crate::python_version::PythonVersion; -use crate::Command; use clap::{ArgAction, ArgMatches, Error, Parser}; use red_knot_project::metadata::options::{EnvironmentOptions, Options}; use red_knot_project::metadata::value::{RangedValue, RelativePathBuf}; @@ -16,16 +15,23 @@ use ruff_db::system::SystemPathBuf; #[command(version)] pub(crate) struct Args { #[command(subcommand)] - pub(crate) command: Option, + pub(crate) command: Command, +} - /// Run the command within the given project directory. - /// - /// All `pyproject.toml` files will be discovered by walking up the directory tree from the given project directory, - /// as will the project's virtual environment (`.venv`) unless the `venv-path` option is set. - /// - /// Other command-line arguments (such as relative paths) will be resolved relative to the current working directory. - #[arg(long, value_name = "PROJECT")] - pub(crate) project: Option, +#[derive(Debug, clap::Subcommand)] +pub(crate) enum Command { + /// Check a project for type errors. + Check(CheckArgs), + + /// Start the language server + Server, +} + +#[derive(Debug, Parser)] +pub(crate) struct CheckArgs { + /// Run in watch mode by re-running whenever files change. + #[arg(long, short = 'W')] + pub(crate) watch: bool, /// Path to the virtual environment the project uses. /// @@ -46,28 +52,31 @@ pub(crate) struct Args { #[arg(long, value_name = "VERSION", alias = "target-version")] pub(crate) python_version: Option, - #[clap(flatten)] - pub(crate) verbosity: Verbosity, - #[clap(flatten)] pub(crate) rules: RulesArg, - /// Run in watch mode by re-running whenever files change. - #[arg(long, short = 'W')] - pub(crate) watch: bool, + /// Run the command within the given project directory. + /// + /// All `pyproject.toml` files will be discovered by walking up the directory tree from the given project directory, + /// as will the project's virtual environment (`.venv`) unless the `venv-path` option is set. + /// + /// Other command-line arguments (such as relative paths) will be resolved relative to the current working directory. + #[arg(long, value_name = "PROJECT")] + pub(crate) project: Option, + + #[clap(flatten)] + pub(crate) verbosity: Verbosity, } -impl Args { - pub(crate) fn to_options(&self) -> Options { +impl CheckArgs { + pub(crate) fn into_options(self) -> Options { let rules = if self.rules.is_empty() { None } else { Some( self.rules - .iter() - .map(|(rule, level)| { - (RangedValue::cli(rule.to_string()), RangedValue::cli(level)) - }) + .into_iter() + .map(|(rule, level)| (RangedValue::cli(rule), RangedValue::cli(level))) .collect(), ) }; @@ -77,11 +86,11 @@ impl Args { python_version: self .python_version .map(|version| RangedValue::cli(version.into())), - venv_path: self.venv_path.as_ref().map(RelativePathBuf::cli), - typeshed: self.typeshed.as_ref().map(RelativePathBuf::cli), - extra_paths: self.extra_search_path.as_ref().map(|extra_search_paths| { + venv_path: self.venv_path.map(RelativePathBuf::cli), + typeshed: self.typeshed.map(RelativePathBuf::cli), + extra_paths: self.extra_search_path.map(|extra_search_paths| { extra_search_paths - .iter() + .into_iter() .map(RelativePathBuf::cli) .collect() }), @@ -105,8 +114,8 @@ impl RulesArg { self.0.is_empty() } - fn iter(&self) -> impl Iterator { - self.0.iter().map(|(rule, level)| (rule.as_str(), *level)) + fn into_iter(self) -> impl Iterator { + self.0.into_iter() } } diff --git a/crates/red_knot/src/main.rs b/crates/red_knot/src/main.rs index 83c0950996e668..628abe06d03ddd 100644 --- a/crates/red_knot/src/main.rs +++ b/crates/red_knot/src/main.rs @@ -1,7 +1,7 @@ use std::process::{ExitCode, Termination}; use std::sync::Mutex; -use crate::args::Args; +use crate::args::{Args, CheckArgs, Command}; use crate::logging::setup_tracing; use anyhow::{anyhow, Context}; use clap::Parser; @@ -21,12 +21,6 @@ mod logging; mod python_version; mod verbosity; -#[derive(Debug, clap::Subcommand)] -pub enum Command { - /// Start the language server - Server, -} - #[allow(clippy::print_stdout, clippy::unnecessary_wraps, clippy::print_stderr)] pub fn main() -> ExitStatus { run().unwrap_or_else(|error| { @@ -52,10 +46,13 @@ pub fn main() -> ExitStatus { fn run() -> anyhow::Result { let args = Args::parse_from(std::env::args()); - if matches!(args.command, Some(Command::Server)) { - return run_server().map(|()| ExitStatus::Success); + match args.command { + Command::Server => run_server().map(|()| ExitStatus::Success), + Command::Check(check_args) => run_check(check_args), } +} +fn run_check(args: CheckArgs) -> anyhow::Result { let verbosity = args.verbosity.level(); countme::enable(verbosity.is_trace()); let _guard = setup_tracing(verbosity)?; @@ -86,7 +83,8 @@ fn run() -> anyhow::Result { .unwrap_or_else(|| cli_base_path.clone()); let system = OsSystem::new(cwd); - let cli_options = args.to_options(); + let watch = args.watch; + let cli_options = args.into_options(); let mut workspace_metadata = ProjectMetadata::discover(system.current_directory(), &system)?; workspace_metadata.apply_cli_options(cli_options.clone()); @@ -104,7 +102,7 @@ fn run() -> anyhow::Result { } })?; - let exit_status = if args.watch { + let exit_status = if watch { main_loop.watch(&mut db)? } else { main_loop.run(&mut db) diff --git a/crates/red_knot/tests/cli.rs b/crates/red_knot/tests/cli.rs index cadcb401bb0479..74d01d0bfb8765 100644 --- a/crates/red_knot/tests/cli.rs +++ b/crates/red_knot/tests/cli.rs @@ -446,7 +446,7 @@ impl TestCase { fn command(&self) -> Command { let mut command = Command::new(get_cargo_bin("red_knot")); - command.current_dir(&self.project_dir); + command.current_dir(&self.project_dir).arg("check"); command } } diff --git a/scripts/knot_benchmark/src/benchmark/cases.py b/scripts/knot_benchmark/src/benchmark/cases.py index 25eecf59210b6d..e2ef9d17ff84b6 100644 --- a/scripts/knot_benchmark/src/benchmark/cases.py +++ b/scripts/knot_benchmark/src/benchmark/cases.py @@ -68,7 +68,7 @@ def __init__(self, *, path: Path | None = None): ) def cold_command(self, project: Project, venv: Venv) -> Command: - command = [str(self.path), "-v"] + command = [str(self.path), "check", "-v"] assert len(project.include) < 2, "Knot doesn't support multiple source folders"