diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029_0.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029.py rename to crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029_0.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029_1.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029_1.py new file mode 100644 index 0000000000..a9634d41b9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP029_1.py @@ -0,0 +1 @@ +from builtins import str, int \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 313c0e0da0..bf8e7495f9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -97,7 +97,7 @@ mod tests { )] #[test_case(Rule::UTF8EncodingDeclaration, Path::new("UP009_many_empty_lines.py"))] #[test_case(Rule::UnicodeKindPrefix, Path::new("UP025.py"))] - #[test_case(Rule::UnnecessaryBuiltinImport, Path::new("UP029.py"))] + #[test_case(Rule::UnnecessaryBuiltinImport, Path::new("UP029_0.py"))] #[test_case(Rule::UnnecessaryClassParentheses, Path::new("UP039.py"))] #[test_case(Rule::UnnecessaryDefaultTypeArgs, Path::new("UP043.py"))] #[test_case(Rule::UnnecessaryEncodeUTF8, Path::new("UP012.py"))] @@ -357,6 +357,32 @@ mod tests { "); } + #[test_case(Path::new("UP029_1.py"))] + fn i002_up029_conflict(path: &Path) -> Result<()> { + let snapshot = format!("{}_skip_required_imports", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("pyupgrade").join(path).as_path(), + &settings::LinterSettings { + isort: isort::settings::Settings { + required_imports: BTreeSet::from_iter([ + // https://github.com/astral-sh/ruff/issues/20601 + NameImport::ImportFrom(MemberNameImport::member( + "builtins".to_string(), + "str".to_string(), + )), + ]), + ..Default::default() + }, + ..settings::LinterSettings::for_rules([ + Rule::MissingRequiredImport, + Rule::UnnecessaryBuiltinImport, + ]) + }, + )?; + assert_diagnostics!(snapshot, diagnostics); + Ok(()) + } + #[test] fn unnecessary_default_type_args_stubs_py312_preview() -> Result<()> { let snapshot = format!("{}__preview", "UP043.pyi"); diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs index a01fe8d23a..c2675200e0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs @@ -6,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::rules::pyupgrade::rules::is_import_required_by_isort; use crate::{AlwaysFixableViolation, Fix}; /// ## What it does @@ -85,6 +86,13 @@ pub(crate) fn unnecessary_builtin_import( // Identify unaliased, builtin imports. let unused_imports: Vec<&Alias> = names .iter() + .filter(|alias| { + !is_import_required_by_isort( + &checker.settings().isort.required_imports, + stmt.into(), + alias, + ) + }) .filter(|alias| alias.asname.is_none()) .filter(|alias| { matches!( diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_0.py.snap similarity index 96% rename from crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029.py.snap rename to crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_0.py.snap index 8016f13af0..841fbed2f6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_0.py.snap @@ -2,7 +2,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- UP029 [*] Unnecessary builtin import: `*` - --> UP029.py:1:1 + --> UP029_0.py:1:1 | 1 | from builtins import * | ^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ help: Remove unnecessary builtin import note: This is an unsafe fix and may change runtime behavior UP029 [*] Unnecessary builtin imports: `ascii`, `bytes` - --> UP029.py:2:1 + --> UP029_0.py:2:1 | 1 | from builtins import * 2 | from builtins import ascii, bytes, compile @@ -35,7 +35,7 @@ help: Remove unnecessary builtin import note: This is an unsafe fix and may change runtime behavior UP029 [*] Unnecessary builtin imports: `filter`, `zip` - --> UP029.py:4:1 + --> UP029_0.py:4:1 | 2 | from builtins import ascii, bytes, compile 3 | from builtins import str as _str @@ -56,7 +56,7 @@ help: Remove unnecessary builtin import note: This is an unsafe fix and may change runtime behavior UP029 [*] Unnecessary builtin import: `open` - --> UP029.py:5:1 + --> UP029_0.py:5:1 | 3 | from builtins import str as _str 4 | from six.moves import filter, zip, zip_longest diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_1.py_skip_required_imports.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_1.py_skip_required_imports.snap new file mode 100644 index 0000000000..9b5a2b1f24 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP029_1.py_skip_required_imports.snap @@ -0,0 +1,13 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP029 [*] Unnecessary builtin import: `int` + --> UP029_1.py:1:1 + | +1 | from builtins import str, int + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Remove unnecessary builtin import + - from builtins import str, int +1 + from builtins import str +note: This is an unsafe fix and may change runtime behavior