diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index 4aa40ea1d5..4d8426e6ff 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -2175,6 +2175,20 @@ fn flake8_import_convention_unused_aliased_import() { .pass_stdin("1")); } +#[test] +fn flake8_import_convention_unused_aliased_import_no_conflict() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .arg("--config") + .arg(r#"lint.isort.required-imports = ["import pandas as pd"]"#) + .args(["--select", "I002,ICN001,F401"]) + .args(["--stdin-filename", "test.py"]) + .arg("--unsafe-fixes") + .arg("--fix") + .arg("-") + .pass_stdin("1")); +} + /// Test that private, old-style `TypeVar` generics /// 1. Get replaced with PEP 695 type parameters (UP046, UP047) /// 2. Get renamed to remove leading underscores (UP049) diff --git a/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import_no_conflict.snap b/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import_no_conflict.snap new file mode 100644 index 0000000000..b074787f9c --- /dev/null +++ b/crates/ruff/tests/snapshots/lint__flake8_import_convention_unused_aliased_import_no_conflict.snap @@ -0,0 +1,27 @@ +--- +source: crates/ruff/tests/lint.rs +info: + program: ruff + args: + - check + - "--no-cache" + - "--output-format" + - concise + - "--config" + - "lint.isort.required-imports = [\"import pandas as pd\"]" + - "--select" + - "I002,ICN001,F401" + - "--stdin-filename" + - test.py + - "--unsafe-fixes" + - "--fix" + - "-" + stdin: "1" +--- +success: true +exit_code: 0 +----- stdout ----- +import pandas as pd +1 +----- stderr ----- +Found 1 error (1 fixed, 0 remaining). diff --git a/crates/ruff_python_semantic/src/imports.rs b/crates/ruff_python_semantic/src/imports.rs index 069956e371..f340ec9919 100644 --- a/crates/ruff_python_semantic/src/imports.rs +++ b/crates/ruff_python_semantic/src/imports.rs @@ -43,7 +43,7 @@ impl NameImports { impl NameImport { /// Returns the name under which the member is bound (e.g., given `from foo import bar as baz`, returns `baz`). - fn bound_name(&self) -> &str { + pub fn bound_name(&self) -> &str { match self { NameImport::Import(import) => { import.name.as_name.as_deref().unwrap_or(&import.name.name) diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 53e3623095..66e3015e6a 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -1576,9 +1576,20 @@ fn conflicting_import_settings( 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(); + // Ex: `from foo import bar as baz` OR `import foo.bar as baz` + // - qualified name: `foo.bar` + // - bound name: `baz` + // - conflicts with: `{"foo.bar":"buzz"}` + // - does not conflict with either of + // - `{"bar":"buzz"}` + // - `{"foo.bar":"baz"}` + let qualified_name = required_import.qualified_name().to_string(); + let bound_name = required_import.bound_name(); + let Some(alias) = flake8_import_conventions.aliases.get(&qualified_name) else { + continue; + }; + if alias != bound_name { + writeln!(err_body, " - `{qualified_name}` -> `{alias}`").unwrap(); } }