Skip to content

Commit a3900d2

Browse files
authored
[pyflakes] Fix preview-mode bugs in F401 when attempting to autofix unused first-party submodule imports in an __init__.py file (#12569)
1 parent 83b1c48 commit a3900d2

13 files changed

+222
-88
lines changed

crates/ruff_linter/src/fix/edits.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Interface for generating fix edits from higher-level actions (e.g., "remove an argument").
22
3-
use std::borrow::Cow;
4-
53
use anyhow::{Context, Result};
64

75
use ruff_diagnostics::Edit;
@@ -126,7 +124,7 @@ pub(crate) fn remove_unused_imports<'a>(
126124

127125
/// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`.
128126
pub(crate) fn make_redundant_alias<'a>(
129-
member_names: impl Iterator<Item = Cow<'a, str>>,
127+
member_names: impl Iterator<Item = &'a str>,
130128
stmt: &Stmt,
131129
) -> Vec<Edit> {
132130
let aliases = match stmt {
@@ -527,7 +525,6 @@ fn all_lines_fit(
527525
#[cfg(test)]
528526
mod tests {
529527
use anyhow::{anyhow, Result};
530-
use std::borrow::Cow;
531528
use test_case::test_case;
532529

533530
use ruff_diagnostics::{Diagnostic, Edit, Fix};
@@ -619,23 +616,23 @@ x = 1 \
619616
let contents = "import x, y as y, z as bees";
620617
let stmt = parse_first_stmt(contents)?;
621618
assert_eq!(
622-
make_redundant_alias(["x"].into_iter().map(Cow::from), &stmt),
619+
make_redundant_alias(["x"].into_iter(), &stmt),
623620
vec![Edit::range_replacement(
624621
String::from("x as x"),
625622
TextRange::new(TextSize::new(7), TextSize::new(8)),
626623
)],
627624
"make just one item redundant"
628625
);
629626
assert_eq!(
630-
make_redundant_alias(vec!["x", "y"].into_iter().map(Cow::from), &stmt),
627+
make_redundant_alias(vec!["x", "y"].into_iter(), &stmt),
631628
vec![Edit::range_replacement(
632629
String::from("x as x"),
633630
TextRange::new(TextSize::new(7), TextSize::new(8)),
634631
)],
635632
"the second item is already a redundant alias"
636633
);
637634
assert_eq!(
638-
make_redundant_alias(vec!["x", "z"].into_iter().map(Cow::from), &stmt),
635+
make_redundant_alias(vec!["x", "z"].into_iter(), &stmt),
639636
vec![Edit::range_replacement(
640637
String::from("x as x"),
641638
TextRange::new(TextSize::new(7), TextSize::new(8)),

crates/ruff_linter/src/rules/pyflakes/mod.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod tests {
1111

1212
use anyhow::Result;
1313
use regex::Regex;
14+
use rustc_hash::FxHashMap;
1415

1516
use test_case::test_case;
1617

@@ -24,11 +25,12 @@ mod tests {
2425

2526
use crate::linter::check_path;
2627
use crate::registry::{AsRule, Linter, Rule};
28+
use crate::rules::isort;
2729
use crate::rules::pyflakes;
2830
use crate::settings::types::PreviewMode;
2931
use crate::settings::{flags, LinterSettings};
3032
use crate::source_kind::SourceKind;
31-
use crate::test::{test_path, test_snippet};
33+
use crate::test::{test_contents, test_path, test_snippet};
3234
use crate::{assert_messages, directives};
3335

3436
#[test_case(Rule::UnusedImport, Path::new("F401_0.py"))]
@@ -232,6 +234,44 @@ mod tests {
232234
Ok(())
233235
}
234236

237+
#[test_case(
238+
r"import submodule.a",
239+
"f401_preview_first_party_submodule_no_dunder_all"
240+
)]
241+
#[test_case(
242+
r"
243+
import submodule.a
244+
__all__ = ['FOO']
245+
FOO = 42",
246+
"f401_preview_first_party_submodule_dunder_all"
247+
)]
248+
fn f401_preview_first_party_submodule(contents: &str, snapshot: &str) {
249+
let diagnostics = test_contents(
250+
&SourceKind::Python(dedent(contents).to_string()),
251+
Path::new("f401_preview_first_party_submodule/__init__.py"),
252+
&LinterSettings {
253+
preview: PreviewMode::Enabled,
254+
isort: isort::settings::Settings {
255+
// This case specifically tests the scenario where
256+
// the unused import is a first-party submodule import;
257+
// use the isort settings to ensure that the `submodule.a` import
258+
// is recognised as first-party in the test:
259+
known_modules: isort::categorize::KnownModules::new(
260+
vec!["submodule".parse().unwrap()],
261+
vec![],
262+
vec![],
263+
vec![],
264+
FxHashMap::default(),
265+
),
266+
..isort::settings::Settings::default()
267+
},
268+
..LinterSettings::for_rule(Rule::UnusedImport)
269+
},
270+
)
271+
.0;
272+
assert_messages!(snapshot, diagnostics);
273+
}
274+
235275
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
236276
#[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))]
237277
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]

0 commit comments

Comments
 (0)