diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index 4189758bb1..3ccd55c3cc 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -30,6 +30,7 @@ clearscreen = { workspace = true } colored = { workspace = true } crossbeam = { workspace = true } ctrlc = { version = "3.4.4" } +dunce = { workspace = true } indicatif = { workspace = true } jiff = { workspace = true } rayon = { workspace = true } diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index f518e028db..4d14f78486 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -2,7 +2,7 @@ use crate::logging::Verbosity; use crate::python_version::PythonVersion; use clap::error::ErrorKind; use clap::{ArgAction, ArgMatches, Error, Parser}; -use ruff_db::system::SystemPathBuf; +use ruff_db::system::{SystemPath, SystemPathBuf}; use ty_combine::Combine; use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions}; use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource}; @@ -158,7 +158,7 @@ pub(crate) struct CheckCommand { } impl CheckCommand { - pub(crate) fn into_options(self) -> Options { + pub(crate) fn into_options(self, project_root: &SystemPath) -> Options { let rules = if self.rules.is_empty() { None } else { @@ -177,6 +177,43 @@ impl CheckCommand { .no_respect_ignore_files .then_some(false) .or(self.respect_ignore_files); + + // When users explicitly specify directories on the command line, adds it as "dir/**/*" + // to include, ensuring their subfolders and subfiles are checked regardless of + // `pyproject.toml` original include configuration. + let include = { + let glob_patterns: Vec = { + self.paths + .iter() + .filter_map(|path| { + let path = path.as_std_path(); + if !path.is_dir() { + return None; + } + + let Ok(canonical_path) = dunce::canonicalize(path) else { + return None; + }; + + let relative_path = canonical_path + .strip_prefix(project_root.as_std_path()) + .ok()?; + let path_str = relative_path.to_str()?; + let path_str = path_str.replace('\\', "/"); + let normalized_path = path_str.strip_suffix('/').unwrap_or(&path_str); + + Some(RelativeGlobPattern::cli(format!("{normalized_path}/**/*"))) + }) + .collect() + }; + + if glob_patterns.is_empty() { + None + } else { + Some(RangedValue::cli(glob_patterns)) + } + }; + let options = Options { environment: Some(EnvironmentOptions { python_version: self @@ -206,6 +243,7 @@ impl CheckCommand { exclude: self.exclude.map(|excludes| { RangedValue::cli(excludes.iter().map(RelativeGlobPattern::cli).collect()) }), + include, ..SrcOptions::default() }), rules, diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index f16192bcd5..23a6727ad6 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -124,7 +124,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result { project_metadata.apply_configuration_files(&system)?; - let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options()); + let project_options_overrides = + ProjectOptionsOverrides::new(config_file, args.into_options(&project_path)); project_metadata.apply_overrides(&project_options_overrides); let mut db = ProjectDatabase::new(project_metadata, system)?;