feat: add option to toggle type checking imports detection

Commit adds functionality to enabled or disable type checking imports,
either as a cli arg or a config file
This commit is contained in:
Gautham Venkataraman 2025-11-15 13:32:24 +01:00
parent 698231a47a
commit b5c24fe923
6 changed files with 52 additions and 1 deletions

View file

@ -167,6 +167,7 @@ pub enum AnalyzeCommand {
}
#[derive(Clone, Debug, clap::Parser)]
#[expect(clippy::struct_excessive_bools)]
pub struct AnalyzeGraphCommand {
/// List of files or directories to include.
#[clap(help = "List of files or directories to include [default: .]")]
@ -193,6 +194,11 @@ pub struct AnalyzeGraphCommand {
/// Path to a virtual environment to use for resolving additional dependencies
#[arg(long)]
python: Option<PathBuf>,
/// Include imports that are only used for type checking (i.e., imports within `if TYPE_CHECKING:` blocks). Use `--no-type-checking-imports` to disable.
#[arg(long, overrides_with("no_type_checking_imports"))]
type_checking_imports: bool,
#[arg(long, overrides_with("type_checking_imports"), hide = true)]
no_type_checking_imports: bool,
}
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
@ -839,6 +845,10 @@ impl AnalyzeGraphCommand {
string_imports_min_dots: self.min_dots,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
target_version: self.target_version.map(ast::PythonVersion::from),
type_checking_imports: resolve_bool_arg(
self.type_checking_imports,
self.no_type_checking_imports,
),
..ExplicitConfigOverrides::default()
};
@ -1335,6 +1345,7 @@ struct ExplicitConfigOverrides {
extension: Option<Vec<ExtensionPair>>,
detect_string_imports: Option<bool>,
string_imports_min_dots: Option<usize>,
type_checking_imports: Option<bool>,
}
impl ConfigurationTransformer for ExplicitConfigOverrides {
@ -1425,6 +1436,9 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
config.analyze.string_imports_min_dots = Some(*string_imports_min_dots);
}
if let Some(type_checking_imports) = &self.type_checking_imports {
config.analyze.type_checking_imports = Some(*type_checking_imports);
}
config
}

View file

@ -105,6 +105,7 @@ pub(crate) fn analyze_graph(
let settings = resolver.resolve(path);
let string_imports = settings.analyze.string_imports;
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
let type_checking_imports = settings.analyze.type_checking_imports;
// Skip excluded files.
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
@ -167,6 +168,7 @@ pub(crate) fn analyze_graph(
&path,
package.as_deref(),
string_imports,
type_checking_imports,
)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");

View file

@ -30,6 +30,7 @@ impl ModuleImports {
path: &SystemPath,
package: Option<&SystemPath>,
string_imports: StringImports,
type_checking_imports: bool,
) -> Result<Self> {
// Parse the source code.
let parsed = parse(source, ParseOptions::from(source_type))?;

View file

@ -6,7 +6,7 @@ use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Default, Clone, CacheKey)]
#[derive(Debug, Clone, CacheKey)]
pub struct AnalyzeSettings {
pub exclude: FilePatternSet,
pub preview: PreviewMode,
@ -14,6 +14,21 @@ pub struct AnalyzeSettings {
pub string_imports: StringImports,
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
pub extension: ExtensionMapping,
pub type_checking_imports: bool,
}
impl Default for AnalyzeSettings {
fn default() -> Self {
Self {
exclude: FilePatternSet::default(),
preview: PreviewMode::default(),
target_version: PythonVersion::default(),
string_imports: StringImports::default(),
include_dependencies: BTreeMap::default(),
extension: ExtensionMapping::default(),
type_checking_imports: true,
}
}
}
impl fmt::Display for AnalyzeSettings {
@ -29,6 +44,7 @@ impl fmt::Display for AnalyzeSettings {
self.string_imports,
self.extension | debug,
self.include_dependencies | debug,
self.type_checking_imports,
]
}
Ok(())

View file

@ -232,6 +232,9 @@ impl Configuration {
include_dependencies: analyze
.include_dependencies
.unwrap_or(analyze_defaults.include_dependencies),
type_checking_imports: analyze
.type_checking_imports
.unwrap_or(analyze_defaults.type_checking_imports),
};
let lint = self.lint;
@ -1277,6 +1280,7 @@ pub struct AnalyzeConfiguration {
pub detect_string_imports: Option<bool>,
pub string_imports_min_dots: Option<usize>,
pub include_dependencies: Option<BTreeMap<PathBuf, (PathBuf, Vec<String>)>>,
pub type_checking_imports: Option<bool>,
}
impl AnalyzeConfiguration {
@ -1303,6 +1307,7 @@ impl AnalyzeConfiguration {
})
.collect::<BTreeMap<_, _>>()
}),
type_checking_imports: options.type_checking_imports,
})
}
@ -1317,6 +1322,7 @@ impl AnalyzeConfiguration {
.string_imports_min_dots
.or(config.string_imports_min_dots),
include_dependencies: self.include_dependencies.or(config.include_dependencies),
type_checking_imports: self.type_checking_imports.or(config.type_checking_imports),
}
}
}

View file

@ -3892,6 +3892,18 @@ pub struct AnalyzeOptions {
"#
)]
pub include_dependencies: Option<BTreeMap<PathBuf, Vec<String>>>,
/// Whether to include imports that are only used for type checking (i.e., imports within `if TYPE_CHECKING:` blocks).
/// When enabled (default), type-checking-only imports are included in the import graph.
/// When disabled, they are excluded.
#[option(
default = "true",
value_type = "bool",
example = r#"
# Exclude type-checking-only imports from the graph
type-checking-imports = false
"#
)]
pub type_checking_imports: Option<bool>,
}
/// Like [`LintCommonOptions`], but with any `#[serde(flatten)]` fields inlined. This leads to far,