From fe7016ee5a4295d0d099b631e4e9e2a224012bd8 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 2 May 2024 17:14:01 -0700 Subject: [PATCH 01/49] Revert "patch to remove the unfinished dunder_all mutation" This reverts commit 31768b6f8effebc22098e78a837f6e0c8fe44594. --- .../src/rules/pyflakes/rules/unused_import.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 963bc8dd2e19b2..3a3302e8c863ba 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -18,7 +18,7 @@ use crate::rules::{isort, isort::ImportSection, isort::ImportType}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum UnusedImportContext { ExceptHandler, - Init { first_party: bool }, + Init { first_party: bool, dunder_all: bool }, } /// ## What it does @@ -108,7 +108,14 @@ impl Violation for UnusedImport { fn fix_title(&self) -> Option { let UnusedImport { name, multiple, .. } = self; let resolution = match self.context { - Some(UnusedImportContext::Init { first_party: true }) => "Use a redundant alias", + Some(UnusedImportContext::Init { + first_party: true, + dunder_all: true, + }) => "Add unused import to __all__", + Some(UnusedImportContext::Init { + first_party: true, + dunder_all: false, + }) => "Use a redundant alias", _ => "Remove unused import", }; Some(if *multiple { @@ -140,7 +147,7 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { /// For some unused binding in an import statement... /// -/// __init__.py ∧ 1stpty → safe, convert to redundant-alias +/// __init__.py ∧ 1stpty → safe, move to __all__ or convert to explicit-import /// __init__.py ∧ stdlib → unsafe, remove /// __init__.py ∧ 3rdpty → unsafe, remove /// @@ -197,6 +204,8 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut let in_init = checker.path().ends_with("__init__.py"); let fix_init = checker.settings.preview.is_enabled(); + // TODO: find the `__all__` node + let dunder_all = None; // Generate a diagnostic for every import, but share fixes across all imports within the same // statement (excluding those that are ignored). @@ -225,6 +234,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut level, checker, ), + dunder_all: dunder_all.is_some(), }) } else { None @@ -234,7 +244,10 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut .partition(|(_, context)| { matches!( context, - Some(UnusedImportContext::Init { first_party: true }) + Some(UnusedImportContext::Init { + first_party: true, + .. + }) ) }); @@ -252,6 +265,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut checker, import_statement, to_reexport.iter().map(|(binding, _)| binding), + dunder_all, ) .ok(), ) @@ -364,12 +378,13 @@ fn fix_by_removing_imports<'a>( ) } -/// Generate a [`Fix`] to make bindings in a statement explicit, by changing from `import a` to -/// `import a as a`. +/// Generate a [`Fix`] to make bindings in a statement explicit, either by adding them to `__all__` +/// or changing them from `import a` to `import a as a`. fn fix_by_reexporting<'a>( checker: &Checker, node_id: NodeId, imports: impl Iterator>, + dunder_all: Option, ) -> Result { let statement = checker.semantic().statement(node_id); @@ -380,7 +395,10 @@ fn fix_by_reexporting<'a>( bail!("Expected import bindings"); } - let edits = fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement); + let edits = match dunder_all { + Some(_dunder_all) => bail!("Not implemented: add to dunder_all"), + None => fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement), + }; // Only emit a fix if there are edits let mut tail = edits.into_iter(); From 41584e17c88aad2e4492aa1fda8125d7ff791229 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Fri, 3 May 2024 15:06:01 -0700 Subject: [PATCH 02/49] charlie patch w/proto dunder_all finder --- .../src/rules/pyflakes/rules/unused_import.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 3a3302e8c863ba..ce3a5fbe46b9de 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -145,6 +145,21 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { } } +fn dunder_all<'a>(binding: &Binding, semantic: &'a SemanticModel) -> Option<&'a ast::ExprList> { + let statement = binding.statement(semantic)?; + match statement { + Stmt::Assign(ast::StmtAssign { value, .. }) => { + let Expr::List(list) = &**value else { + return None; + }; + Some(&list) + } + Stmt::AnnAssign(_) => todo!(), + Stmt::AugAssign(_) => todo!(), + _ => None, + } +} + /// For some unused binding in an import statement... /// /// __init__.py ∧ 1stpty → safe, move to __all__ or convert to explicit-import From 61cca6c78e16f85b78156d0ee4bceb6d0b1b2d48 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Fri, 3 May 2024 15:50:40 -0700 Subject: [PATCH 03/49] find_dunder_all_expr implemented --- .../src/rules/pyflakes/rules/unused_import.rs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index ce3a5fbe46b9de..4a7466381da8b5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -145,17 +145,24 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { } } -fn dunder_all<'a>(binding: &Binding, semantic: &'a SemanticModel) -> Option<&'a ast::ExprList> { - let statement = binding.statement(semantic)?; - match statement { - Stmt::Assign(ast::StmtAssign { value, .. }) => { - let Expr::List(list) = &**value else { - return None; - }; - Some(&list) - } - Stmt::AnnAssign(_) => todo!(), - Stmt::AugAssign(_) => todo!(), +/// Find the `ExprList` for a top level `__all__`, if one exists. +fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::ExprList> { + let stmt = semantic + .global_scope() + .get_all("__all__") + .map(|binding_id| &semantic.bindings[binding_id]) + .find_map(|binding| match binding.kind { + BindingKind::Export(_) => binding.statement(semantic), + _ => None, + })?; + let expr = match stmt { + Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), + Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), + Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(&**value), + _ => None, + }?; + match expr { + ast::Expr::List(list) => Some(list), _ => None, } } From 453c905aab9b1f8cc1b48f2c835964b3ddce417e Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Fri, 3 May 2024 16:01:00 -0700 Subject: [PATCH 04/49] pass ExprList to fix_by_reexporting --- .../src/rules/pyflakes/rules/unused_import.rs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 4a7466381da8b5..95e4605300cb8a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -6,8 +6,11 @@ use rustc_hash::FxHashMap; use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast as ast; use ruff_python_ast::{Stmt, StmtImportFrom}; -use ruff_python_semantic::{AnyImport, Exceptions, Imported, NodeId, Scope}; +use ruff_python_semantic::{ + AnyImport, BindingKind, Exceptions, Imported, NodeId, Scope, SemanticModel, +}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -226,8 +229,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut let in_init = checker.path().ends_with("__init__.py"); let fix_init = checker.settings.preview.is_enabled(); - // TODO: find the `__all__` node - let dunder_all = None; + let dunder_all_expr = find_dunder_all_expr(checker.semantic()); // Generate a diagnostic for every import, but share fixes across all imports within the same // statement (excluding those that are ignored). @@ -256,7 +258,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut level, checker, ), - dunder_all: dunder_all.is_some(), + dunder_all: dunder_all_expr.is_some(), }) } else { None @@ -287,7 +289,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut checker, import_statement, to_reexport.iter().map(|(binding, _)| binding), - dunder_all, + dunder_all_expr, ) .ok(), ) @@ -402,11 +404,11 @@ fn fix_by_removing_imports<'a>( /// Generate a [`Fix`] to make bindings in a statement explicit, either by adding them to `__all__` /// or changing them from `import a` to `import a as a`. -fn fix_by_reexporting<'a>( +fn fix_by_reexporting<'a, 'b>( checker: &Checker, node_id: NodeId, imports: impl Iterator>, - dunder_all: Option, + dunder_all: Option<&ast::ExprList>, ) -> Result { let statement = checker.semantic().statement(node_id); @@ -418,7 +420,9 @@ fn fix_by_reexporting<'a>( } let edits = match dunder_all { - Some(_dunder_all) => bail!("Not implemented: add to dunder_all"), + Some(dunder_all) => { + fix::edits::add_to_dunder_all(member_names.iter().map(AsRef::as_ref), dunder_all) + } None => fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement), }; From 628053ed55695d0f6e4364e5378b42b946efd6b1 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Fri, 3 May 2024 16:03:27 -0700 Subject: [PATCH 05/49] slight cleanup of branch arms readability --- .../ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 95e4605300cb8a..4b59d01881c20b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -418,12 +418,11 @@ fn fix_by_reexporting<'a, 'b>( if member_names.is_empty() { bail!("Expected import bindings"); } + let member_names = member_names.iter().map(AsRef::as_ref); let edits = match dunder_all { - Some(dunder_all) => { - fix::edits::add_to_dunder_all(member_names.iter().map(AsRef::as_ref), dunder_all) - } - None => fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement), + Some(dunder_all) => fix::edits::add_to_dunder_all(member_names, dunder_all), + None => fix::edits::make_redundant_alias(member_names, statement), }; // Only emit a fix if there are edits From 59d96aeb3c956e536623757c0fc835c31e37f1a8 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Fri, 3 May 2024 17:28:59 -0700 Subject: [PATCH 06/49] rename F401_25__all fixture test to F401_25__all_nonempty --- .../{F401_25__all => F401_25__all_nonempty}/__init__.py | 2 +- .../pyflakes/{F401_25__all => F401_25__all_nonempty}/aliased.py | 0 .../{F401_25__all => F401_25__all_nonempty}/exported.py | 0 .../pyflakes/{F401_25__all => F401_25__all_nonempty}/renamed.py | 0 .../pyflakes/{F401_25__all => F401_25__all_nonempty}/unused.py | 0 .../pyflakes/{F401_25__all => F401_25__all_nonempty}/used.py | 0 crates/ruff_linter/src/rules/pyflakes/mod.rs | 2 +- ...ests__preview__F401_F401_25__all_nonempty____init__.py.snap} | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/__init__.py (93%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/aliased.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/exported.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/renamed.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/unused.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_25__all => F401_25__all_nonempty}/used.py (100%) rename crates/ruff_linter/src/rules/pyflakes/snapshots/{ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all____init__.py.snap => ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap} (100%) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py similarity index 93% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/__init__.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py index 3d6f86c663ca42..633b6c2a1e521f 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py @@ -1,4 +1,4 @@ -"""__init__.py with __all__ +"""__init__.py with nonempty __all__ Unused stdlib and third party imports are unsafe removals diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/aliased.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/aliased.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/aliased.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/aliased.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/exported.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/exported.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/exported.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/exported.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/renamed.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/renamed.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/renamed.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/unused.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/unused.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/unused.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/used.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/used.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all/used.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/used.py diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index b4f361809878bb..55747fa82b9f28 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -207,7 +207,7 @@ mod tests { #[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))] #[test_case(Rule::UnusedImport, Path::new("__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))] - #[test_case(Rule::UnusedImport, Path::new("F401_25__all/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all____init__.py.snap rename to crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap From 5b1f3e5812e37cd71bf84ff35df234d673c2d52d Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 10:30:26 -0700 Subject: [PATCH 07/49] remove unnecessary pattern match --- .../src/rules/pyflakes/rules/unused_import.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 4b59d01881c20b..e561ae8129eda9 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -323,21 +323,16 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut // Separately, generate a diagnostic for every _ignored_ import, to ensure that the // suppression comments aren't marked as unused. - for ImportBinding { - import, - range, - parent_range, - } in ignored.into_values().flatten() - { + for binding in ignored.into_values().flatten() { let mut diagnostic = Diagnostic::new( UnusedImport { - name: import.qualified_name().to_string(), + name: binding.import.qualified_name().to_string(), context: None, multiple: false, }, - range, + binding.range, ); - if let Some(range) = parent_range { + if let Some(range) = binding.parent_range { diagnostic.set_parent(range.start()); } diagnostics.push(diagnostic); From 1d16c8be523af2e9aa40557ea1e1c3b6a8178854 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 11:23:10 -0700 Subject: [PATCH 08/49] new field in ImportBinding which is the name of the binding --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index e561ae8129eda9..33c1c7ecff0d6d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -205,6 +205,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut }; let import = ImportBinding { + name: binding.name(checker.locator()), import, range: binding.range(), parent_range: binding.parent_range(checker.semantic()), @@ -342,6 +343,8 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut /// An unused import with its surrounding context. #[derive(Debug)] struct ImportBinding<'a> { + /// Name of the binding, which for renamed imports will differ from the qualified name. + name: &'a str, /// The qualified name of the import (e.g., `typing.List` for `from typing import List`). import: AnyImport<'a, 'a>, /// The trimmed range of the import (e.g., `List` in `from typing import List`). From ff5f82f76726eefcbd3bba92dab7c92de1b90ab7 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 11:24:22 -0700 Subject: [PATCH 09/49] change fix_bind_reexporting call add_to_dunder_all with the list of binding names, /not import member_names/ to fix bug handling renamed imports; also to use a slice imput (per micha) --- .../src/rules/pyflakes/rules/unused_import.rs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 33c1c7ecff0d6d..95b513fbecc7c3 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -289,7 +289,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut fix_by_reexporting( checker, import_statement, - to_reexport.iter().map(|(binding, _)| binding), + &to_reexport.iter().map(|(b, _)| b).collect::>(), dunder_all_expr, ) .ok(), @@ -402,25 +402,28 @@ fn fix_by_removing_imports<'a>( /// Generate a [`Fix`] to make bindings in a statement explicit, either by adding them to `__all__` /// or changing them from `import a` to `import a as a`. -fn fix_by_reexporting<'a, 'b>( +fn fix_by_reexporting( checker: &Checker, node_id: NodeId, - imports: impl Iterator>, + imports: &[&ImportBinding], dunder_all: Option<&ast::ExprList>, ) -> Result { let statement = checker.semantic().statement(node_id); - - let member_names = imports - .map(|binding| binding.import.member_name()) - .collect::>(); - if member_names.is_empty() { + if imports.is_empty() { bail!("Expected import bindings"); } - let member_names = member_names.iter().map(AsRef::as_ref); let edits = match dunder_all { - Some(dunder_all) => fix::edits::add_to_dunder_all(member_names, dunder_all), - None => fix::edits::make_redundant_alias(member_names, statement), + Some(dunder_all) => { + fix::edits::add_to_dunder_all(imports.iter().map(|b| b.name), dunder_all) + } + None => { + let member_names = imports + .iter() + .map(|b| b.import.member_name()) + .collect::>(); + fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement) + } }; // Only emit a fix if there are edits From 93f0bc31c6d21343276220accb08b43dece0ecb8 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 11:25:59 -0700 Subject: [PATCH 10/49] uncomment and review test cases for F401_25 fixture test --- .../F401_25__all_nonempty/__init__.py | 4 +-- ...01_F401_25__all_nonempty____init__.py.snap | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py index 633b6c2a1e521f..0f792c90c70732 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py @@ -33,10 +33,10 @@ from . import exported # Ok: is exported in __all__ -# from . import unused # F401: add to __all__ +from . import unused # F401: add to __all__ -# from . import renamed as bees # F401: add to __all__ +from . import renamed as bees # F401: add to __all__ __all__ = ["argparse", "exported"] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 43c550ba55f7d0..99f9ef8f01918e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -16,3 +16,31 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding 20 19 | 21 20 | 22 21 | # first-party + +__init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +36 | from . import unused # F401: add to __all__ + | ^^^^^^ F401 + | + = help: Add unused import to __all__: `.unused` + +ℹ Safe fix +39 39 | from . import renamed as bees # F401: add to __all__ +40 40 | +41 41 | +42 |-__all__ = ["argparse", "exported"] + 42 |+__all__ = ["argparse", "exported", "unused"] + +__init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +39 | from . import renamed as bees # F401: add to __all__ + | ^^^^ F401 + | + = help: Add unused import to __all__: `.renamed` + +ℹ Safe fix +39 39 | from . import renamed as bees # F401: add to __all__ +40 40 | +41 41 | +42 |-__all__ = ["argparse", "exported"] + 42 |+__all__ = ["argparse", "exported", "bees"] From 07bd559fb4e0b274c0d7d708990479ed77ab8e9a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 11:42:25 -0700 Subject: [PATCH 11/49] add edit to insert binding names into __all__ --- crates/ruff_linter/src/fix/edits.rs | 64 +++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 3ce660b70ce282..be113f8995165d 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use ruff_diagnostics::Edit; use ruff_python_ast::parenthesize::parenthesized_range; -use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt}; +use ruff_python_ast::{self as ast, Arguments, ExceptHandler, ExprList, Stmt}; use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; @@ -144,6 +144,25 @@ pub(crate) fn make_redundant_alias<'a>( .collect() } +/// Fix to add the specified imports to the `__all__` export list. +pub(crate) fn add_to_dunder_all<'a>( + names: impl Iterator, + dunder_all: &ExprList, +) -> Vec { + let insertion_point = dunder_all + .elts + .last() + .map(|expr| expr.range().end()) + .unwrap_or(dunder_all.range.end() - TextSize::from(1)); + names + .enumerate() + .map(|(offset, name)| match dunder_all.elts.len() + offset { + 0 => Edit::insertion(format!("{name:?}"), insertion_point), + _ => Edit::insertion(format!(", {name:?}"), insertion_point), + }) + .collect() +} + #[derive(Debug, Copy, Clone)] pub(crate) enum Parentheses { /// Remove parentheses, if the removed argument is the only argument left. @@ -480,11 +499,14 @@ mod tests { use anyhow::Result; use ruff_diagnostics::Edit; - use ruff_python_parser::parse_suite; + use ruff_python_ast as ast; + use ruff_python_parser::{parse_expression, parse_suite}; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; - use crate::fix::edits::{make_redundant_alias, next_stmt_break, trailing_semicolon}; + use crate::fix::edits::{ + add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, + }; #[test] fn find_semicolon() -> Result<()> { @@ -586,4 +608,40 @@ x = 1 \ "the third item is already aliased to something else" ); } + + #[test] + fn add_to_empty_dunder_all() { + let Ok(ast::Expr::List(ref list)) = parse_expression("[]") else { + panic!("expected a list"); + }; + assert_eq!( + add_to_dunder_all(["x"].into_iter(), list), + vec![Edit::insertion(String::from("\"x\""), TextSize::new(1))], + ); + assert_eq!( + add_to_dunder_all(["x", "y"].into_iter(), list), + vec![ + Edit::insertion(String::from("\"x\""), TextSize::new(1)), + Edit::insertion(String::from(", \"y\""), TextSize::new(1)) + ], + ); + } + + #[test] + fn add_to_nonempty_dunder_all() { + let Ok(ast::Expr::List(ref list)) = parse_expression("[\"a\", \"b\"]") else { + panic!("expected a list"); + }; + assert_eq!( + add_to_dunder_all(["x"].into_iter(), list), + vec![Edit::insertion(String::from(", \"x\""), TextSize::new(9))], + ); + assert_eq!( + add_to_dunder_all(["x", "y"].into_iter(), list), + vec![ + Edit::insertion(String::from(", \"x\""), TextSize::new(9)), + Edit::insertion(String::from(", \"y\""), TextSize::new(9)) + ], + ); + } } From 4c3895022ad0f17d2047b7d2c64435d20f7936cc Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 11:57:11 -0700 Subject: [PATCH 12/49] fixture tests to more thoroughly explore the cases of the "add to __all__" feature --- .../pyflakes/F401_26__all_empty/__init__.py | 12 +++++++ .../pyflakes/F401_26__all_empty/renamed.py | 1 + .../pyflakes/F401_26__all_empty/unused.py | 1 + .../F401_27__all_mistyped/__init__.py | 11 +++++++ .../pyflakes/F401_27__all_mistyped/renamed.py | 1 + .../pyflakes/F401_27__all_mistyped/unused.py | 1 + .../F401_28__all_multiple/__init__.py | 9 ++++++ .../pyflakes/F401_28__all_multiple/renamed.py | 1 + .../pyflakes/F401_28__all_multiple/unused.py | 1 + .../pyflakes/F401_29__all_pluseq/__init__.py | 16 ++++++++++ .../pyflakes/F401_29__all_pluseq/renamed.py | 1 + .../pyflakes/F401_29__all_pluseq/unused.py | 1 + crates/ruff_linter/src/rules/pyflakes/mod.rs | 4 +++ ..._F401_F401_26__all_empty____init__.py.snap | 32 +++++++++++++++++++ ...01_F401_27__all_mistyped____init__.py.snap | 26 +++++++++++++++ ...01_F401_28__all_multiple____init__.py.snap | 32 +++++++++++++++++++ ...F401_F401_29__all_pluseq____init__.py.snap | 32 +++++++++++++++++++ 17 files changed, 182 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py new file mode 100644 index 00000000000000..b742dfda2225a1 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py @@ -0,0 +1,12 @@ +"""__init__.py with empty __all__ +""" + + +from . import unused # F401: add to __all__ + + +from . import renamed as bees # F401: add to __all__ + + +__all__ = [] + diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py new file mode 100644 index 00000000000000..8dc1275743db8b --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py @@ -0,0 +1,11 @@ +"""__init__.py with mis-typed __all__ +""" + + +from . import unused # F401: change to redundant alias b/c __all__ cannot be located + + +from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located + + +__all__ = None diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py new file mode 100644 index 00000000000000..3ab7d9bd3ecc51 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py @@ -0,0 +1,9 @@ +"""__init__.py with multiple imports added to all in one edit +""" + + +from . import unused, renamed as bees # F401: add to __all__ + + +__all__ = []; + diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py new file mode 100644 index 00000000000000..81900dbb3316f0 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py @@ -0,0 +1,16 @@ +"""__init__.py with __all__ populated by plus-eq +""" + + +from . import exported # Ok: is exported in __all__ + + +from . import unused # F401: add to __all__ + + +from . import renamed as bees # F401: add to __all__ + + +__all__ = [] +__all__ += ["exported"] + diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 55747fa82b9f28..e93b80c02ec7e9 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -208,6 +208,10 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_29__all_pluseq/__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap new file mode 100644 index 00000000000000..e0441650117dc0 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -0,0 +1,32 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +5 | from . import unused # F401: add to __all__ + | ^^^^^^ F401 + | + = help: Add unused import to __all__: `.unused` + +ℹ Safe fix +8 8 | from . import renamed as bees # F401: add to __all__ +9 9 | +10 10 | +11 |-__all__ = [] + 11 |+__all__ = ["unused"] +12 12 | + +__init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +8 | from . import renamed as bees # F401: add to __all__ + | ^^^^ F401 + | + = help: Add unused import to __all__: `.renamed` + +ℹ Safe fix +8 8 | from . import renamed as bees # F401: add to __all__ +9 9 | +10 10 | +11 |-__all__ = [] + 11 |+__all__ = ["bees"] +12 12 | diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap new file mode 100644 index 00000000000000..c2c20936765798 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +5 | from . import unused # F401: change to redundant alias b/c __all__ cannot be located + | ^^^^^^ F401 + | + = help: Use a redundant alias: `.unused` + +ℹ Safe fix +2 2 | """ +3 3 | +4 4 | +5 |-from . import unused # F401: change to redundant alias b/c __all__ cannot be located + 5 |+from . import unused as unused # F401: change to redundant alias b/c __all__ cannot be located +6 6 | +7 7 | +8 8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located + +__init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located + | ^^^^ F401 + | + = help: Use a redundant alias: `.renamed` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap new file mode 100644 index 00000000000000..3b7494f375b41a --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap @@ -0,0 +1,32 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +5 | from . import unused, renamed as bees # F401: add to __all__ + | ^^^^^^ F401 + | + = help: Add unused import to __all__ + +ℹ Safe fix +5 5 | from . import unused, renamed as bees # F401: add to __all__ +6 6 | +7 7 | +8 |-__all__ = []; + 8 |+__all__ = ["bees", "unused"]; +9 9 | + +__init__.py:5:34: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +5 | from . import unused, renamed as bees # F401: add to __all__ + | ^^^^ F401 + | + = help: Add unused import to __all__ + +ℹ Safe fix +5 5 | from . import unused, renamed as bees # F401: add to __all__ +6 6 | +7 7 | +8 |-__all__ = []; + 8 |+__all__ = ["bees", "unused"]; +9 9 | diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap new file mode 100644 index 00000000000000..e0c5449458f4f2 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap @@ -0,0 +1,32 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:8:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +8 | from . import unused # F401: add to __all__ + | ^^^^^^ F401 + | + = help: Add unused import to __all__: `.unused` + +ℹ Safe fix +12 12 | +13 13 | +14 14 | __all__ = [] +15 |-__all__ += ["exported"] + 15 |+__all__ += ["exported", "unused"] +16 16 | + +__init__.py:11:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +11 | from . import renamed as bees # F401: add to __all__ + | ^^^^ F401 + | + = help: Add unused import to __all__: `.renamed` + +ℹ Safe fix +12 12 | +13 13 | +14 14 | __all__ = [] +15 |-__all__ += ["exported"] + 15 |+__all__ += ["exported", "bees"] +16 16 | From f5370cbd18747eb4a6aea5556a05f91ecf88c45b Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 12:43:46 -0700 Subject: [PATCH 13/49] cargo clippy edits -- map..or to map_or and single-pattern match to if..let --- crates/ruff_linter/src/fix/edits.rs | 5 +++-- .../src/rules/pyflakes/rules/unused_import.rs | 19 ++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index be113f8995165d..fdd3ba1e5a7817 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -152,8 +152,9 @@ pub(crate) fn add_to_dunder_all<'a>( let insertion_point = dunder_all .elts .last() - .map(|expr| expr.range().end()) - .unwrap_or(dunder_all.range.end() - TextSize::from(1)); + .map_or(dunder_all.range.end() - TextSize::from(1), |expr| { + expr.range().end() + }); names .enumerate() .map(|(offset, name)| match dunder_all.elts.len() + offset { diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 95b513fbecc7c3..31e32b6477dbd8 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -413,17 +413,14 @@ fn fix_by_reexporting( bail!("Expected import bindings"); } - let edits = match dunder_all { - Some(dunder_all) => { - fix::edits::add_to_dunder_all(imports.iter().map(|b| b.name), dunder_all) - } - None => { - let member_names = imports - .iter() - .map(|b| b.import.member_name()) - .collect::>(); - fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement) - } + let edits = if let Some(dunder_all) = dunder_all { + fix::edits::add_to_dunder_all(imports.iter().map(|b| b.name), dunder_all) + } else { + let member_names = imports + .iter() + .map(|b| b.import.member_name()) + .collect::>(); + fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement) }; // Only emit a fix if there are edits From db7cbb5c4c811fd86b15e495d580278010a9fb2c Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 12:45:43 -0700 Subject: [PATCH 14/49] include the binding name in UnusedImport; when it is not the suffix of the qualified name, use the binding name in the fix title --- .../src/rules/pyflakes/rules/unused_import.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 31e32b6477dbd8..220b5c01550b71 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -82,7 +82,10 @@ enum UnusedImportContext { /// - [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols) #[violation] pub struct UnusedImport { + /// Qualified name of the import name: String, + /// Name of the import binding + binding: String, context: Option, multiple: bool, } @@ -109,7 +112,12 @@ impl Violation for UnusedImport { } fn fix_title(&self) -> Option { - let UnusedImport { name, multiple, .. } = self; + let UnusedImport { + name, + binding, + multiple, + .. + } = self; let resolution = match self.context { Some(UnusedImportContext::Init { first_party: true, @@ -123,8 +131,10 @@ impl Violation for UnusedImport { }; Some(if *multiple { resolution.to_string() - } else { + } else if name.ends_with(binding) { format!("{resolution}: `{name}`") + } else { + format!("{resolution}: `{binding}`") }) } } @@ -305,6 +315,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut let mut diagnostic = Diagnostic::new( UnusedImport { name: binding.import.qualified_name().to_string(), + binding: binding.name.to_string(), context, multiple, }, @@ -328,6 +339,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut let mut diagnostic = Diagnostic::new( UnusedImport { name: binding.import.qualified_name().to_string(), + binding: binding.name.to_string(), context: None, multiple: false, }, From 1ad23f887510f660311a95a184062b2e22a28082 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 6 May 2024 12:50:42 -0700 Subject: [PATCH 15/49] accept fixture test changes that show the binding when it differs from the qualified name --- ...ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap | 6 ++---- ...ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap | 4 +--- ...pyflakes__tests__preview__F401_F401_24____init__.py.snap | 2 +- ...s__preview__F401_F401_25__all_nonempty____init__.py.snap | 2 +- ...ests__preview__F401_F401_26__all_empty____init__.py.snap | 2 +- ...s__preview__F401_F401_27__all_mistyped____init__.py.snap | 2 +- ...sts__preview__F401_F401_29__all_pluseq____init__.py.snap | 2 +- 7 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap index e4317c5d195e7a..a9ae7aa7648747 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap @@ -27,7 +27,7 @@ F401_5.py:3:22: F401 [*] `d.e.f` imported but unused 4 | import h.i 5 | import j.k as l | - = help: Remove unused import: `d.e.f` + = help: Remove unused import: `g` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -60,12 +60,10 @@ F401_5.py:5:15: F401 [*] `j.k` imported but unused 5 | import j.k as l | ^ F401 | - = help: Remove unused import: `j.k` + = help: Remove unused import: `l` ℹ Safe fix 2 2 | from a.b import c 3 3 | from d.e import f as g 4 4 | import h.i 5 |-import j.k as l - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap index d0db5ee40d5df2..ad483af4e0132c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap @@ -28,7 +28,7 @@ F401_6.py:10:43: F401 [*] `.datastructures.UploadFile` imported but unused 11 | 12 | # OK | - = help: Remove unused import: `.datastructures.UploadFile` + = help: Remove unused import: `FileUpload` ℹ Safe fix 7 7 | from .background import BackgroundTasks @@ -71,5 +71,3 @@ F401_6.py:19:26: F401 [*] `datastructures` imported but unused 17 17 | 18 18 | # F401 `datastructures` imported but unused 19 |-import datastructures as structures - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index 2a5f8071045ea9..c91230ec56ccba 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -39,4 +39,4 @@ __init__.py:36:26: F401 `.renamed` imported but unused; consider removing, addin 36 | from . import renamed as bees # F401: no fix | ^^^^ F401 | - = help: Use a redundant alias: `.renamed` + = help: Use a redundant alias: `bees` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 99f9ef8f01918e..d0e20067c39450 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -36,7 +36,7 @@ __init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, a 39 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__: `.renamed` + = help: Add unused import to __all__: `bees` ℹ Safe fix 39 39 | from . import renamed as bees # F401: add to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap index e0441650117dc0..dd6dd10cd5e315 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -21,7 +21,7 @@ __init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, ad 8 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__: `.renamed` + = help: Add unused import to __all__: `bees` ℹ Safe fix 8 8 | from . import renamed as bees # F401: add to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index c2c20936765798..4a504fa1ea5f57 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -23,4 +23,4 @@ __init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding 8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located | ^^^^ F401 | - = help: Use a redundant alias: `.renamed` + = help: Use a redundant alias: `bees` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap index e0c5449458f4f2..52b3087a082d8e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap @@ -21,7 +21,7 @@ __init__.py:11:26: F401 [*] `.renamed` imported but unused; consider removing, a 11 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__: `.renamed` + = help: Add unused import to __all__: `bees` ℹ Safe fix 12 12 | From 2a72d341ac9a84fa32d63db2214fd1c5cb788047 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 09:28:38 -0700 Subject: [PATCH 16/49] accept stylist argument to place correct quotes around binders added to __all__; use "]".text_len() instead of specifying a TextSize manually --- crates/ruff_linter/src/fix/edits.rs | 35 +++++++++++++------ .../src/rules/pyflakes/rules/unused_import.rs | 6 +++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index fdd3ba1e5a7817..de64909ead746d 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -148,18 +148,20 @@ pub(crate) fn make_redundant_alias<'a>( pub(crate) fn add_to_dunder_all<'a>( names: impl Iterator, dunder_all: &ExprList, + stylist: &Stylist, ) -> Vec { + let quote = stylist.quote(); let insertion_point = dunder_all .elts .last() - .map_or(dunder_all.range.end() - TextSize::from(1), |expr| { + .map_or(dunder_all.range.end() - "]".text_len(), |expr| { expr.range().end() }); names .enumerate() .map(|(offset, name)| match dunder_all.elts.len() + offset { - 0 => Edit::insertion(format!("{name:?}"), insertion_point), - _ => Edit::insertion(format!(", {name:?}"), insertion_point), + 0 => Edit::insertion(format!("{quote}{name}{quote}"), insertion_point), + _ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point), }) .collect() } @@ -501,7 +503,8 @@ mod tests { use ruff_diagnostics::Edit; use ruff_python_ast as ast; - use ruff_python_parser::{parse_expression, parse_suite}; + use ruff_python_codegen::Stylist; + use ruff_python_parser::{lexer, parse_expression, parse_suite, Mode}; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -612,15 +615,21 @@ x = 1 \ #[test] fn add_to_empty_dunder_all() { - let Ok(ast::Expr::List(ref list)) = parse_expression("[]") else { + let raw = "[]"; + let locator = Locator::new(raw); + let stylist = Stylist::from_tokens( + &lexer::lex(raw, Mode::Expression).collect::>(), + &locator, + ); + let Ok(ast::Expr::List(ref list)) = parse_expression(raw) else { panic!("expected a list"); }; assert_eq!( - add_to_dunder_all(["x"].into_iter(), list), + add_to_dunder_all(["x"].into_iter(), list, &stylist), vec![Edit::insertion(String::from("\"x\""), TextSize::new(1))], ); assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), list), + add_to_dunder_all(["x", "y"].into_iter(), list, &stylist), vec![ Edit::insertion(String::from("\"x\""), TextSize::new(1)), Edit::insertion(String::from(", \"y\""), TextSize::new(1)) @@ -630,15 +639,21 @@ x = 1 \ #[test] fn add_to_nonempty_dunder_all() { - let Ok(ast::Expr::List(ref list)) = parse_expression("[\"a\", \"b\"]") else { + let raw = "[\"a\", \"b\"]"; + let locator = Locator::new(raw); + let stylist = Stylist::from_tokens( + &lexer::lex(raw, Mode::Expression).collect::>(), + &locator, + ); + let Ok(ast::Expr::List(ref list)) = parse_expression(raw) else { panic!("expected a list"); }; assert_eq!( - add_to_dunder_all(["x"].into_iter(), list), + add_to_dunder_all(["x"].into_iter(), list, &stylist), vec![Edit::insertion(String::from(", \"x\""), TextSize::new(9))], ); assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), list), + add_to_dunder_all(["x", "y"].into_iter(), list, &stylist), vec![ Edit::insertion(String::from(", \"x\""), TextSize::new(9)), Edit::insertion(String::from(", \"y\""), TextSize::new(9)) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 220b5c01550b71..c422ebfc18f6d8 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -426,7 +426,11 @@ fn fix_by_reexporting( } let edits = if let Some(dunder_all) = dunder_all { - fix::edits::add_to_dunder_all(imports.iter().map(|b| b.name), dunder_all) + fix::edits::add_to_dunder_all( + imports.iter().map(|b| b.name), + dunder_all, + checker.stylist(), + ) } else { let member_names = imports .iter() From 494e56d5e5e0d6f5a04214a1304671808537a07a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 14:29:28 -0700 Subject: [PATCH 17/49] use Expr instead of ExprList to convey dunder_all value to the edit-producing function b/c we want to also support tuples --- crates/ruff_linter/src/fix/edits.rs | 56 ++++++++++--------- .../src/rules/pyflakes/rules/unused_import.rs | 9 +-- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index de64909ead746d..c05093684ce986 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use ruff_diagnostics::Edit; use ruff_python_ast::parenthesize::parenthesized_range; -use ruff_python_ast::{self as ast, Arguments, ExceptHandler, ExprList, Stmt}; +use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Stmt}; use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; @@ -147,19 +147,25 @@ pub(crate) fn make_redundant_alias<'a>( /// Fix to add the specified imports to the `__all__` export list. pub(crate) fn add_to_dunder_all<'a>( names: impl Iterator, - dunder_all: &ExprList, + expr: &Expr, stylist: &Stylist, ) -> Vec { + let (insertion_point, export_prefix_length) = match expr { + ast::Expr::List(ast::ExprList { elts, range, .. }) + | ast::Expr::Tuple(ast::ExprTuple { elts, range, .. }) => ( + elts.last() + .map(|elt| elt.range().end()) + .unwrap_or(range.end() - "]".text_len()), + elts.len(), + ), + _ => { + return vec![]; + } + }; let quote = stylist.quote(); - let insertion_point = dunder_all - .elts - .last() - .map_or(dunder_all.range.end() - "]".text_len(), |expr| { - expr.range().end() - }); names .enumerate() - .map(|(offset, name)| match dunder_all.elts.len() + offset { + .map(|(offset, name)| match export_prefix_length + offset { 0 => Edit::insertion(format!("{quote}{name}{quote}"), insertion_point), _ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point), }) @@ -500,9 +506,9 @@ fn all_lines_fit( #[cfg(test)] mod tests { use anyhow::Result; + use test_case::test_case; use ruff_diagnostics::Edit; - use ruff_python_ast as ast; use ruff_python_codegen::Stylist; use ruff_python_parser::{lexer, parse_expression, parse_suite, Mode}; use ruff_source_file::Locator; @@ -613,51 +619,49 @@ x = 1 \ ); } - #[test] - fn add_to_empty_dunder_all() { - let raw = "[]"; + #[test_case("[]" ; "empty list")] + #[test_case("()" ; "empty tuple")] + fn add_to_empty_dunder_all(raw: &str) -> Result<()> { let locator = Locator::new(raw); let stylist = Stylist::from_tokens( &lexer::lex(raw, Mode::Expression).collect::>(), &locator, ); - let Ok(ast::Expr::List(ref list)) = parse_expression(raw) else { - panic!("expected a list"); - }; + let expr = parse_expression(raw)?; assert_eq!( - add_to_dunder_all(["x"].into_iter(), list, &stylist), + add_to_dunder_all(["x"].into_iter(), &expr, &stylist), vec![Edit::insertion(String::from("\"x\""), TextSize::new(1))], ); assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), list, &stylist), + add_to_dunder_all(["x", "y"].into_iter(), &expr, &stylist), vec![ Edit::insertion(String::from("\"x\""), TextSize::new(1)), Edit::insertion(String::from(", \"y\""), TextSize::new(1)) ], ); + Ok(()) } - #[test] - fn add_to_nonempty_dunder_all() { - let raw = "[\"a\", \"b\"]"; + #[test_case(r#"["a", "b"]"# ; "nonempty list")] + #[test_case(r#"("a", "b")"# ; "nonempty tuple")] + fn add_to_nonempty_dunder_all(raw: &str) -> Result<()> { let locator = Locator::new(raw); let stylist = Stylist::from_tokens( &lexer::lex(raw, Mode::Expression).collect::>(), &locator, ); - let Ok(ast::Expr::List(ref list)) = parse_expression(raw) else { - panic!("expected a list"); - }; + let expr = parse_expression(raw)?; assert_eq!( - add_to_dunder_all(["x"].into_iter(), list, &stylist), + add_to_dunder_all(["x"].into_iter(), &expr, &stylist), vec![Edit::insertion(String::from(", \"x\""), TextSize::new(9))], ); assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), list, &stylist), + add_to_dunder_all(["x", "y"].into_iter(), &expr, &stylist), vec![ Edit::insertion(String::from(", \"x\""), TextSize::new(9)), Edit::insertion(String::from(", \"y\""), TextSize::new(9)) ], ); + Ok(()) } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index c422ebfc18f6d8..9081bc95e4a1bc 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -158,8 +158,8 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { } } -/// Find the `ExprList` for a top level `__all__`, if one exists. -fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::ExprList> { +/// Find the `Expr` value for a top level `__all__`, if one exists. +fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr> { let stmt = semantic .global_scope() .get_all("__all__") @@ -175,7 +175,8 @@ fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr _ => None, }?; match expr { - ast::Expr::List(list) => Some(list), + ast::Expr::List(_) => Some(expr), + ast::Expr::Tuple(_) => Some(expr), _ => None, } } @@ -418,7 +419,7 @@ fn fix_by_reexporting( checker: &Checker, node_id: NodeId, imports: &[&ImportBinding], - dunder_all: Option<&ast::ExprList>, + dunder_all: Option<&ast::Expr>, ) -> Result { let statement = checker.semantic().statement(node_id); if imports.is_empty() { From a54ea9dc44a0d76223c9a328ae447355d8c0dfff Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 14:37:11 -0700 Subject: [PATCH 18/49] clippy again --- crates/ruff_linter/src/fix/edits.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index c05093684ce986..24b0eb6983024e 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -154,8 +154,7 @@ pub(crate) fn add_to_dunder_all<'a>( ast::Expr::List(ast::ExprList { elts, range, .. }) | ast::Expr::Tuple(ast::ExprTuple { elts, range, .. }) => ( elts.last() - .map(|elt| elt.range().end()) - .unwrap_or(range.end() - "]".text_len()), + .map_or(range.end() - "]".text_len(), |elt| elt.range().end()), elts.len(), ), _ => { From 99fd1f681fbed60425c2dad59195854030a75e45 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 14:50:03 -0700 Subject: [PATCH 19/49] change make_redundant_alias to take an iter of Cow to simplify caller --- crates/ruff_linter/src/fix/edits.rs | 10 ++++++---- .../src/rules/pyflakes/rules/unused_import.rs | 6 +----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 24b0eb6983024e..59eca9f1014d4b 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -1,5 +1,7 @@ //! Interface for generating fix edits from higher-level actions (e.g., "remove an argument"). +use std::borrow::Cow; + use anyhow::{Context, Result}; use ruff_diagnostics::Edit; @@ -124,7 +126,7 @@ pub(crate) fn remove_unused_imports<'a>( /// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`. pub(crate) fn make_redundant_alias<'a>( - member_names: impl Iterator, + member_names: impl Iterator>, stmt: &Stmt, ) -> Vec { let aliases = match stmt { @@ -593,7 +595,7 @@ x = 1 \ let program = parse_suite(contents).unwrap(); let stmt = program.first().unwrap(); assert_eq!( - make_redundant_alias(["x"].into_iter(), stmt), + make_redundant_alias(["x"].into_iter().map(|s| s.into()), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -601,7 +603,7 @@ x = 1 \ "make just one item redundant" ); assert_eq!( - make_redundant_alias(vec!["x", "y"].into_iter(), stmt), + make_redundant_alias(vec!["x", "y"].into_iter().map(|s| s.into()), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -609,7 +611,7 @@ x = 1 \ "the second item is already a redundant alias" ); assert_eq!( - make_redundant_alias(vec!["x", "z"].into_iter(), stmt), + make_redundant_alias(vec!["x", "z"].into_iter().map(|s| s.into()), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 9081bc95e4a1bc..77242ef47a0659 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -433,11 +433,7 @@ fn fix_by_reexporting( checker.stylist(), ) } else { - let member_names = imports - .iter() - .map(|b| b.import.member_name()) - .collect::>(); - fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement) + fix::edits::make_redundant_alias(imports.iter().map(|b| b.import.member_name()), statement) }; // Only emit a fix if there are edits From c8dc342b4c6668441bfbd7e1783390e094ffc283 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 17:16:09 -0700 Subject: [PATCH 20/49] rewrite add_to_dunder_all and its tests --- crates/ruff_linter/src/fix/edits.rs | 115 ++++++++++++++++------------ 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 59eca9f1014d4b..c48392cbaee5d2 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -153,24 +153,45 @@ pub(crate) fn add_to_dunder_all<'a>( stylist: &Stylist, ) -> Vec { let (insertion_point, export_prefix_length) = match expr { - ast::Expr::List(ast::ExprList { elts, range, .. }) - | ast::Expr::Tuple(ast::ExprTuple { elts, range, .. }) => ( + ast::Expr::List(ast::ExprList { elts, range, .. }) => ( elts.last() .map_or(range.end() - "]".text_len(), |elt| elt.range().end()), elts.len(), ), + ast::Expr::Tuple(tup) if tup.parenthesized => ( + tup.elts + .last() + .map_or(tup.range.end() - ")".text_len(), |elt| elt.range().end()), + tup.elts.len(), + ), + ast::Expr::Tuple(tup) if !tup.parenthesized => ( + tup.elts + .last() + .expect("unparenthesized empty tuple is not possible") + .range() + .end(), + tup.elts.len(), + ), _ => { + // we don't know how to insert into this expression return vec![]; } }; let quote = stylist.quote(); - names + let mut edits: Vec<_> = names .enumerate() .map(|(offset, name)| match export_prefix_length + offset { 0 => Edit::insertion(format!("{quote}{name}{quote}"), insertion_point), _ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point), }) - .collect() + .collect(); + match expr { + ast::Expr::Tuple(tup) if tup.parenthesized && 1 == export_prefix_length + edits.len() => { + edits.push(Edit::insertion(",".to_string(), insertion_point)); + } + _ => {} + }; + edits } #[derive(Debug, Copy, Clone)] @@ -506,15 +527,16 @@ fn all_lines_fit( #[cfg(test)] mod tests { - use anyhow::Result; + use anyhow::{anyhow, Result}; use test_case::test_case; - use ruff_diagnostics::Edit; + use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_python_codegen::Stylist; use ruff_python_parser::{lexer, parse_expression, parse_suite, Mode}; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; + use crate::fix::apply_fixes; use crate::fix::edits::{ add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; @@ -620,49 +642,46 @@ x = 1 \ ); } - #[test_case("[]" ; "empty list")] - #[test_case("()" ; "empty tuple")] - fn add_to_empty_dunder_all(raw: &str) -> Result<()> { - let locator = Locator::new(raw); - let stylist = Stylist::from_tokens( - &lexer::lex(raw, Mode::Expression).collect::>(), - &locator, - ); - let expr = parse_expression(raw)?; - assert_eq!( - add_to_dunder_all(["x"].into_iter(), &expr, &stylist), - vec![Edit::insertion(String::from("\"x\""), TextSize::new(1))], - ); - assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), &expr, &stylist), - vec![ - Edit::insertion(String::from("\"x\""), TextSize::new(1)), - Edit::insertion(String::from(", \"y\""), TextSize::new(1)) - ], - ); - Ok(()) - } - - #[test_case(r#"["a", "b"]"# ; "nonempty list")] - #[test_case(r#"("a", "b")"# ; "nonempty tuple")] - fn add_to_nonempty_dunder_all(raw: &str) -> Result<()> { + #[test_case("()", &["x", "y"], r#"("x", "y")"# ; "2 into empty tuple")] + #[test_case("()", &["x"], r#"("x",)"# ; "1 into empty tuple adding a trailing comma")] + #[test_case("[]", &["x", "y"], r#"["x", "y"]"# ; "2 into empty list")] + #[test_case("[]", &["x"], r#"["x"]"# ; "1 into empty list")] + #[test_case(r#""a", "b""#, &["x", "y"], r#""a", "b", "x", "y""# ; "2 into unparenthesized tuple")] + #[test_case(r#""a", "b""#, &["x"], r#""a", "b", "x""# ; "1 into unparenthesized tuple")] + #[test_case(r#""a", "b","#, &["x", "y"], r#""a", "b", "x", "y","# ; "2 into unparenthesized tuple w/trailing comma")] + #[test_case(r#""a", "b","#, &["x"], r#""a", "b", "x","# ; "1 into unparenthesized tuple w/trailing comma")] + #[test_case(r#"("a", "b")"#, &["x", "y"], r#"("a", "b", "x", "y")"# ; "2 into nonempty tuple")] + #[test_case(r#"("a", "b")"#, &["x"], r#"("a", "b", "x")"# ; "1 into nonempty tuple")] + #[test_case(r#"("a", "b",)"#, &["x", "y"], r#"("a", "b", "x", "y",)"# ; "2 into nonempty tuple w/trailing comma")] + #[test_case(r#"("a", "b",)"#, &["x"], r#"("a", "b", "x",)"# ; "1 into nonempty tuple w/trailing comma")] + #[test_case(r#"["a", "b",]"#, &["x", "y"], r#"["a", "b", "x", "y",]"# ; "2 into nonempty list w/trailing comma")] + #[test_case(r#"["a", "b",]"#, &["x"], r#"["a", "b", "x",]"# ; "1 into nonempty list w/trailing comma")] + #[test_case(r#"["a", "b"]"#, &["x", "y"], r#"["a", "b", "x", "y"]"# ; "2 into nonempty list")] + #[test_case(r#"["a", "b"]"#, &["x"], r#"["a", "b", "x"]"# ; "1 into nonempty list")] + fn add_to_dunder_all_test(raw: &str, names: &[&str], expect: &str) -> Result<()> { let locator = Locator::new(raw); - let stylist = Stylist::from_tokens( - &lexer::lex(raw, Mode::Expression).collect::>(), - &locator, - ); - let expr = parse_expression(raw)?; - assert_eq!( - add_to_dunder_all(["x"].into_iter(), &expr, &stylist), - vec![Edit::insertion(String::from(", \"x\""), TextSize::new(9))], - ); - assert_eq!( - add_to_dunder_all(["x", "y"].into_iter(), &expr, &stylist), - vec![ - Edit::insertion(String::from(", \"x\""), TextSize::new(9)), - Edit::insertion(String::from(", \"y\""), TextSize::new(9)) - ], - ); + let edits = { + let expr = parse_expression(raw)?; + let stylist = Stylist::from_tokens( + &lexer::lex(raw, Mode::Expression).collect::>(), + &locator, + ); + // SUT + add_to_dunder_all(names.iter().map(|s| *s), &expr, &stylist) + }; + let diag = { + use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; + let mut iter = edits.into_iter(); + Diagnostic::new( + MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary. + TextRange::default(), + ) + .with_fix(Fix::safe_edits( + iter.next().ok_or(anyhow!("expected edits nonempty"))?, + iter, + )) + }; + assert_eq!(apply_fixes([diag].iter(), &locator).code, expect); Ok(()) } } From 188a232dd071468f7bf4253720e828792686f027 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 7 May 2024 17:31:04 -0700 Subject: [PATCH 21/49] shakes fist at cloud and yells, "Clippy!!!" --- crates/ruff_linter/src/fix/edits.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index c48392cbaee5d2..1d490bde9bf22c 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -617,7 +617,7 @@ x = 1 \ let program = parse_suite(contents).unwrap(); let stmt = program.first().unwrap(); assert_eq!( - make_redundant_alias(["x"].into_iter().map(|s| s.into()), stmt), + make_redundant_alias(["x"].into_iter().map(std::convert::Into::into), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -625,7 +625,10 @@ x = 1 \ "make just one item redundant" ); assert_eq!( - make_redundant_alias(vec!["x", "y"].into_iter().map(|s| s.into()), stmt), + make_redundant_alias( + vec!["x", "y"].into_iter().map(std::convert::Into::into), + stmt + ), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -633,7 +636,10 @@ x = 1 \ "the second item is already a redundant alias" ); assert_eq!( - make_redundant_alias(vec!["x", "z"].into_iter().map(|s| s.into()), stmt), + make_redundant_alias( + vec!["x", "z"].into_iter().map(std::convert::Into::into), + stmt + ), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -667,7 +673,7 @@ x = 1 \ &locator, ); // SUT - add_to_dunder_all(names.iter().map(|s| *s), &expr, &stylist) + add_to_dunder_all(names.iter().copied(), &expr, &stylist) }; let diag = { use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; From 877800ecfc5593269d0db5943a5232e4e7476efd Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 11:13:59 -0700 Subject: [PATCH 22/49] change message for redundant-aliases --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 2 +- ...__pyflakes__tests__preview__F401_F401_24____init__.py.snap | 4 ++-- ...sts__preview__F401_F401_27__all_mistyped____init__.py.snap | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 77242ef47a0659..0b2ccf43ce5c13 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -126,7 +126,7 @@ impl Violation for UnusedImport { Some(UnusedImportContext::Init { first_party: true, dunder_all: false, - }) => "Use a redundant alias", + }) => "Use an explicit re-export", _ => "Remove unused import", }; Some(if *multiple { diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index c91230ec56ccba..fdc90bddf82435 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -22,7 +22,7 @@ __init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, ad 33 | from . import unused # F401: change to redundant alias | ^^^^^^ F401 | - = help: Use a redundant alias: `.unused` + = help: Use an explicit re-export: `.unused` ℹ Safe fix 30 30 | from . import aliased as aliased # Ok: is redundant alias @@ -39,4 +39,4 @@ __init__.py:36:26: F401 `.renamed` imported but unused; consider removing, addin 36 | from . import renamed as bees # F401: no fix | ^^^^ F401 | - = help: Use a redundant alias: `bees` + = help: Use an explicit re-export: `bees` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index 4a504fa1ea5f57..29f280ce025973 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -6,7 +6,7 @@ __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, add 5 | from . import unused # F401: change to redundant alias b/c __all__ cannot be located | ^^^^^^ F401 | - = help: Use a redundant alias: `.unused` + = help: Use an explicit re-export: `.unused` ℹ Safe fix 2 2 | """ @@ -23,4 +23,4 @@ __init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding 8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located | ^^^^ F401 | - = help: Use a redundant alias: `bees` + = help: Use an explicit re-export: `bees` From d5978f9de85535fa56f8634e138c092e5f24ae14 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 11:48:46 -0700 Subject: [PATCH 23/49] use binding method instead of accessing the field --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 0b2ccf43ce5c13..b295b5ff90601f 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -163,7 +163,7 @@ fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr let stmt = semantic .global_scope() .get_all("__all__") - .map(|binding_id| &semantic.bindings[binding_id]) + .map(|binding_id| semantic.binding(binding_id)) .find_map(|binding| match binding.kind { BindingKind::Export(_) => binding.statement(semantic), _ => None, From 86f30e7aefc3bc46ccdccb2c71d5bc0ef2fd1672 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 11:49:18 -0700 Subject: [PATCH 24/49] use filter_map followed by last to obtain the first __all__ binding --- .../ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index b295b5ff90601f..dc2a83ee490ab5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -164,10 +164,12 @@ fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr .global_scope() .get_all("__all__") .map(|binding_id| semantic.binding(binding_id)) - .find_map(|binding| match binding.kind { + .filter_map(|binding| match binding.kind { BindingKind::Export(_) => binding.statement(semantic), _ => None, - })?; + }) + // `.get_all(…)` returns bindings in reverse-execution-order; we want the first __all__ + .last()?; let expr = match stmt { Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), From 7f092bf268d7fb204c31871f412293dd77df7626 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 11:49:56 -0700 Subject: [PATCH 25/49] update fixture test to reflect that we add to the first binding of __all__ --- ...iew__F401_F401_29__all_pluseq____init__.py.snap | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap index 52b3087a082d8e..dd70fabaefec6a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap @@ -9,11 +9,12 @@ __init__.py:8:15: F401 [*] `.unused` imported but unused; consider removing, add = help: Add unused import to __all__: `.unused` ℹ Safe fix +11 11 | from . import renamed as bees # F401: add to __all__ 12 12 | 13 13 | -14 14 | __all__ = [] -15 |-__all__ += ["exported"] - 15 |+__all__ += ["exported", "unused"] +14 |-__all__ = [] + 14 |+__all__ = ["unused"] +15 15 | __all__ += ["exported"] 16 16 | __init__.py:11:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias @@ -24,9 +25,10 @@ __init__.py:11:26: F401 [*] `.renamed` imported but unused; consider removing, a = help: Add unused import to __all__: `bees` ℹ Safe fix +11 11 | from . import renamed as bees # F401: add to __all__ 12 12 | 13 13 | -14 14 | __all__ = [] -15 |-__all__ += ["exported"] - 15 |+__all__ += ["exported", "bees"] +14 |-__all__ = [] + 14 |+__all__ = ["bees"] +15 15 | __all__ += ["exported"] 16 16 | From 1d0f3cabf05f3a271a9a86123e317d5edc3e143a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 11:50:51 -0700 Subject: [PATCH 26/49] fixture test to demonstrate how we treat __all__ followed by a conditional mutation --- .../F401_30__all_conditional/__init__.py | 14 +++++++++++ .../F401_30__all_conditional/also_exported.py | 1 + .../F401_30__all_conditional/exported.py | 1 + .../F401_30__all_conditional/renamed.py | 1 + crates/ruff_linter/src/rules/pyflakes/mod.rs | 1 + ...F401_30__all_conditional____init__.py.snap | 23 +++++++++++++++++++ 6 files changed, 41 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py new file mode 100644 index 00000000000000..d3fdd337556563 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py @@ -0,0 +1,14 @@ +"""__init__.py with __all__ populated by conditional plus-eq +""" + +import sys + +from . import exported, renamed as bees + +if sys.version_info > (3, 9): + from . import also_exported + +__all__ = ["exported"] + +if sys.version_info >= (3, 9): + __all__ += ["also_exported"] diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index e93b80c02ec7e9..000fca33658d5a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -212,6 +212,7 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_29__all_pluseq/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_30__all_conditional/__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap new file mode 100644 index 00000000000000..bb9e238901f767 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:6:36: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +4 | import sys +5 | +6 | from . import exported, renamed as bees + | ^^^^ F401 +7 | +8 | if sys.version_info > (3, 9): + | + = help: Add unused import to __all__: `bees` + +ℹ Safe fix +8 8 | if sys.version_info > (3, 9): +9 9 | from . import also_exported +10 10 | +11 |-__all__ = ["exported"] + 11 |+__all__ = ["exported", "bees"] +12 12 | +13 13 | if sys.version_info >= (3, 9): +14 14 | __all__ += ["also_exported"] From 3e284b6104a55588bf0793aa1df23464fef4db2a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Wed, 8 May 2024 13:26:51 -0700 Subject: [PATCH 27/49] do not offer a fix to add to __all__ when there is more than one __all__; fall back to the redundant-alias recommending behavior --- .../pyflakes/F401_29__all_pluseq/__init__.py | 6 ++- .../F401_30__all_conditional/__init__.py | 2 + .../src/rules/pyflakes/rules/unused_import.rs | 10 +++-- ...F401_F401_29__all_pluseq____init__.py.snap | 40 ++++++++----------- ...F401_30__all_conditional____init__.py.snap | 30 +++++--------- 5 files changed, 38 insertions(+), 50 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py index 81900dbb3316f0..714371c13b81b3 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py @@ -1,14 +1,16 @@ """__init__.py with __all__ populated by plus-eq + +multiple __all__ so cannot offer a fix to add to them; offer fixes for redundant-aliases """ from . import exported # Ok: is exported in __all__ -from . import unused # F401: add to __all__ +from . import unused # F401: convert to redundant-alias (with fix) -from . import renamed as bees # F401: add to __all__ +from . import renamed as bees # F401: convert to redundant-alias (no fix) __all__ = [] diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py index d3fdd337556563..09ca2ba72d113a 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py @@ -1,4 +1,6 @@ """__init__.py with __all__ populated by conditional plus-eq + +multiple __all__ so cannot offer a fix to add to them; offer fixes for redundant-aliases """ import sys diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index dc2a83ee490ab5..2594abef2a8893 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -158,9 +158,9 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { } } -/// Find the `Expr` value for a top level `__all__`, if one exists. +/// Find the `Expr` value for a unique top level `__all__` binding. fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr> { - let stmt = semantic + let stmts: Vec<_> = semantic .global_scope() .get_all("__all__") .map(|binding_id| semantic.binding(binding_id)) @@ -168,8 +168,10 @@ fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr BindingKind::Export(_) => binding.statement(semantic), _ => None, }) - // `.get_all(…)` returns bindings in reverse-execution-order; we want the first __all__ - .last()?; + .collect(); + let [stmt] = stmts.as_slice() else { + return None; // only fix when there is /exactly one/ binding + }; let expr = match stmt { Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap index dd70fabaefec6a..603feecff15c08 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap @@ -1,34 +1,26 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:8:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias - | -8 | from . import unused # F401: add to __all__ - | ^^^^^^ F401 - | - = help: Add unused import to __all__: `.unused` +__init__.py:10:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +10 | from . import unused # F401: convert to redundant-alias (with fix) + | ^^^^^^ F401 + | + = help: Use an explicit re-export: `.unused` ℹ Safe fix -11 11 | from . import renamed as bees # F401: add to __all__ +7 7 | from . import exported # Ok: is exported in __all__ +8 8 | +9 9 | +10 |-from . import unused # F401: convert to redundant-alias (with fix) + 10 |+from . import unused as unused # F401: convert to redundant-alias (with fix) +11 11 | 12 12 | -13 13 | -14 |-__all__ = [] - 14 |+__all__ = ["unused"] -15 15 | __all__ += ["exported"] -16 16 | +13 13 | from . import renamed as bees # F401: convert to redundant-alias (no fix) -__init__.py:11:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:13:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -11 | from . import renamed as bees # F401: add to __all__ +13 | from . import renamed as bees # F401: convert to redundant-alias (no fix) | ^^^^ F401 | - = help: Add unused import to __all__: `bees` - -ℹ Safe fix -11 11 | from . import renamed as bees # F401: add to __all__ -12 12 | -13 13 | -14 |-__all__ = [] - 14 |+__all__ = ["bees"] -15 15 | __all__ += ["exported"] -16 16 | + = help: Use an explicit re-export: `bees` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap index bb9e238901f767..02127502398801 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap @@ -1,23 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:6:36: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias - | -4 | import sys -5 | -6 | from . import exported, renamed as bees - | ^^^^ F401 -7 | -8 | if sys.version_info > (3, 9): - | - = help: Add unused import to __all__: `bees` - -ℹ Safe fix -8 8 | if sys.version_info > (3, 9): -9 9 | from . import also_exported -10 10 | -11 |-__all__ = ["exported"] - 11 |+__all__ = ["exported", "bees"] -12 12 | -13 13 | if sys.version_info >= (3, 9): -14 14 | __all__ += ["also_exported"] +__init__.py:8:36: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | + 6 | import sys + 7 | + 8 | from . import exported, renamed as bees + | ^^^^ F401 + 9 | +10 | if sys.version_info > (3, 9): + | + = help: Use an explicit re-export: `bees` From 7f3335d08e16dbed47ee934ea1ca02a8f9df394c Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 08:08:15 -0700 Subject: [PATCH 28/49] delete F401_29 fixture test --- .../pyflakes/F401_29__all_pluseq/__init__.py | 18 ------------- .../pyflakes/F401_29__all_pluseq/renamed.py | 1 - .../pyflakes/F401_29__all_pluseq/unused.py | 1 - crates/ruff_linter/src/rules/pyflakes/mod.rs | 1 - ...F401_F401_29__all_pluseq____init__.py.snap | 26 ------------------- 5 files changed, 47 deletions(-) delete mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py delete mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py delete mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py delete mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py deleted file mode 100644 index 714371c13b81b3..00000000000000 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -"""__init__.py with __all__ populated by plus-eq - -multiple __all__ so cannot offer a fix to add to them; offer fixes for redundant-aliases -""" - - -from . import exported # Ok: is exported in __all__ - - -from . import unused # F401: convert to redundant-alias (with fix) - - -from . import renamed as bees # F401: convert to redundant-alias (no fix) - - -__all__ = [] -__all__ += ["exported"] - diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py deleted file mode 100644 index 7391604add03e9..00000000000000 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/renamed.py +++ /dev/null @@ -1 +0,0 @@ -# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py deleted file mode 100644 index 7391604add03e9..00000000000000 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_pluseq/unused.py +++ /dev/null @@ -1 +0,0 @@ -# empty module imported by __init__.py for test fixture diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 000fca33658d5a..09f927dbe3b573 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -211,7 +211,6 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] - #[test_case(Rule::UnusedImport, Path::new("F401_29__all_pluseq/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_30__all_conditional/__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap deleted file mode 100644 index 603feecff15c08..00000000000000 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_pluseq____init__.py.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pyflakes/mod.rs ---- -__init__.py:10:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias - | -10 | from . import unused # F401: convert to redundant-alias (with fix) - | ^^^^^^ F401 - | - = help: Use an explicit re-export: `.unused` - -ℹ Safe fix -7 7 | from . import exported # Ok: is exported in __all__ -8 8 | -9 9 | -10 |-from . import unused # F401: convert to redundant-alias (with fix) - 10 |+from . import unused as unused # F401: convert to redundant-alias (with fix) -11 11 | -12 12 | -13 13 | from . import renamed as bees # F401: convert to redundant-alias (no fix) - -__init__.py:13:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias - | -13 | from . import renamed as bees # F401: convert to redundant-alias (no fix) - | ^^^^ F401 - | - = help: Use an explicit re-export: `bees` From 097ce5515e1f96e600216bdda562ed5ffc2fc1f3 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 08:11:20 -0700 Subject: [PATCH 29/49] rename F401_30 fixture test to F401_29 --- .../__init__.py | 0 .../also_exported.py | 0 .../exported.py | 0 .../renamed.py | 0 crates/ruff_linter/src/rules/pyflakes/mod.rs | 2 +- ...s__preview__F401_F401_29__all_conditional____init__.py.snap} | 0 6 files changed, 1 insertion(+), 1 deletion(-) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_30__all_conditional => F401_29__all_conditional}/__init__.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_30__all_conditional => F401_29__all_conditional}/also_exported.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_30__all_conditional => F401_29__all_conditional}/exported.py (100%) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F401_30__all_conditional => F401_29__all_conditional}/renamed.py (100%) rename crates/ruff_linter/src/rules/pyflakes/snapshots/{ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap => ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap} (100%) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/__init__.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/also_exported.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/also_exported.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/also_exported.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/exported.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/exported.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/exported.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/renamed.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30__all_conditional/renamed.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/renamed.py diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 09f927dbe3b573..4d31234106abaa 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -211,7 +211,7 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] - #[test_case(Rule::UnusedImport, Path::new("F401_30__all_conditional/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_30__all_conditional____init__.py.snap rename to crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap From 5beb13f4a3384b18ed96da4e394398e45ec8852a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 09:08:46 -0700 Subject: [PATCH 30/49] add unused-import case to F401_29 fixture test --- .../fixtures/pyflakes/F401_29__all_conditional/__init__.py | 4 ++-- .../test/fixtures/pyflakes/F401_29__all_conditional/unused.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py index 09ca2ba72d113a..4802749b4759ff 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py @@ -1,11 +1,11 @@ """__init__.py with __all__ populated by conditional plus-eq -multiple __all__ so cannot offer a fix to add to them; offer fixes for redundant-aliases +multiple __all__ so cannot offer a fix to add to them """ import sys -from . import exported, renamed as bees +from . import unused, exported, renamed as bees if sys.version_info > (3, 9): from . import also_exported diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py new file mode 100644 index 00000000000000..7391604add03e9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py @@ -0,0 +1 @@ +# empty module imported by __init__.py for test fixture From 92a7a43f4aeafb80503180c5fa9dd6bf23f0f083 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 09:15:11 -0700 Subject: [PATCH 31/49] change `find_dunder_all_expr` to `find_dunder_all_exprs` and track the number of matches on UnusedImportContext::Init; update fix-title messaging and fix_by_reexporting to only fix when zero or one __all__ are present; update fixture tests --- .../src/rules/pyflakes/rules/unused_import.rs | 77 ++++++++++--------- ...F401_29__all_conditional____init__.py.snap | 19 ++++- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 2594abef2a8893..4006b4e2409fee 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -21,7 +21,10 @@ use crate::rules::{isort, isort::ImportSection, isort::ImportType}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum UnusedImportContext { ExceptHandler, - Init { first_party: bool, dunder_all: bool }, + Init { + first_party: bool, + dunder_all_count: usize, + }, } /// ## What it does @@ -121,11 +124,11 @@ impl Violation for UnusedImport { let resolution = match self.context { Some(UnusedImportContext::Init { first_party: true, - dunder_all: true, + dunder_all_count: 1, }) => "Add unused import to __all__", Some(UnusedImportContext::Init { first_party: true, - dunder_all: false, + dunder_all_count: 0, }) => "Use an explicit re-export", _ => "Remove unused import", }; @@ -158,36 +161,37 @@ fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { } } -/// Find the `Expr` value for a unique top level `__all__` binding. -fn find_dunder_all_expr<'a>(semantic: &'a SemanticModel) -> Option<&'a ast::Expr> { - let stmts: Vec<_> = semantic +/// Find the `Expr` for top level `__all__` bindings. +fn find_dunder_all_exprs<'a>(semantic: &'a SemanticModel) -> Vec<&'a ast::Expr> { + semantic .global_scope() .get_all("__all__") - .map(|binding_id| semantic.binding(binding_id)) - .filter_map(|binding| match binding.kind { - BindingKind::Export(_) => binding.statement(semantic), - _ => None, + .filter_map(|binding_id| { + let binding = semantic.binding(binding_id); + let stmt = match binding.kind { + BindingKind::Export(_) => binding.statement(semantic), + _ => None, + }?; + let expr = match stmt { + Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), + Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), + Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(&**value), + _ => None, + }?; + match expr { + ast::Expr::List(_) => Some(expr), + ast::Expr::Tuple(_) => Some(expr), + _ => None, + } }) - .collect(); - let [stmt] = stmts.as_slice() else { - return None; // only fix when there is /exactly one/ binding - }; - let expr = match stmt { - Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), - Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), - Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(&**value), - _ => None, - }?; - match expr { - ast::Expr::List(_) => Some(expr), - ast::Expr::Tuple(_) => Some(expr), - _ => None, - } + .collect() } /// For some unused binding in an import statement... /// -/// __init__.py ∧ 1stpty → safe, move to __all__ or convert to explicit-import +/// __init__.py ∧ 1stpty → safe, if one __all__, add to __all__ +/// safe, if no __all__, convert to redundant-alias +/// n/a, if multiple __all__, offer no fix /// __init__.py ∧ stdlib → unsafe, remove /// __init__.py ∧ 3rdpty → unsafe, remove /// @@ -245,7 +249,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut let in_init = checker.path().ends_with("__init__.py"); let fix_init = checker.settings.preview.is_enabled(); - let dunder_all_expr = find_dunder_all_expr(checker.semantic()); + let dunder_all_exprs = find_dunder_all_exprs(checker.semantic()); // Generate a diagnostic for every import, but share fixes across all imports within the same // statement (excluding those that are ignored). @@ -274,7 +278,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut level, checker, ), - dunder_all: dunder_all_expr.is_some(), + dunder_all_count: dunder_all_exprs.len(), }) } else { None @@ -305,7 +309,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut checker, import_statement, &to_reexport.iter().map(|(b, _)| b).collect::>(), - dunder_all_expr, + &dunder_all_exprs, ) .ok(), ) @@ -423,21 +427,24 @@ fn fix_by_reexporting( checker: &Checker, node_id: NodeId, imports: &[&ImportBinding], - dunder_all: Option<&ast::Expr>, + dunder_all_exprs: &[&ast::Expr], ) -> Result { let statement = checker.semantic().statement(node_id); if imports.is_empty() { bail!("Expected import bindings"); } - let edits = if let Some(dunder_all) = dunder_all { - fix::edits::add_to_dunder_all( + let edits = match dunder_all_exprs { + [] => fix::edits::make_redundant_alias( + imports.iter().map(|b| b.import.member_name()), + statement, + ), + [dunder_all] => fix::edits::add_to_dunder_all( imports.iter().map(|b| b.name), dunder_all, checker.stylist(), - ) - } else { - fix::edits::make_redundant_alias(imports.iter().map(|b| b.import.member_name()), statement) + ), + _ => bail!("Cannot offer a fix when there are multiple __all__"), }; // Only emit a fix if there are edits diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap index 02127502398801..0100397db388f6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap @@ -1,13 +1,24 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:8:36: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 6 | import sys 7 | - 8 | from . import exported, renamed as bees - | ^^^^ F401 + 8 | from . import unused, exported, renamed as bees + | ^^^^^^ F401 9 | 10 | if sys.version_info > (3, 9): | - = help: Use an explicit re-export: `bees` + = help: Remove unused import + +__init__.py:8:44: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | + 6 | import sys + 7 | + 8 | from . import unused, exported, renamed as bees + | ^^^^ F401 + 9 | +10 | if sys.version_info > (3, 9): + | + = help: Remove unused import From 190d1917e01fcdcafe1c1227f29e7502e33e0952 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 10:07:38 -0700 Subject: [PATCH 32/49] change find_dunder_all_exprs to return /all/ such exprs regardless of their types; the add_to_dunder_all fix-producing function will still only produce fixes for lists & tuples; update fixture test with non tuple/list __all__ to not recommend redundant alias --- .../F401_27__all_mistyped/__init__.py | 4 ++-- .../src/rules/pyflakes/rules/unused_import.rs | 7 +------ ...01_F401_27__all_mistyped____init__.py.snap | 20 +++++-------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py index 8dc1275743db8b..85baafe7d5af2b 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py @@ -2,10 +2,10 @@ """ -from . import unused # F401: change to redundant alias b/c __all__ cannot be located +from . import unused # F401: recommend add to all w/o fix -from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located +from . import renamed as bees # F401: recommend add to all w/o fix __all__ = None diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 4006b4e2409fee..d6d28a181e3677 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -172,16 +172,11 @@ fn find_dunder_all_exprs<'a>(semantic: &'a SemanticModel) -> Vec<&'a ast::Expr> BindingKind::Export(_) => binding.statement(semantic), _ => None, }?; - let expr = match stmt { + match stmt { Stmt::Assign(ast::StmtAssign { value, .. }) => Some(&**value), Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_deref(), Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(&**value), _ => None, - }?; - match expr { - ast::Expr::List(_) => Some(expr), - ast::Expr::Tuple(_) => Some(expr), - _ => None, } }) .collect() diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index 29f280ce025973..31d6c46676620f 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -1,26 +1,16 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -5 | from . import unused # F401: change to redundant alias b/c __all__ cannot be located +5 | from . import unused # F401: recommend add to all w/o fix | ^^^^^^ F401 | - = help: Use an explicit re-export: `.unused` - -ℹ Safe fix -2 2 | """ -3 3 | -4 4 | -5 |-from . import unused # F401: change to redundant alias b/c __all__ cannot be located - 5 |+from . import unused as unused # F401: change to redundant alias b/c __all__ cannot be located -6 6 | -7 7 | -8 8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located + = help: Add unused import to __all__: `.unused` __init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -8 | from . import renamed as bees # F401: change to redundant alias b/c __all__ cannot be located +8 | from . import renamed as bees # F401: recommend add to all w/o fix | ^^^^ F401 | - = help: Use an explicit re-export: `bees` + = help: Add unused import to __all__: `bees` From 6035e1d91cd283eeec277b83b0530855f4dfe870 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Thu, 9 May 2024 10:33:01 -0700 Subject: [PATCH 33/49] change fix-title for redundant alias case to format itself and return early --- .../ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 6 +++++- ...pyflakes__tests__preview__F401_F401_24____init__.py.snap | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index d6d28a181e3677..4f5842473e6253 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -129,7 +129,11 @@ impl Violation for UnusedImport { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 0, - }) => "Use an explicit re-export", + }) => { + return Some(format!( + "Use an explicit re-export: `{binding} as {binding}`" + )); + } _ => "Remove unused import", }; Some(if *multiple { diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index fdc90bddf82435..46850ca6619e66 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -22,7 +22,7 @@ __init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, ad 33 | from . import unused # F401: change to redundant alias | ^^^^^^ F401 | - = help: Use an explicit re-export: `.unused` + = help: Use an explicit re-export: `unused as unused` ℹ Safe fix 30 30 | from . import aliased as aliased # Ok: is redundant alias @@ -39,4 +39,4 @@ __init__.py:36:26: F401 `.renamed` imported but unused; consider removing, addin 36 | from . import renamed as bees # F401: no fix | ^^^^ F401 | - = help: Use an explicit re-export: `bees` + = help: Use an explicit re-export: `bees as bees` From c81506aebc9ed43ec95a31bb9fb77ee4c6b7bff6 Mon Sep 17 00:00:00 2001 From: plredmond <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 10:55:14 -0700 Subject: [PATCH 34/49] use if-let instead of match with empty branch per alex Co-authored-by: Alex Waygood --- crates/ruff_linter/src/fix/edits.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 1d490bde9bf22c..8e93eab2ec9cb5 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -185,12 +185,11 @@ pub(crate) fn add_to_dunder_all<'a>( _ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point), }) .collect(); - match expr { - ast::Expr::Tuple(tup) if tup.parenthesized && 1 == export_prefix_length + edits.len() => { + if let ast::Expr::Tuple(tup) = expr { + if tup.parenthesized && export_prefix_length + edits.len() == 1 { edits.push(Edit::insertion(",".to_string(), insertion_point)); } - _ => {} - }; + } edits } From bb545a2354762fcf6bacabbd17470c96965a340f Mon Sep 17 00:00:00 2001 From: plredmond <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:01:22 -0700 Subject: [PATCH 35/49] Update crates/ruff_linter/src/fix/edits.rs Co-authored-by: Alex Waygood --- crates/ruff_linter/src/fix/edits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 8e93eab2ec9cb5..8f255af8767327 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -616,7 +616,7 @@ x = 1 \ let program = parse_suite(contents).unwrap(); let stmt = program.first().unwrap(); assert_eq!( - make_redundant_alias(["x"].into_iter().map(std::convert::Into::into), stmt), + make_redundant_alias(["x"].into_iter().map(Cow::from), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), From 1cbf320d997428a84d8badab32b722951756ebf7 Mon Sep 17 00:00:00 2001 From: plredmond <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:03:50 -0700 Subject: [PATCH 36/49] wording improvement per alex Co-authored-by: Alex Waygood --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 4f5842473e6253..6dde205315188c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -443,7 +443,7 @@ fn fix_by_reexporting( dunder_all, checker.stylist(), ), - _ => bail!("Cannot offer a fix when there are multiple __all__"), + _ => bail!("Cannot offer a fix when there are multiple __all__ definitions"), }; // Only emit a fix if there are edits From 64c17e25bd3e2416099b4bf113bd7be3f17ed28a Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:03:19 -0700 Subject: [PATCH 37/49] replace into::Into with Cow::from to make conversion explicit --- crates/ruff_linter/src/fix/edits.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 8f255af8767327..053ed64879e39d 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -527,6 +527,7 @@ fn all_lines_fit( #[cfg(test)] mod tests { use anyhow::{anyhow, Result}; + use std::borrow::Cow; use test_case::test_case; use ruff_diagnostics::{Diagnostic, Edit, Fix}; @@ -624,10 +625,7 @@ x = 1 \ "make just one item redundant" ); assert_eq!( - make_redundant_alias( - vec!["x", "y"].into_iter().map(std::convert::Into::into), - stmt - ), + make_redundant_alias(vec!["x", "y"].into_iter().map(Cow::from), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -635,10 +633,7 @@ x = 1 \ "the second item is already a redundant alias" ); assert_eq!( - make_redundant_alias( - vec!["x", "z"].into_iter().map(std::convert::Into::into), - stmt - ), + make_redundant_alias(vec!["x", "z"].into_iter().map(Cow::from), stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), From a74789777dc3de88b7d22ac053cbf92d170bfc69 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:12:00 -0700 Subject: [PATCH 38/49] improve the wording of "add unused import to __all__" message per alex --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 4 +++- ...sts__preview__F401_F401_25__all_nonempty____init__.py.snap | 4 ++-- ..._tests__preview__F401_F401_26__all_empty____init__.py.snap | 4 ++-- ...sts__preview__F401_F401_27__all_mistyped____init__.py.snap | 4 ++-- ...sts__preview__F401_F401_28__all_multiple____init__.py.snap | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 6dde205315188c..007ba7ffdaa23e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -125,7 +125,9 @@ impl Violation for UnusedImport { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 1, - }) => "Add unused import to __all__", + }) => { + return Some(format!("Add unused import `{binding}` to __all__")); + } Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 0, diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index d0e20067c39450..74635490f8a4de 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -22,7 +22,7 @@ __init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, ad 36 | from . import unused # F401: add to __all__ | ^^^^^^ F401 | - = help: Add unused import to __all__: `.unused` + = help: Add unused import `unused` to __all__ ℹ Safe fix 39 39 | from . import renamed as bees # F401: add to __all__ @@ -36,7 +36,7 @@ __init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, a 39 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__: `bees` + = help: Add unused import `bees` to __all__ ℹ Safe fix 39 39 | from . import renamed as bees # F401: add to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap index dd6dd10cd5e315..4eb88c140e3b39 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -6,7 +6,7 @@ __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, add 5 | from . import unused # F401: add to __all__ | ^^^^^^ F401 | - = help: Add unused import to __all__: `.unused` + = help: Add unused import `unused` to __all__ ℹ Safe fix 8 8 | from . import renamed as bees # F401: add to __all__ @@ -21,7 +21,7 @@ __init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, ad 8 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__: `bees` + = help: Add unused import `bees` to __all__ ℹ Safe fix 8 8 | from . import renamed as bees # F401: add to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index 31d6c46676620f..0f194df7fde1bb 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -6,11 +6,11 @@ __init__.py:5:15: F401 `.unused` imported but unused; consider removing, adding 5 | from . import unused # F401: recommend add to all w/o fix | ^^^^^^ F401 | - = help: Add unused import to __all__: `.unused` + = help: Add unused import `unused` to __all__ __init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 8 | from . import renamed as bees # F401: recommend add to all w/o fix | ^^^^ F401 | - = help: Add unused import to __all__: `bees` + = help: Add unused import `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap index 3b7494f375b41a..b97c7ff59772d0 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap @@ -6,7 +6,7 @@ __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, add 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^^^ F401 | - = help: Add unused import to __all__ + = help: Add unused import `unused` to __all__ ℹ Safe fix 5 5 | from . import unused, renamed as bees # F401: add to __all__ @@ -21,7 +21,7 @@ __init__.py:5:34: F401 [*] `.renamed` imported but unused; consider removing, ad 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^ F401 | - = help: Add unused import to __all__ + = help: Add unused import `bees` to __all__ ℹ Safe fix 5 5 | from . import unused, renamed as bees # F401: add to __all__ From edc88f3defbd36eb707cb48f96c87eb5feb45fbe Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:19:27 -0700 Subject: [PATCH 39/49] reformat a function -- noop w.r.t. the tests --- .../src/rules/pyflakes/rules/unused_import.rs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 007ba7ffdaa23e..483046675f6b1c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -121,30 +121,27 @@ impl Violation for UnusedImport { multiple, .. } = self; - let resolution = match self.context { + match self.context { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 1, - }) => { - return Some(format!("Add unused import `{binding}` to __all__")); - } + }) => Some(format!("Add unused import `{binding}` to __all__")), + Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 0, - }) => { - return Some(format!( - "Use an explicit re-export: `{binding} as {binding}`" - )); - } - _ => "Remove unused import", - }; - Some(if *multiple { - resolution.to_string() - } else if name.ends_with(binding) { - format!("{resolution}: `{name}`") - } else { - format!("{resolution}: `{binding}`") - }) + }) => Some(format!( + "Use an explicit re-export: `{binding} as {binding}`" + )), + + _ => Some(if *multiple { + "Remove unused import".to_string() + } else if name.ends_with(binding) { + format!("Remove unused import: `{name}`") + } else { + format!("Remove unused import: `{binding}`") + }), + } } } From 117a1f06824bf84b39c461fdf046715af0ce731b Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Mon, 13 May 2024 11:22:47 -0700 Subject: [PATCH 40/49] remove a now unnecessary branch, which also makes the overall PR diff smaller --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 4 +--- .../ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap | 4 ++-- .../ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 483046675f6b1c..6fe44cf88a5786 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -136,10 +136,8 @@ impl Violation for UnusedImport { _ => Some(if *multiple { "Remove unused import".to_string() - } else if name.ends_with(binding) { - format!("Remove unused import: `{name}`") } else { - format!("Remove unused import: `{binding}`") + format!("Remove unused import: `{name}`") }), } } diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap index a9ae7aa7648747..67c75c0655a623 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap @@ -27,7 +27,7 @@ F401_5.py:3:22: F401 [*] `d.e.f` imported but unused 4 | import h.i 5 | import j.k as l | - = help: Remove unused import: `g` + = help: Remove unused import: `d.e.f` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -60,7 +60,7 @@ F401_5.py:5:15: F401 [*] `j.k` imported but unused 5 | import j.k as l | ^ F401 | - = help: Remove unused import: `l` + = help: Remove unused import: `j.k` ℹ Safe fix 2 2 | from a.b import c diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap index ad483af4e0132c..a78064afd7568a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap @@ -28,7 +28,7 @@ F401_6.py:10:43: F401 [*] `.datastructures.UploadFile` imported but unused 11 | 12 | # OK | - = help: Remove unused import: `FileUpload` + = help: Remove unused import: `.datastructures.UploadFile` ℹ Safe fix 7 7 | from .background import BackgroundTasks From 1b167e61889ccc0bdec6630e454bf003acaa261b Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 10:29:18 -0700 Subject: [PATCH 41/49] call .end() directly instead of going through .range or .range(); remove unnecessary import paths --- crates/ruff_linter/src/fix/edits.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 053ed64879e39d..3f3a5b8c6e0891 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -6,7 +6,7 @@ use anyhow::{Context, Result}; use ruff_diagnostics::Edit; use ruff_python_ast::parenthesize::parenthesized_range; -use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Stmt}; +use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Stmt}; use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; @@ -153,18 +153,18 @@ pub(crate) fn add_to_dunder_all<'a>( stylist: &Stylist, ) -> Vec { let (insertion_point, export_prefix_length) = match expr { - ast::Expr::List(ast::ExprList { elts, range, .. }) => ( + Expr::List(ExprList { elts, range, .. }) => ( elts.last() - .map_or(range.end() - "]".text_len(), |elt| elt.range().end()), + .map_or(range.end() - "]".text_len(), |elt| elt.end()), elts.len(), ), - ast::Expr::Tuple(tup) if tup.parenthesized => ( + Expr::Tuple(tup) if tup.parenthesized => ( tup.elts .last() - .map_or(tup.range.end() - ")".text_len(), |elt| elt.range().end()), + .map_or(tup.end() - ")".text_len(), |elt| elt.end()), tup.elts.len(), ), - ast::Expr::Tuple(tup) if !tup.parenthesized => ( + Expr::Tuple(tup) if !tup.parenthesized => ( tup.elts .last() .expect("unparenthesized empty tuple is not possible") @@ -185,7 +185,7 @@ pub(crate) fn add_to_dunder_all<'a>( _ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point), }) .collect(); - if let ast::Expr::Tuple(tup) = expr { + if let Expr::Tuple(tup) = expr { if tup.parenthesized && export_prefix_length + edits.len() == 1 { edits.push(Edit::insertion(",".to_string(), insertion_point)); } From 26d4714b729d1abb34a3167867634af247ac925b Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 10:30:37 -0700 Subject: [PATCH 42/49] readd erroneously removed newlines to fixture snaps --- .../ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap | 2 ++ .../ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap index 67c75c0655a623..e4317c5d195e7a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap @@ -67,3 +67,5 @@ F401_5.py:5:15: F401 [*] `j.k` imported but unused 3 3 | from d.e import f as g 4 4 | import h.i 5 |-import j.k as l + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap index a78064afd7568a..d0db5ee40d5df2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap @@ -71,3 +71,5 @@ F401_6.py:19:26: F401 [*] `datastructures` imported but unused 17 17 | 18 18 | # F401 `datastructures` imported but unused 19 |-import datastructures as structures + + From 5dbdcfe1851e80a7e998b36176bd84180a17d4be Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 10:33:59 -0700 Subject: [PATCH 43/49] ruff format the fixture __init__.py files --- .../test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py | 4 ++-- .../test/fixtures/pyflakes/F401_26__all_empty/__init__.py | 5 ++--- .../test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py | 4 ++-- .../test/fixtures/pyflakes/F401_28__all_multiple/__init__.py | 3 +-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py index 0f792c90c70732..874b9bde483128 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_25__all_nonempty/__init__.py @@ -33,10 +33,10 @@ from . import exported # Ok: is exported in __all__ -from . import unused # F401: add to __all__ +from . import unused # F401: add to __all__ -from . import renamed as bees # F401: add to __all__ +from . import renamed as bees # F401: add to __all__ __all__ = ["argparse", "exported"] diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py index b742dfda2225a1..1ef75a30d65ec3 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py @@ -2,11 +2,10 @@ """ -from . import unused # F401: add to __all__ +from . import unused # F401: add to __all__ -from . import renamed as bees # F401: add to __all__ +from . import renamed as bees # F401: add to __all__ __all__ = [] - diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py index 85baafe7d5af2b..dc913f390f5c74 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py @@ -2,10 +2,10 @@ """ -from . import unused # F401: recommend add to all w/o fix +from . import unused # F401: recommend add to all w/o fix -from . import renamed as bees # F401: recommend add to all w/o fix +from . import renamed as bees # F401: recommend add to all w/o fix __all__ = None diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py index 3ab7d9bd3ecc51..28dbb9f0222988 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py @@ -2,8 +2,7 @@ """ -from . import unused, renamed as bees # F401: add to __all__ +from . import unused, renamed as bees # F401: add to __all__ __all__ = []; - From 64fa9af0f62da9c2497380203d5e5fc38b3470dc Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 10:35:33 -0700 Subject: [PATCH 44/49] cargo insta review -- accept whitespace changes (see `git diff -w`) --- ...eview__F401_F401_25__all_nonempty____init__.py.snap | 8 ++++---- ..._preview__F401_F401_26__all_empty____init__.py.snap | 10 ++++------ ...eview__F401_F401_27__all_mistyped____init__.py.snap | 4 ++-- ...eview__F401_F401_28__all_multiple____init__.py.snap | 10 ++++------ 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 74635490f8a4de..9d04194da7494b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -19,13 +19,13 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding __init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -36 | from . import unused # F401: add to __all__ +36 | from . import unused # F401: add to __all__ | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ ℹ Safe fix -39 39 | from . import renamed as bees # F401: add to __all__ +39 39 | from . import renamed as bees # F401: add to __all__ 40 40 | 41 41 | 42 |-__all__ = ["argparse", "exported"] @@ -33,13 +33,13 @@ __init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, ad __init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -39 | from . import renamed as bees # F401: add to __all__ +39 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | = help: Add unused import `bees` to __all__ ℹ Safe fix -39 39 | from . import renamed as bees # F401: add to __all__ +39 39 | from . import renamed as bees # F401: add to __all__ 40 40 | 41 41 | 42 |-__all__ = ["argparse", "exported"] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap index 4eb88c140e3b39..6c393f0fbfce91 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -3,30 +3,28 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -5 | from . import unused # F401: add to __all__ +5 | from . import unused # F401: add to __all__ | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ ℹ Safe fix -8 8 | from . import renamed as bees # F401: add to __all__ +8 8 | from . import renamed as bees # F401: add to __all__ 9 9 | 10 10 | 11 |-__all__ = [] 11 |+__all__ = ["unused"] -12 12 | __init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -8 | from . import renamed as bees # F401: add to __all__ +8 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 | = help: Add unused import `bees` to __all__ ℹ Safe fix -8 8 | from . import renamed as bees # F401: add to __all__ +8 8 | from . import renamed as bees # F401: add to __all__ 9 9 | 10 10 | 11 |-__all__ = [] 11 |+__all__ = ["bees"] -12 12 | diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index 0f194df7fde1bb..c665795df94472 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -3,14 +3,14 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- __init__.py:5:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -5 | from . import unused # F401: recommend add to all w/o fix +5 | from . import unused # F401: recommend add to all w/o fix | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ __init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -8 | from . import renamed as bees # F401: recommend add to all w/o fix +8 | from . import renamed as bees # F401: recommend add to all w/o fix | ^^^^ F401 | = help: Add unused import `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap index b97c7ff59772d0..a77b95641d6191 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap @@ -3,30 +3,28 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -5 | from . import unused, renamed as bees # F401: add to __all__ +5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ ℹ Safe fix -5 5 | from . import unused, renamed as bees # F401: add to __all__ +5 5 | from . import unused, renamed as bees # F401: add to __all__ 6 6 | 7 7 | 8 |-__all__ = []; 8 |+__all__ = ["bees", "unused"]; -9 9 | __init__.py:5:34: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | -5 | from . import unused, renamed as bees # F401: add to __all__ +5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^ F401 | = help: Add unused import `bees` to __all__ ℹ Safe fix -5 5 | from . import unused, renamed as bees # F401: add to __all__ +5 5 | from . import unused, renamed as bees # F401: add to __all__ 6 6 | 7 7 | 8 |-__all__ = []; 8 |+__all__ = ["bees", "unused"]; -9 9 | From 527d3954b5e024bfb88f79f05185d408f0b4a089 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 10:37:13 -0700 Subject: [PATCH 45/49] clippy fix -- replace closure with static reference to method --- crates/ruff_linter/src/fix/edits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 3f3a5b8c6e0891..3d45f1ea01bb18 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -155,13 +155,13 @@ pub(crate) fn add_to_dunder_all<'a>( let (insertion_point, export_prefix_length) = match expr { Expr::List(ExprList { elts, range, .. }) => ( elts.last() - .map_or(range.end() - "]".text_len(), |elt| elt.end()), + .map_or(range.end() - "]".text_len(), Ranged::end), elts.len(), ), Expr::Tuple(tup) if tup.parenthesized => ( tup.elts .last() - .map_or(tup.end() - ")".text_len(), |elt| elt.end()), + .map_or(tup.end() - ")".text_len(), Ranged::end), tup.elts.len(), ), Expr::Tuple(tup) if !tup.parenthesized => ( From 548180b3ffb0bfcada85976777494a9c00269d57 Mon Sep 17 00:00:00 2001 From: plredmond <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 11:20:57 -0700 Subject: [PATCH 46/49] collect only 2 `__all__` definitions and then stop pulling the iterator Co-authored-by: Alex Waygood --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 6fe44cf88a5786..9938d43b733605 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -180,6 +180,7 @@ fn find_dunder_all_exprs<'a>(semantic: &'a SemanticModel) -> Vec<&'a ast::Expr> _ => None, } }) + .take(2) .collect() } From 01e974fa7fcee43f16060c0d3b7f35172165d984 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 11:28:13 -0700 Subject: [PATCH 47/49] Revert "collect only 2 `__all__` definitions and then stop pulling the iterator" This reverts commit 548180b3ffb0bfcada85976777494a9c00269d57. --- crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 9938d43b733605..6fe44cf88a5786 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -180,7 +180,6 @@ fn find_dunder_all_exprs<'a>(semantic: &'a SemanticModel) -> Vec<&'a ast::Expr> _ => None, } }) - .take(2) .collect() } From 0696df52aa86fb73496ddaf05e0d675895f4d8c8 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 11:34:02 -0700 Subject: [PATCH 48/49] remove the qualified-name field from UnusedImport in favor of the binding-name field (named as .name); update fixture test results --- .../src/rules/pyflakes/rules/unused_import.rs | 23 +++++-------------- ...ules__pyflakes__tests__F401_F401_0.py.snap | 10 ++++---- ...les__pyflakes__tests__F401_F401_11.py.snap | 6 ++--- ...les__pyflakes__tests__F401_F401_15.py.snap | 6 ++--- ...les__pyflakes__tests__F401_F401_17.py.snap | 10 ++++---- ...les__pyflakes__tests__F401_F401_23.py.snap | 4 ++-- ...ules__pyflakes__tests__F401_F401_5.py.snap | 14 +++++------ ...ules__pyflakes__tests__F401_F401_6.py.snap | 14 +++++------ ...ules__pyflakes__tests__F401_F401_7.py.snap | 10 ++++---- ...ules__pyflakes__tests__F401_F401_9.py.snap | 6 ++--- ...__pyflakes__tests__future_annotations.snap | 6 ++--- ...s__preview__F401_F401_24____init__.py.snap | 4 ++-- ...01_F401_25__all_nonempty____init__.py.snap | 4 ++-- ..._F401_F401_26__all_empty____init__.py.snap | 4 ++-- ...01_F401_27__all_mistyped____init__.py.snap | 4 ++-- ...01_F401_28__all_multiple____init__.py.snap | 4 ++-- ...F401_29__all_conditional____init__.py.snap | 4 ++-- ..._linter__rules__ruff__tests__ruf100_1.snap | 10 ++++---- 18 files changed, 56 insertions(+), 87 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 6fe44cf88a5786..e838cd3905e45f 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -85,10 +85,8 @@ enum UnusedImportContext { /// - [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols) #[violation] pub struct UnusedImport { - /// Qualified name of the import - name: String, /// Name of the import binding - binding: String, + name: String, context: Option, multiple: bool, } @@ -115,24 +113,17 @@ impl Violation for UnusedImport { } fn fix_title(&self) -> Option { - let UnusedImport { - name, - binding, - multiple, - .. - } = self; + let UnusedImport { name, multiple, .. } = self; match self.context { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 1, - }) => Some(format!("Add unused import `{binding}` to __all__")), + }) => Some(format!("Add unused import `{name}` to __all__")), Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 0, - }) => Some(format!( - "Use an explicit re-export: `{binding} as {binding}`" - )), + }) => Some(format!("Use an explicit re-export: `{name} as {name}`")), _ => Some(if *multiple { "Remove unused import".to_string() @@ -319,8 +310,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut ) { let mut diagnostic = Diagnostic::new( UnusedImport { - name: binding.import.qualified_name().to_string(), - binding: binding.name.to_string(), + name: binding.name.to_string(), context, multiple, }, @@ -343,8 +333,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut for binding in ignored.into_values().flatten() { let mut diagnostic = Diagnostic::new( UnusedImport { - name: binding.import.qualified_name().to_string(), - binding: binding.name.to_string(), + name: binding.name.to_string(), context: None, multiple: false, }, diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap index 433865de234817..e4868ec3d8507e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap @@ -19,7 +19,7 @@ F401_0.py:2:8: F401 [*] `functools` imported but unused 4 4 | from collections import ( 5 5 | Counter, -F401_0.py:6:5: F401 [*] `collections.OrderedDict` imported but unused +F401_0.py:6:5: F401 [*] `OrderedDict` imported but unused | 4 | from collections import ( 5 | Counter, @@ -28,7 +28,7 @@ F401_0.py:6:5: F401 [*] `collections.OrderedDict` imported but unused 7 | namedtuple, 8 | ) | - = help: Remove unused import: `collections.OrderedDict` + = help: Remove unused import: `OrderedDict` ℹ Safe fix 3 3 | from datetime import datetime @@ -268,13 +268,13 @@ F401_0.py:114:16: F401 [*] `b2` imported but unused 116 115 | 117 116 | # Regression test for: https://github.com/astral-sh/ruff/issues/7244 -F401_0.py:122:1: F401 [*] `datameta_client_lib.model_helpers.noqa` imported but unused +F401_0.py:122:1: F401 [*] `noqa` imported but unused | 121 | from datameta_client_lib.model_helpers import ( 122 | noqa ) | ^^^^ F401 | - = help: Remove unused import: `datameta_client_lib.model_helpers.noqa` + = help: Remove unused import: `noqa` ℹ Safe fix 118 118 | from datameta_client_lib.model_utils import ( # noqa: F401 @@ -282,5 +282,3 @@ F401_0.py:122:1: F401 [*] `datameta_client_lib.model_helpers.noqa` imported but 120 120 | 121 |-from datameta_client_lib.model_helpers import ( 122 |-noqa ) - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap index 39c9b95a011e20..e78cd69387f0fb 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_11.py:4:27: F401 [*] `pathlib.PurePath` imported but unused +F401_11.py:4:27: F401 [*] `PurePath` imported but unused | 3 | from typing import List 4 | from pathlib import Path, PurePath | ^^^^^^^^ F401 | - = help: Remove unused import: `pathlib.PurePath` + = help: Remove unused import: `PurePath` ℹ Safe fix 1 1 | """Test: parsing of nested string annotations.""" @@ -18,5 +18,3 @@ F401_11.py:4:27: F401 [*] `pathlib.PurePath` imported but unused 5 5 | 6 6 | 7 7 | x: """List['Path']""" = [] - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap index cd5526e8f4fde7..6dc0c75faf6fbf 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_15.py:5:25: F401 [*] `pathlib.Path` imported but unused +F401_15.py:5:25: F401 [*] `Path` imported but unused | 4 | if TYPE_CHECKING: 5 | from pathlib import Path | ^^^^ F401 | - = help: Remove unused import: `pathlib.Path` + = help: Remove unused import: `Path` ℹ Safe fix 2 2 | from django.db.models import ForeignKey @@ -18,5 +18,3 @@ F401_15.py:5:25: F401 [*] `pathlib.Path` imported but unused 6 6 | 7 7 | 8 8 | class Foo: - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap index caf5d2c6767c33..a83b7f71a4c3d6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_17.py:12:27: F401 [*] `threading.Thread` imported but unused +F401_17.py:12:27: F401 [*] `Thread` imported but unused | 11 | def fn(thread: Thread): 12 | from threading import Thread @@ -9,7 +9,7 @@ F401_17.py:12:27: F401 [*] `threading.Thread` imported but unused 13 | 14 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the | - = help: Remove unused import: `threading.Thread` + = help: Remove unused import: `Thread` ℹ Safe fix 9 9 | @@ -20,7 +20,7 @@ F401_17.py:12:27: F401 [*] `threading.Thread` imported but unused 14 13 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the 15 14 | # top level. -F401_17.py:20:27: F401 [*] `threading.Thread` imported but unused +F401_17.py:20:27: F401 [*] `Thread` imported but unused | 19 | def fn(thread: Thread): 20 | from threading import Thread @@ -28,7 +28,7 @@ F401_17.py:20:27: F401 [*] `threading.Thread` imported but unused 21 | 22 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the | - = help: Remove unused import: `threading.Thread` + = help: Remove unused import: `Thread` ℹ Safe fix 17 17 | @@ -38,5 +38,3 @@ F401_17.py:20:27: F401 [*] `threading.Thread` imported but unused 21 20 | 22 21 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the 23 22 | # top level. - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap index afde9e550a87db..fa0fce1874e39e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_23.py:4:16: F401 [*] `re.RegexFlag` imported but unused +F401_23.py:4:16: F401 [*] `RegexFlag` imported but unused | 3 | from pathlib import Path 4 | from re import RegexFlag | ^^^^^^^^^ F401 5 | from typing import Annotated | - = help: Remove unused import: `re.RegexFlag` + = help: Remove unused import: `RegexFlag` ℹ Safe fix 1 1 | """Test: ensure that we treat strings in `typing.Annotation` as type definitions.""" diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap index e4317c5d195e7a..c987c0dc971fad 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_5.py:2:17: F401 [*] `a.b.c` imported but unused +F401_5.py:2:17: F401 [*] `c` imported but unused | 1 | """Test: removal of multi-segment and aliases imports.""" 2 | from a.b import c @@ -9,7 +9,7 @@ F401_5.py:2:17: F401 [*] `a.b.c` imported but unused 3 | from d.e import f as g 4 | import h.i | - = help: Remove unused import: `a.b.c` + = help: Remove unused import: `c` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -18,7 +18,7 @@ F401_5.py:2:17: F401 [*] `a.b.c` imported but unused 4 3 | import h.i 5 4 | import j.k as l -F401_5.py:3:22: F401 [*] `d.e.f` imported but unused +F401_5.py:3:22: F401 [*] `g` imported but unused | 1 | """Test: removal of multi-segment and aliases imports.""" 2 | from a.b import c @@ -27,7 +27,7 @@ F401_5.py:3:22: F401 [*] `d.e.f` imported but unused 4 | import h.i 5 | import j.k as l | - = help: Remove unused import: `d.e.f` + = help: Remove unused import: `g` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -53,19 +53,17 @@ F401_5.py:4:8: F401 [*] `h.i` imported but unused 4 |-import h.i 5 4 | import j.k as l -F401_5.py:5:15: F401 [*] `j.k` imported but unused +F401_5.py:5:15: F401 [*] `l` imported but unused | 3 | from d.e import f as g 4 | import h.i 5 | import j.k as l | ^ F401 | - = help: Remove unused import: `j.k` + = help: Remove unused import: `l` ℹ Safe fix 2 2 | from a.b import c 3 3 | from d.e import f as g 4 4 | import h.i 5 |-import j.k as l - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap index d0db5ee40d5df2..094f7d25d79c10 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_6.py:7:25: F401 [*] `.background.BackgroundTasks` imported but unused +F401_6.py:7:25: F401 [*] `BackgroundTasks` imported but unused | 6 | # F401 `background.BackgroundTasks` imported but unused 7 | from .background import BackgroundTasks @@ -9,7 +9,7 @@ F401_6.py:7:25: F401 [*] `.background.BackgroundTasks` imported but unused 8 | 9 | # F401 `datastructures.UploadFile` imported but unused | - = help: Remove unused import: `.background.BackgroundTasks` + = help: Remove unused import: `BackgroundTasks` ℹ Safe fix 4 4 | from .applications import FastAPI as FastAPI @@ -20,7 +20,7 @@ F401_6.py:7:25: F401 [*] `.background.BackgroundTasks` imported but unused 9 8 | # F401 `datastructures.UploadFile` imported but unused 10 9 | from .datastructures import UploadFile as FileUpload -F401_6.py:10:43: F401 [*] `.datastructures.UploadFile` imported but unused +F401_6.py:10:43: F401 [*] `FileUpload` imported but unused | 9 | # F401 `datastructures.UploadFile` imported but unused 10 | from .datastructures import UploadFile as FileUpload @@ -28,7 +28,7 @@ F401_6.py:10:43: F401 [*] `.datastructures.UploadFile` imported but unused 11 | 12 | # OK | - = help: Remove unused import: `.datastructures.UploadFile` + = help: Remove unused import: `FileUpload` ℹ Safe fix 7 7 | from .background import BackgroundTasks @@ -58,18 +58,16 @@ F401_6.py:16:8: F401 [*] `background` imported but unused 18 17 | # F401 `datastructures` imported but unused 19 18 | import datastructures as structures -F401_6.py:19:26: F401 [*] `datastructures` imported but unused +F401_6.py:19:26: F401 [*] `structures` imported but unused | 18 | # F401 `datastructures` imported but unused 19 | import datastructures as structures | ^^^^^^^^^^ F401 | - = help: Remove unused import: `datastructures` + = help: Remove unused import: `structures` ℹ Safe fix 16 16 | import background 17 17 | 18 18 | # F401 `datastructures` imported but unused 19 |-import datastructures as structures - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap index cb4ec750285925..7c6792b3ef5a8e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_7.py:30:5: F401 [*] `typing.Union` imported but unused +F401_7.py:30:5: F401 [*] `Union` imported but unused | 28 | from typing import ( 29 | Mapping, # noqa: F401 @@ -9,7 +9,7 @@ F401_7.py:30:5: F401 [*] `typing.Union` imported but unused | ^^^^^ F401 31 | ) | - = help: Remove unused import: `typing.Union` + = help: Remove unused import: `Union` ℹ Safe fix 27 27 | # This should ignore the first error. @@ -22,7 +22,7 @@ F401_7.py:30:5: F401 [*] `typing.Union` imported but unused 33 32 | # This should ignore both errors. 34 33 | from typing import ( # noqa -F401_7.py:66:20: F401 [*] `typing.Awaitable` imported but unused +F401_7.py:66:20: F401 [*] `Awaitable` imported but unused | 65 | # This should mark F501 as unused. 66 | from typing import Awaitable, AwaitableGenerator # noqa: F501 @@ -36,7 +36,7 @@ F401_7.py:66:20: F401 [*] `typing.Awaitable` imported but unused 65 65 | # This should mark F501 as unused. 66 |-from typing import Awaitable, AwaitableGenerator # noqa: F501 -F401_7.py:66:31: F401 [*] `typing.AwaitableGenerator` imported but unused +F401_7.py:66:31: F401 [*] `AwaitableGenerator` imported but unused | 65 | # This should mark F501 as unused. 66 | from typing import Awaitable, AwaitableGenerator # noqa: F501 @@ -49,5 +49,3 @@ F401_7.py:66:31: F401 [*] `typing.AwaitableGenerator` imported but unused 64 64 | 65 65 | # This should mark F501 as unused. 66 |-from typing import Awaitable, AwaitableGenerator # noqa: F501 - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap index c7071cc24fbfa5..f761517e7a0a92 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_9.py:4:22: F401 [*] `foo.baz` imported but unused +F401_9.py:4:22: F401 [*] `baz` imported but unused | 3 | __all__ = ("bar",) 4 | from foo import bar, baz | ^^^ F401 | - = help: Remove unused import: `foo.baz` + = help: Remove unused import: `baz` ℹ Safe fix 1 1 | """Test: late-binding of `__all__`.""" @@ -15,5 +15,3 @@ F401_9.py:4:22: F401 [*] `foo.baz` imported but unused 3 3 | __all__ = ("bar",) 4 |-from foo import bar, baz 4 |+from foo import bar - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap index 5a15b089872bd9..b00296aff5532d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -future_annotations.py:8:5: F401 [*] `models.Nut` imported but unused +future_annotations.py:8:5: F401 [*] `Nut` imported but unused | 6 | from models import ( 7 | Fruit, @@ -9,7 +9,7 @@ future_annotations.py:8:5: F401 [*] `models.Nut` imported but unused | ^^^ F401 9 | ) | - = help: Remove unused import: `models.Nut` + = help: Remove unused import: `Nut` ℹ Safe fix 5 5 | @@ -27,5 +27,3 @@ future_annotations.py:26:19: F821 Undefined name `Bar` | ^^^ F821 27 | return cls(x=0, y=0) | - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index 46850ca6619e66..2d1cec65600691 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -17,7 +17,7 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding 21 20 | 22 21 | # first-party -__init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:33:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 33 | from . import unused # F401: change to redundant alias | ^^^^^^ F401 @@ -34,7 +34,7 @@ __init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, ad 35 35 | 36 36 | from . import renamed as bees # F401: no fix -__init__.py:36:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:36:26: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 36 | from . import renamed as bees # F401: no fix | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 9d04194da7494b..9f5ced4132a025 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -17,7 +17,7 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding 21 20 | 22 21 | # first-party -__init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:36:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 36 | from . import unused # F401: add to __all__ | ^^^^^^ F401 @@ -31,7 +31,7 @@ __init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, ad 42 |-__all__ = ["argparse", "exported"] 42 |+__all__ = ["argparse", "exported", "unused"] -__init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:39:26: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 39 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap index 6c393f0fbfce91..b5fab3893b1405 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused # F401: add to __all__ | ^^^^^^ F401 @@ -15,7 +15,7 @@ __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, add 11 |-__all__ = [] 11 |+__all__ = ["unused"] -__init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:26: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 8 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index c665795df94472..f04b42cd9a1f0e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused # F401: recommend add to all w/o fix | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ -__init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:26: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 8 | from . import renamed as bees # F401: recommend add to all w/o fix | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap index a77b95641d6191..5a3f18a41dfc9a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^^^ F401 @@ -15,7 +15,7 @@ __init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, add 8 |-__all__ = []; 8 |+__all__ = ["bees", "unused"]; -__init__.py:5:34: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:34: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap index 0100397db388f6..1b6d523467d5d2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:8:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:15: F401 `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 6 | import sys 7 | @@ -12,7 +12,7 @@ __init__.py:8:15: F401 `.unused` imported but unused; consider removing, adding | = help: Remove unused import -__init__.py:8:44: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:44: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 6 | import sys 7 | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap index bd0aaf365a2cf6..8c9f695740b46b 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF100_1.py:37:9: F401 [*] `typing.Union` imported but unused +RUF100_1.py:37:9: F401 [*] `Union` imported but unused | 35 | from typing import ( 36 | Mapping, # noqa: F401 @@ -9,7 +9,7 @@ RUF100_1.py:37:9: F401 [*] `typing.Union` imported but unused | ^^^^^ F401 38 | ) | - = help: Remove unused import: `typing.Union` + = help: Remove unused import: `Union` ℹ Safe fix 34 34 | # This should ignore the first error. @@ -103,7 +103,7 @@ RUF100_1.py:72:27: RUF100 [*] Unused `noqa` directive (non-enabled: `F501`) 74 74 | ) 75 75 | -RUF100_1.py:89:24: F401 [*] `typing.Awaitable` imported but unused +RUF100_1.py:89:24: F401 [*] `Awaitable` imported but unused | 87 | def f(): 88 | # This should mark F501 as unused. @@ -119,7 +119,7 @@ RUF100_1.py:89:24: F401 [*] `typing.Awaitable` imported but unused 89 |- from typing import Awaitable, AwaitableGenerator # noqa: F501 89 |+ pass # noqa: F501 -RUF100_1.py:89:35: F401 [*] `typing.AwaitableGenerator` imported but unused +RUF100_1.py:89:35: F401 [*] `AwaitableGenerator` imported but unused | 87 | def f(): 88 | # This should mark F501 as unused. @@ -150,5 +150,3 @@ RUF100_1.py:89:55: RUF100 [*] Unused `noqa` directive (non-enabled: `F501`) 88 88 | # This should mark F501 as unused. 89 |- from typing import Awaitable, AwaitableGenerator # noqa: F501 89 |+ from typing import Awaitable, AwaitableGenerator - - From dde97562cf843300cf96bb78fa88c68989769502 Mon Sep 17 00:00:00 2001 From: PLR <51248199+plredmond@users.noreply.github.com> Date: Tue, 14 May 2024 13:53:17 -0700 Subject: [PATCH 49/49] Revert "remove the qualified-name field from UnusedImport in favor of the binding-name field (named as .name); update fixture test results" This reverts commit 0696df52aa86fb73496ddaf05e0d675895f4d8c8. --- .../src/rules/pyflakes/rules/unused_import.rs | 23 ++++++++++++++----- ...ules__pyflakes__tests__F401_F401_0.py.snap | 10 ++++---- ...les__pyflakes__tests__F401_F401_11.py.snap | 6 +++-- ...les__pyflakes__tests__F401_F401_15.py.snap | 6 +++-- ...les__pyflakes__tests__F401_F401_17.py.snap | 10 ++++---- ...les__pyflakes__tests__F401_F401_23.py.snap | 4 ++-- ...ules__pyflakes__tests__F401_F401_5.py.snap | 14 ++++++----- ...ules__pyflakes__tests__F401_F401_6.py.snap | 14 ++++++----- ...ules__pyflakes__tests__F401_F401_7.py.snap | 10 ++++---- ...ules__pyflakes__tests__F401_F401_9.py.snap | 6 +++-- ...__pyflakes__tests__future_annotations.snap | 6 +++-- ...s__preview__F401_F401_24____init__.py.snap | 4 ++-- ...01_F401_25__all_nonempty____init__.py.snap | 4 ++-- ..._F401_F401_26__all_empty____init__.py.snap | 4 ++-- ...01_F401_27__all_mistyped____init__.py.snap | 4 ++-- ...01_F401_28__all_multiple____init__.py.snap | 4 ++-- ...F401_29__all_conditional____init__.py.snap | 4 ++-- ..._linter__rules__ruff__tests__ruf100_1.snap | 10 ++++---- 18 files changed, 87 insertions(+), 56 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index e838cd3905e45f..6fe44cf88a5786 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -85,8 +85,10 @@ enum UnusedImportContext { /// - [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols) #[violation] pub struct UnusedImport { - /// Name of the import binding + /// Qualified name of the import name: String, + /// Name of the import binding + binding: String, context: Option, multiple: bool, } @@ -113,17 +115,24 @@ impl Violation for UnusedImport { } fn fix_title(&self) -> Option { - let UnusedImport { name, multiple, .. } = self; + let UnusedImport { + name, + binding, + multiple, + .. + } = self; match self.context { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 1, - }) => Some(format!("Add unused import `{name}` to __all__")), + }) => Some(format!("Add unused import `{binding}` to __all__")), Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 0, - }) => Some(format!("Use an explicit re-export: `{name} as {name}`")), + }) => Some(format!( + "Use an explicit re-export: `{binding} as {binding}`" + )), _ => Some(if *multiple { "Remove unused import".to_string() @@ -310,7 +319,8 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut ) { let mut diagnostic = Diagnostic::new( UnusedImport { - name: binding.name.to_string(), + name: binding.import.qualified_name().to_string(), + binding: binding.name.to_string(), context, multiple, }, @@ -333,7 +343,8 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut for binding in ignored.into_values().flatten() { let mut diagnostic = Diagnostic::new( UnusedImport { - name: binding.name.to_string(), + name: binding.import.qualified_name().to_string(), + binding: binding.name.to_string(), context: None, multiple: false, }, diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap index e4868ec3d8507e..433865de234817 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_0.py.snap @@ -19,7 +19,7 @@ F401_0.py:2:8: F401 [*] `functools` imported but unused 4 4 | from collections import ( 5 5 | Counter, -F401_0.py:6:5: F401 [*] `OrderedDict` imported but unused +F401_0.py:6:5: F401 [*] `collections.OrderedDict` imported but unused | 4 | from collections import ( 5 | Counter, @@ -28,7 +28,7 @@ F401_0.py:6:5: F401 [*] `OrderedDict` imported but unused 7 | namedtuple, 8 | ) | - = help: Remove unused import: `OrderedDict` + = help: Remove unused import: `collections.OrderedDict` ℹ Safe fix 3 3 | from datetime import datetime @@ -268,13 +268,13 @@ F401_0.py:114:16: F401 [*] `b2` imported but unused 116 115 | 117 116 | # Regression test for: https://github.com/astral-sh/ruff/issues/7244 -F401_0.py:122:1: F401 [*] `noqa` imported but unused +F401_0.py:122:1: F401 [*] `datameta_client_lib.model_helpers.noqa` imported but unused | 121 | from datameta_client_lib.model_helpers import ( 122 | noqa ) | ^^^^ F401 | - = help: Remove unused import: `noqa` + = help: Remove unused import: `datameta_client_lib.model_helpers.noqa` ℹ Safe fix 118 118 | from datameta_client_lib.model_utils import ( # noqa: F401 @@ -282,3 +282,5 @@ F401_0.py:122:1: F401 [*] `noqa` imported but unused 120 120 | 121 |-from datameta_client_lib.model_helpers import ( 122 |-noqa ) + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap index e78cd69387f0fb..39c9b95a011e20 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_11.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_11.py:4:27: F401 [*] `PurePath` imported but unused +F401_11.py:4:27: F401 [*] `pathlib.PurePath` imported but unused | 3 | from typing import List 4 | from pathlib import Path, PurePath | ^^^^^^^^ F401 | - = help: Remove unused import: `PurePath` + = help: Remove unused import: `pathlib.PurePath` ℹ Safe fix 1 1 | """Test: parsing of nested string annotations.""" @@ -18,3 +18,5 @@ F401_11.py:4:27: F401 [*] `PurePath` imported but unused 5 5 | 6 6 | 7 7 | x: """List['Path']""" = [] + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap index 6dc0c75faf6fbf..cd5526e8f4fde7 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_15.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_15.py:5:25: F401 [*] `Path` imported but unused +F401_15.py:5:25: F401 [*] `pathlib.Path` imported but unused | 4 | if TYPE_CHECKING: 5 | from pathlib import Path | ^^^^ F401 | - = help: Remove unused import: `Path` + = help: Remove unused import: `pathlib.Path` ℹ Safe fix 2 2 | from django.db.models import ForeignKey @@ -18,3 +18,5 @@ F401_15.py:5:25: F401 [*] `Path` imported but unused 6 6 | 7 7 | 8 8 | class Foo: + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap index a83b7f71a4c3d6..caf5d2c6767c33 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_17.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_17.py:12:27: F401 [*] `Thread` imported but unused +F401_17.py:12:27: F401 [*] `threading.Thread` imported but unused | 11 | def fn(thread: Thread): 12 | from threading import Thread @@ -9,7 +9,7 @@ F401_17.py:12:27: F401 [*] `Thread` imported but unused 13 | 14 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the | - = help: Remove unused import: `Thread` + = help: Remove unused import: `threading.Thread` ℹ Safe fix 9 9 | @@ -20,7 +20,7 @@ F401_17.py:12:27: F401 [*] `Thread` imported but unused 14 13 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the 15 14 | # top level. -F401_17.py:20:27: F401 [*] `Thread` imported but unused +F401_17.py:20:27: F401 [*] `threading.Thread` imported but unused | 19 | def fn(thread: Thread): 20 | from threading import Thread @@ -28,7 +28,7 @@ F401_17.py:20:27: F401 [*] `Thread` imported but unused 21 | 22 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the | - = help: Remove unused import: `Thread` + = help: Remove unused import: `threading.Thread` ℹ Safe fix 17 17 | @@ -38,3 +38,5 @@ F401_17.py:20:27: F401 [*] `Thread` imported but unused 21 20 | 22 21 | # The `Thread` on the left-hand side should resolve to the `Thread` imported at the 23 22 | # top level. + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap index fa0fce1874e39e..afde9e550a87db 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_23.py.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_23.py:4:16: F401 [*] `RegexFlag` imported but unused +F401_23.py:4:16: F401 [*] `re.RegexFlag` imported but unused | 3 | from pathlib import Path 4 | from re import RegexFlag | ^^^^^^^^^ F401 5 | from typing import Annotated | - = help: Remove unused import: `RegexFlag` + = help: Remove unused import: `re.RegexFlag` ℹ Safe fix 1 1 | """Test: ensure that we treat strings in `typing.Annotation` as type definitions.""" diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap index c987c0dc971fad..e4317c5d195e7a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_5.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_5.py:2:17: F401 [*] `c` imported but unused +F401_5.py:2:17: F401 [*] `a.b.c` imported but unused | 1 | """Test: removal of multi-segment and aliases imports.""" 2 | from a.b import c @@ -9,7 +9,7 @@ F401_5.py:2:17: F401 [*] `c` imported but unused 3 | from d.e import f as g 4 | import h.i | - = help: Remove unused import: `c` + = help: Remove unused import: `a.b.c` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -18,7 +18,7 @@ F401_5.py:2:17: F401 [*] `c` imported but unused 4 3 | import h.i 5 4 | import j.k as l -F401_5.py:3:22: F401 [*] `g` imported but unused +F401_5.py:3:22: F401 [*] `d.e.f` imported but unused | 1 | """Test: removal of multi-segment and aliases imports.""" 2 | from a.b import c @@ -27,7 +27,7 @@ F401_5.py:3:22: F401 [*] `g` imported but unused 4 | import h.i 5 | import j.k as l | - = help: Remove unused import: `g` + = help: Remove unused import: `d.e.f` ℹ Safe fix 1 1 | """Test: removal of multi-segment and aliases imports.""" @@ -53,17 +53,19 @@ F401_5.py:4:8: F401 [*] `h.i` imported but unused 4 |-import h.i 5 4 | import j.k as l -F401_5.py:5:15: F401 [*] `l` imported but unused +F401_5.py:5:15: F401 [*] `j.k` imported but unused | 3 | from d.e import f as g 4 | import h.i 5 | import j.k as l | ^ F401 | - = help: Remove unused import: `l` + = help: Remove unused import: `j.k` ℹ Safe fix 2 2 | from a.b import c 3 3 | from d.e import f as g 4 4 | import h.i 5 |-import j.k as l + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap index 094f7d25d79c10..d0db5ee40d5df2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_6.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_6.py:7:25: F401 [*] `BackgroundTasks` imported but unused +F401_6.py:7:25: F401 [*] `.background.BackgroundTasks` imported but unused | 6 | # F401 `background.BackgroundTasks` imported but unused 7 | from .background import BackgroundTasks @@ -9,7 +9,7 @@ F401_6.py:7:25: F401 [*] `BackgroundTasks` imported but unused 8 | 9 | # F401 `datastructures.UploadFile` imported but unused | - = help: Remove unused import: `BackgroundTasks` + = help: Remove unused import: `.background.BackgroundTasks` ℹ Safe fix 4 4 | from .applications import FastAPI as FastAPI @@ -20,7 +20,7 @@ F401_6.py:7:25: F401 [*] `BackgroundTasks` imported but unused 9 8 | # F401 `datastructures.UploadFile` imported but unused 10 9 | from .datastructures import UploadFile as FileUpload -F401_6.py:10:43: F401 [*] `FileUpload` imported but unused +F401_6.py:10:43: F401 [*] `.datastructures.UploadFile` imported but unused | 9 | # F401 `datastructures.UploadFile` imported but unused 10 | from .datastructures import UploadFile as FileUpload @@ -28,7 +28,7 @@ F401_6.py:10:43: F401 [*] `FileUpload` imported but unused 11 | 12 | # OK | - = help: Remove unused import: `FileUpload` + = help: Remove unused import: `.datastructures.UploadFile` ℹ Safe fix 7 7 | from .background import BackgroundTasks @@ -58,16 +58,18 @@ F401_6.py:16:8: F401 [*] `background` imported but unused 18 17 | # F401 `datastructures` imported but unused 19 18 | import datastructures as structures -F401_6.py:19:26: F401 [*] `structures` imported but unused +F401_6.py:19:26: F401 [*] `datastructures` imported but unused | 18 | # F401 `datastructures` imported but unused 19 | import datastructures as structures | ^^^^^^^^^^ F401 | - = help: Remove unused import: `structures` + = help: Remove unused import: `datastructures` ℹ Safe fix 16 16 | import background 17 17 | 18 18 | # F401 `datastructures` imported but unused 19 |-import datastructures as structures + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap index 7c6792b3ef5a8e..cb4ec750285925 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_7.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_7.py:30:5: F401 [*] `Union` imported but unused +F401_7.py:30:5: F401 [*] `typing.Union` imported but unused | 28 | from typing import ( 29 | Mapping, # noqa: F401 @@ -9,7 +9,7 @@ F401_7.py:30:5: F401 [*] `Union` imported but unused | ^^^^^ F401 31 | ) | - = help: Remove unused import: `Union` + = help: Remove unused import: `typing.Union` ℹ Safe fix 27 27 | # This should ignore the first error. @@ -22,7 +22,7 @@ F401_7.py:30:5: F401 [*] `Union` imported but unused 33 32 | # This should ignore both errors. 34 33 | from typing import ( # noqa -F401_7.py:66:20: F401 [*] `Awaitable` imported but unused +F401_7.py:66:20: F401 [*] `typing.Awaitable` imported but unused | 65 | # This should mark F501 as unused. 66 | from typing import Awaitable, AwaitableGenerator # noqa: F501 @@ -36,7 +36,7 @@ F401_7.py:66:20: F401 [*] `Awaitable` imported but unused 65 65 | # This should mark F501 as unused. 66 |-from typing import Awaitable, AwaitableGenerator # noqa: F501 -F401_7.py:66:31: F401 [*] `AwaitableGenerator` imported but unused +F401_7.py:66:31: F401 [*] `typing.AwaitableGenerator` imported but unused | 65 | # This should mark F501 as unused. 66 | from typing import Awaitable, AwaitableGenerator # noqa: F501 @@ -49,3 +49,5 @@ F401_7.py:66:31: F401 [*] `AwaitableGenerator` imported but unused 64 64 | 65 65 | # This should mark F501 as unused. 66 |-from typing import Awaitable, AwaitableGenerator # noqa: F501 + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap index f761517e7a0a92..c7071cc24fbfa5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_F401_9.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F401_9.py:4:22: F401 [*] `baz` imported but unused +F401_9.py:4:22: F401 [*] `foo.baz` imported but unused | 3 | __all__ = ("bar",) 4 | from foo import bar, baz | ^^^ F401 | - = help: Remove unused import: `baz` + = help: Remove unused import: `foo.baz` ℹ Safe fix 1 1 | """Test: late-binding of `__all__`.""" @@ -15,3 +15,5 @@ F401_9.py:4:22: F401 [*] `baz` imported but unused 3 3 | __all__ = ("bar",) 4 |-from foo import bar, baz 4 |+from foo import bar + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap index b00296aff5532d..5a15b089872bd9 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__future_annotations.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -future_annotations.py:8:5: F401 [*] `Nut` imported but unused +future_annotations.py:8:5: F401 [*] `models.Nut` imported but unused | 6 | from models import ( 7 | Fruit, @@ -9,7 +9,7 @@ future_annotations.py:8:5: F401 [*] `Nut` imported but unused | ^^^ F401 9 | ) | - = help: Remove unused import: `Nut` + = help: Remove unused import: `models.Nut` ℹ Safe fix 5 5 | @@ -27,3 +27,5 @@ future_annotations.py:26:19: F821 Undefined name `Bar` | ^^^ F821 27 | return cls(x=0, y=0) | + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index 2d1cec65600691..46850ca6619e66 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -17,7 +17,7 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding 21 20 | 22 21 | # first-party -__init__.py:33:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 33 | from . import unused # F401: change to redundant alias | ^^^^^^ F401 @@ -34,7 +34,7 @@ __init__.py:33:15: F401 [*] `unused` imported but unused; consider removing, add 35 35 | 36 36 | from . import renamed as bees # F401: no fix -__init__.py:36:26: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:36:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 36 | from . import renamed as bees # F401: no fix | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 9f5ced4132a025..9d04194da7494b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -17,7 +17,7 @@ __init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding 21 20 | 22 21 | # first-party -__init__.py:36:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:36:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 36 | from . import unused # F401: add to __all__ | ^^^^^^ F401 @@ -31,7 +31,7 @@ __init__.py:36:15: F401 [*] `unused` imported but unused; consider removing, add 42 |-__all__ = ["argparse", "exported"] 42 |+__all__ = ["argparse", "exported", "unused"] -__init__.py:39:26: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:39:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 39 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap index b5fab3893b1405..6c393f0fbfce91 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_26__all_empty____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused # F401: add to __all__ | ^^^^^^ F401 @@ -15,7 +15,7 @@ __init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, addi 11 |-__all__ = [] 11 |+__all__ = ["unused"] -__init__.py:8:26: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:26: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 8 | from . import renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap index f04b42cd9a1f0e..c665795df94472 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_27__all_mistyped____init__.py.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused # F401: recommend add to all w/o fix | ^^^^^^ F401 | = help: Add unused import `unused` to __all__ -__init__.py:8:26: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 8 | from . import renamed as bees # F401: recommend add to all w/o fix | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap index 5a3f18a41dfc9a..a77b95641d6191 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_28__all_multiple____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^^^ F401 @@ -15,7 +15,7 @@ __init__.py:5:15: F401 [*] `unused` imported but unused; consider removing, addi 8 |-__all__ = []; 8 |+__all__ = ["bees", "unused"]; -__init__.py:5:34: F401 [*] `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:5:34: F401 [*] `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 5 | from . import unused, renamed as bees # F401: add to __all__ | ^^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap index 1b6d523467d5d2..0100397db388f6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_29__all_conditional____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:8:15: F401 `unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:15: F401 `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 6 | import sys 7 | @@ -12,7 +12,7 @@ __init__.py:8:15: F401 `unused` imported but unused; consider removing, adding t | = help: Remove unused import -__init__.py:8:44: F401 `bees` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:8:44: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias | 6 | import sys 7 | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap index 8c9f695740b46b..bd0aaf365a2cf6 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__ruf100_1.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF100_1.py:37:9: F401 [*] `Union` imported but unused +RUF100_1.py:37:9: F401 [*] `typing.Union` imported but unused | 35 | from typing import ( 36 | Mapping, # noqa: F401 @@ -9,7 +9,7 @@ RUF100_1.py:37:9: F401 [*] `Union` imported but unused | ^^^^^ F401 38 | ) | - = help: Remove unused import: `Union` + = help: Remove unused import: `typing.Union` ℹ Safe fix 34 34 | # This should ignore the first error. @@ -103,7 +103,7 @@ RUF100_1.py:72:27: RUF100 [*] Unused `noqa` directive (non-enabled: `F501`) 74 74 | ) 75 75 | -RUF100_1.py:89:24: F401 [*] `Awaitable` imported but unused +RUF100_1.py:89:24: F401 [*] `typing.Awaitable` imported but unused | 87 | def f(): 88 | # This should mark F501 as unused. @@ -119,7 +119,7 @@ RUF100_1.py:89:24: F401 [*] `Awaitable` imported but unused 89 |- from typing import Awaitable, AwaitableGenerator # noqa: F501 89 |+ pass # noqa: F501 -RUF100_1.py:89:35: F401 [*] `AwaitableGenerator` imported but unused +RUF100_1.py:89:35: F401 [*] `typing.AwaitableGenerator` imported but unused | 87 | def f(): 88 | # This should mark F501 as unused. @@ -150,3 +150,5 @@ RUF100_1.py:89:55: RUF100 [*] Unused `noqa` directive (non-enabled: `F501`) 88 88 | # This should mark F501 as unused. 89 |- from typing import Awaitable, AwaitableGenerator # noqa: F501 89 |+ from typing import Awaitable, AwaitableGenerator + +