diff --git a/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import.snap b/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import.snap index 8425041bb7242..e011e60d2ad68 100644 --- a/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import.snap +++ b/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import.snap @@ -20,11 +20,10 @@ info: snapshot_kind: text --- success: false -exit_code: 1 +exit_code: 2 ----- stdout ----- -import pandas -1 + ----- stderr ----- -warning: (ICN001) requested alias (`pd`) for `pandas` conflicts with isort required import: `import pandas` -test.py:1:8: ICN001 `pandas` should be imported as `pd` -Found 2 errors (1 fixed, 1 remaining). +ruff failed + Cause: isort required import (I002) conflicts with unconventional import alias (ICN001): + - pandas -> pd diff --git a/crates/ruff_python_semantic/src/imports.rs b/crates/ruff_python_semantic/src/imports.rs index 6ef0725e6d497..069956e371fb1 100644 --- a/crates/ruff_python_semantic/src/imports.rs +++ b/crates/ruff_python_semantic/src/imports.rs @@ -57,7 +57,7 @@ impl NameImport { } /// Returns the [`QualifiedName`] of the imported name (e.g., given `from foo import bar as baz`, returns `["foo", "bar"]`). - fn qualified_name(&self) -> QualifiedName { + pub fn qualified_name(&self) -> QualifiedName { match self { NameImport::Import(import) => QualifiedName::user_defined(&import.name.name), NameImport::ImportFrom(import_from) => collect_import_from_member( diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 22436db9c92ae..0d4111bbf2f1a 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -25,7 +25,7 @@ use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::registry::RuleNamespace; use ruff_linter::registry::{Rule, RuleSet, INCOMPATIBLE_CODES}; use ruff_linter::rule_selector::{PreviewOptions, Specificity}; -use ruff_linter::rules::pycodestyle; +use ruff_linter::rules::{flake8_import_conventions, isort, pycodestyle}; use ruff_linter::settings::fix_safety_table::FixSafetyTable; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ @@ -232,6 +232,21 @@ impl Configuration { let line_length = self.line_length.unwrap_or_default(); + let rules = lint.as_rule_table(lint_preview)?; + + // LinterSettings validation + let isort = lint + .isort + .map(IsortOptions::try_into_settings) + .transpose()? + .unwrap_or_default(); + let flake8_import_conventions = lint + .flake8_import_conventions + .map(Flake8ImportConventionsOptions::into_settings) + .unwrap_or_default(); + + conflicting_import_settings(&isort, &flake8_import_conventions)?; + Ok(Settings { cache_dir: self .cache_dir @@ -259,7 +274,7 @@ impl Configuration { #[allow(deprecated)] linter: LinterSettings { - rules: lint.as_rule_table(lint_preview)?, + rules, exclude: FilePatternSet::try_from_iter(lint.exclude.unwrap_or_default())?, extension: self.extension.unwrap_or_default(), preview: lint_preview, @@ -341,10 +356,7 @@ impl Configuration { .flake8_implicit_str_concat .map(Flake8ImplicitStrConcatOptions::into_settings) .unwrap_or_default(), - flake8_import_conventions: lint - .flake8_import_conventions - .map(Flake8ImportConventionsOptions::into_settings) - .unwrap_or_default(), + flake8_import_conventions, flake8_pytest_style: lint .flake8_pytest_style .map(Flake8PytestStyleOptions::try_into_settings) @@ -374,11 +386,7 @@ impl Configuration { .flake8_gettext .map(Flake8GetTextOptions::into_settings) .unwrap_or_default(), - isort: lint - .isort - .map(IsortOptions::try_into_settings) - .transpose()? - .unwrap_or_default(), + isort, mccabe: lint .mccabe .map(McCabeOptions::into_settings) @@ -1553,6 +1561,30 @@ fn warn_about_deprecated_top_level_lint_options( ); } +/// Detect conflicts between I002 (missing-required-import) and ICN001 (unconventional-import-alias) +fn conflicting_import_settings( + isort: &isort::settings::Settings, + flake8_import_conventions: &flake8_import_conventions::settings::Settings, +) -> Result<()> { + use std::fmt::Write; + let mut err_body = String::new(); + for required_import in &isort.required_imports { + let name = required_import.qualified_name().to_string(); + if let Some(alias) = flake8_import_conventions.aliases.get(&name) { + writeln!(err_body, " - {name} -> {alias}").unwrap(); + } + } + + if !err_body.is_empty() { + return Err(anyhow!( + "isort required import (I002) conflicts with unconventional import alias (ICN001):\ + \n{err_body}" + )); + } + + Ok(()) +} + #[cfg(test)] mod tests { use std::str::FromStr;