From 72257328591825b7068f4f31a88f8f975ddc538b Mon Sep 17 00:00:00 2001 From: plredmond <51248199+plredmond@users.noreply.github.com> Date: Tue, 21 May 2024 09:23:45 -0700 Subject: [PATCH] F401 - update documentation and deprecate `ignore_init_module_imports` (#11436) ## Summary * Update documentation for F401 following recent PRs * #11168 * #11314 * Deprecate `ignore_init_module_imports` * Add a deprecation pragma to the option and a "warn user once" message when the option is used. * Restore the old behavior for stable (non-preview) mode: * When `ignore_init_module_imports` is set to `true` (default) there are no `__init_.py` fixes (but we get nice fix titles!). * When `ignore_init_module_imports` is set to `false` there are unsafe `__init__.py` fixes to remove unused imports. * When preview mode is enabled, it overrides `ignore_init_module_imports`. * Fixed a bug in fix titles where `import foo as bar` would recommend reexporting `bar as bar`. It now says to reexport `foo as foo`. (In this case we don't issue a fix, fwiw; it was just a fix title bug.) ## Test plan Added new fixture tests that reuse the existing fixtures for `__init__.py` files. Each of the three situations listed above has fixture tests. The F401 "stable" tests cover: > * When `ignore_init_module_imports` is set to `true` (default) there are no `__init_.py` fixes (but we get nice fix titles!). The F401 "deprecated option" tests cover: > * When `ignore_init_module_imports` is set to `false` there are unsafe `__init__.py` fixes to remove unused imports. These complement existing "preview" tests that show the new behavior which recommends fixes in `__init__.py` according to whether the import is 1st party and other circumstances (for more on that behavior see: #11314). --- .../integration_test__rule_f401.snap | 23 +++++- crates/ruff_linter/src/rules/pyflakes/mod.rs | 43 ++++++++++ .../src/rules/pyflakes/rules/unused_import.rs | 80 +++++++++++++------ ...eprecated_option_F401_24____init__.py.snap | 47 +++++++++++ ...on_F401_25__all_nonempty____init__.py.snap | 50 ++++++++++++ ...ption_F401_26__all_empty____init__.py.snap | 34 ++++++++ ...on_F401_27__all_mistyped____init__.py.snap | 34 ++++++++ ...on_F401_28__all_multiple____init__.py.snap | 34 ++++++++ ...F401_29__all_conditional____init__.py.snap | 44 ++++++++++ ...sts__F401_stable_F401_24____init__.py.snap | 23 ++++++ ...le_F401_25__all_nonempty____init__.py.snap | 23 ++++++ ...table_F401_26__all_empty____init__.py.snap | 16 ++++ ...le_F401_27__all_mistyped____init__.py.snap | 16 ++++ ...le_F401_28__all_multiple____init__.py.snap | 16 ++++ ...F401_29__all_conditional____init__.py.snap | 24 ++++++ ...s__preview__F401_F401_24____init__.py.snap | 2 +- crates/ruff_workspace/src/configuration.rs | 7 +- crates/ruff_workspace/src/options.rs | 4 + ruff.schema.json | 1 + 19 files changed, 491 insertions(+), 30 deletions(-) create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_26__all_empty____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_27__all_mistyped____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_28__all_multiple____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_29__all_conditional____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_26__all_empty____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_27__all_mistyped____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_28__all_multiple____init__.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_29__all_conditional____init__.py.snap diff --git a/crates/ruff/tests/snapshots/integration_test__rule_f401.snap b/crates/ruff/tests/snapshots/integration_test__rule_f401.snap index 2c69bfa92bcd0..09b9e23c32d05 100644 --- a/crates/ruff/tests/snapshots/integration_test__rule_f401.snap +++ b/crates/ruff/tests/snapshots/integration_test__rule_f401.snap @@ -34,12 +34,29 @@ marking it as unused, as in: from module import member as member ``` +Alternatively, you can use `__all__` to declare a symbol as part of the module's +interface, as in: + +```python +# __init__.py +import some_module + +__all__ = [ "some_module"] +``` + ## Fix safety -When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files. -These fixes are considered unsafe because they can change the public interface. +Fixes to remove unused imports are safe, except in `__init__.py` files. + +Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the +type of the unused import. Ruff will suggest a safe fix to export first-party imports with +either a redundant alias or, if already present in the file, an `__all__` entry. If multiple +`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix +to remove third-party and standard library imports -- the fix is unsafe because the module's +interface changes. ## Example + ```python import numpy as np # unused import @@ -49,12 +66,14 @@ def area(radius): ``` Use instead: + ```python def area(radius): return 3.14 * radius**2 ``` To check the availability of a module, use `importlib.util.find_spec`: + ```python from importlib.util import find_spec diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 4d31234106aba..107cb96fb3769 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -229,6 +229,49 @@ mod tests { Ok(()) } + #[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_conditional/__init__.py"))] + fn f401_stable(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "{}_stable_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("pyflakes").join(path).as_path(), + &LinterSettings::for_rule(rule_code), + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[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_conditional/__init__.py"))] + fn f401_deprecated_option(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "{}_deprecated_option_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("pyflakes").join(path).as_path(), + &LinterSettings { + ignore_init_module_imports: false, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test] fn f841_dummy_variable_rgx() -> Result<()> { let diagnostics = test_path( 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 6fe44cf88a578..3ce0f56c12ca3 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -24,6 +24,7 @@ enum UnusedImportContext { Init { first_party: bool, dunder_all_count: usize, + ignore_init_module_imports: bool, }, } @@ -46,12 +47,29 @@ enum UnusedImportContext { /// from module import member as member /// ``` /// +/// Alternatively, you can use `__all__` to declare a symbol as part of the module's +/// interface, as in: +/// +/// ```python +/// # __init__.py +/// import some_module +/// +/// __all__ = [ "some_module"] +/// ``` +/// /// ## Fix safety /// -/// When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files. -/// These fixes are considered unsafe because they can change the public interface. +/// Fixes to remove unused imports are safe, except in `__init__.py` files. +/// +/// Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the +/// type of the unused import. Ruff will suggest a safe fix to export first-party imports with +/// either a redundant alias or, if already present in the file, an `__all__` entry. If multiple +/// `__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix +/// to remove third-party and standard library imports -- the fix is unsafe because the module's +/// interface changes. /// /// ## Example +/// /// ```python /// import numpy as np # unused import /// @@ -61,12 +79,14 @@ enum UnusedImportContext { /// ``` /// /// Use instead: +/// /// ```python /// def area(radius): /// return 3.14 * radius**2 /// ``` /// /// To check the availability of a module, use `importlib.util.find_spec`: +/// /// ```python /// from importlib.util import find_spec /// @@ -87,6 +107,8 @@ enum UnusedImportContext { pub struct UnusedImport { /// Qualified name of the import name: String, + /// Unqualified name of the import + module: String, /// Name of the import binding binding: String, context: Option, @@ -117,6 +139,7 @@ impl Violation for UnusedImport { fn fix_title(&self) -> Option { let UnusedImport { name, + module, binding, multiple, .. @@ -125,14 +148,14 @@ impl Violation for UnusedImport { Some(UnusedImportContext::Init { first_party: true, dunder_all_count: 1, + ignore_init_module_imports: true, }) => 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: `{binding} as {binding}`" - )), + ignore_init_module_imports: true, + }) => Some(format!("Use an explicit re-export: `{module} as {module}`")), _ => Some(if *multiple { "Remove unused import".to_string() @@ -244,7 +267,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(); + let fix_init = !checker.settings.ignore_init_module_imports; + let preview_mode = checker.settings.preview.is_enabled(); 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 @@ -275,6 +299,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut checker, ), dunder_all_count: dunder_all_exprs.len(), + ignore_init_module_imports: !fix_init, }) } else { None @@ -288,30 +313,31 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut first_party: true, .. }) - ) + ) && preview_mode }); // generate fixes that are shared across bindings in the statement - let (fix_remove, fix_reexport) = if (!in_init || fix_init) && !in_except_handler { - ( - fix_by_removing_imports( - checker, - import_statement, - to_remove.iter().map(|(binding, _)| binding), - in_init, - ) - .ok(), - fix_by_reexporting( - checker, - import_statement, - &to_reexport.iter().map(|(b, _)| b).collect::>(), - &dunder_all_exprs, + let (fix_remove, fix_reexport) = + if (!in_init || fix_init || preview_mode) && !in_except_handler { + ( + fix_by_removing_imports( + checker, + import_statement, + to_remove.iter().map(|(binding, _)| binding), + in_init, + ) + .ok(), + fix_by_reexporting( + checker, + import_statement, + &to_reexport.iter().map(|(b, _)| b).collect::>(), + &dunder_all_exprs, + ) + .ok(), ) - .ok(), - ) - } else { - (None, None) - }; + } else { + (None, None) + }; for ((binding, context), fix) in iter::Iterator::chain( iter::zip(to_remove, iter::repeat(fix_remove)), @@ -320,6 +346,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(), + module: binding.import.member_name().to_string(), binding: binding.name.to_string(), context, multiple, @@ -344,6 +371,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(), + module: binding.import.member_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_deprecated_option_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap new file mode 100644 index 0000000000000..3f44409c80a34 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap @@ -0,0 +1,47 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +19 | import sys # F401: remove unused + | ^^^ F401 + | + = help: Remove unused import: `sys` + +ℹ Unsafe fix +16 16 | import argparse as argparse # Ok: is redundant alias +17 17 | +18 18 | +19 |-import sys # F401: remove unused +20 19 | +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 + | +33 | from . import unused # F401: change to redundant alias + | ^^^^^^ F401 + | + = help: Remove unused import: `.unused` + +ℹ Unsafe fix +30 30 | from . import aliased as aliased # Ok: is redundant alias +31 31 | +32 32 | +33 |-from . import unused # F401: change to redundant alias +34 33 | +35 34 | +36 35 | 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 + | +36 | from . import renamed as bees # F401: no fix + | ^^^^ F401 + | + = help: Remove unused import: `.renamed` + +ℹ Unsafe fix +33 33 | from . import unused # F401: change to redundant alias +34 34 | +35 35 | +36 |-from . import renamed as bees # F401: no fix diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap new file mode 100644 index 0000000000000..f75cb2f1dc9e6 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap @@ -0,0 +1,50 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +19 | import sys # F401: remove unused + | ^^^ F401 + | + = help: Remove unused import: `sys` + +ℹ Unsafe fix +16 16 | import argparse # Ok: is exported in __all__ +17 17 | +18 18 | +19 |-import sys # F401: remove unused +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: Remove unused import: `.unused` + +ℹ Unsafe fix +33 33 | from . import exported # Ok: is exported in __all__ +34 34 | +35 35 | +36 |-from . import unused # F401: add to __all__ +37 36 | +38 37 | +39 38 | from . import renamed as bees # F401: add to __all__ + +__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: Remove unused import: `.renamed` + +ℹ Unsafe fix +36 36 | from . import unused # F401: add to __all__ +37 37 | +38 38 | +39 |-from . import renamed as bees # F401: add to __all__ +40 39 | +41 40 | +42 41 | __all__ = ["argparse", "exported"] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_26__all_empty____init__.py.snap new file mode 100644 index 0000000000000..0fa48cf5f179e --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_26__all_empty____init__.py.snap @@ -0,0 +1,34 @@ +--- +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: Remove unused import: `.unused` + +ℹ Unsafe fix +2 2 | """ +3 3 | +4 4 | +5 |-from . import unused # F401: add to __all__ +6 5 | +7 6 | +8 7 | from . import renamed as bees # F401: add 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: add to __all__ + | ^^^^ F401 + | + = help: Remove unused import: `.renamed` + +ℹ Unsafe fix +5 5 | from . import unused # F401: add to __all__ +6 6 | +7 7 | +8 |-from . import renamed as bees # F401: add to __all__ +9 8 | +10 9 | +11 10 | __all__ = [] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_27__all_mistyped____init__.py.snap new file mode 100644 index 0000000000000..a4f1d557bdef4 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_27__all_mistyped____init__.py.snap @@ -0,0 +1,34 @@ +--- +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 + | ^^^^^^ F401 + | + = help: Remove unused import: `.unused` + +ℹ Unsafe fix +2 2 | """ +3 3 | +4 4 | +5 |-from . import unused # F401: recommend add to all w/o fix +6 5 | +7 6 | +8 7 | from . import renamed as bees # F401: recommend add to all w/o fix + +__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: Remove unused import: `.renamed` + +ℹ Unsafe fix +5 5 | from . import unused # F401: recommend add to all w/o fix +6 6 | +7 7 | +8 |-from . import renamed as bees # F401: recommend add to all w/o fix +9 8 | +10 9 | +11 10 | __all__ = None diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_28__all_multiple____init__.py.snap new file mode 100644 index 0000000000000..daeee1984e4aa --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_28__all_multiple____init__.py.snap @@ -0,0 +1,34 @@ +--- +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: Remove unused import + +ℹ Unsafe fix +2 2 | """ +3 3 | +4 4 | +5 |-from . import unused, renamed as bees # F401: add to __all__ +6 5 | +7 6 | +8 7 | __all__ = []; + +__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: Remove unused import + +ℹ Unsafe fix +2 2 | """ +3 3 | +4 4 | +5 |-from . import unused, renamed as bees # F401: add to __all__ +6 5 | +7 6 | +8 7 | __all__ = []; diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_29__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_29__all_conditional____init__.py.snap new file mode 100644 index 0000000000000..392a58cf7aa16 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_29__all_conditional____init__.py.snap @@ -0,0 +1,44 @@ +--- +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 + | + 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 + +ℹ Unsafe fix +5 5 | +6 6 | import sys +7 7 | +8 |-from . import unused, exported, renamed as bees + 8 |+from . import exported +9 9 | +10 10 | if sys.version_info > (3, 9): +11 11 | from . import also_exported + +__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 + +ℹ Unsafe fix +5 5 | +6 6 | import sys +7 7 | +8 |-from . import unused, exported, renamed as bees + 8 |+from . import exported +9 9 | +10 10 | if sys.version_info > (3, 9): +11 11 | from . import also_exported diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap new file mode 100644 index 0000000000000..e1e8ca664f400 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:19:8: F401 `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +19 | import sys # F401: remove unused + | ^^^ F401 + | + = help: Remove unused import: `sys` + +__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 + | + = help: Use an explicit re-export: `unused as unused` + +__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 + | + = help: Use an explicit re-export: `renamed as renamed` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap new file mode 100644 index 0000000000000..faeb9037ef744 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:19:8: F401 `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +19 | import sys # F401: remove unused + | ^^^ F401 + | + = help: Remove unused import: `sys` + +__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 `unused` to __all__ + +__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 `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_26__all_empty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_26__all_empty____init__.py.snap new file mode 100644 index 0000000000000..d01839e564728 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_26__all_empty____init__.py.snap @@ -0,0 +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 + | +5 | from . import unused # F401: add to __all__ + | ^^^^^^ 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: add to __all__ + | ^^^^ F401 + | + = help: Add unused import `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_27__all_mistyped____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_27__all_mistyped____init__.py.snap new file mode 100644 index 0000000000000..c665795df9447 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_27__all_mistyped____init__.py.snap @@ -0,0 +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 + | +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 + | ^^^^ F401 + | + = help: Add unused import `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_28__all_multiple____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_28__all_multiple____init__.py.snap new file mode 100644 index 0000000000000..219539886e329 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_28__all_multiple____init__.py.snap @@ -0,0 +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 + | +5 | from . import unused, renamed as bees # F401: add to __all__ + | ^^^^^^ F401 + | + = help: Add unused import `unused` to __all__ + +__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 `bees` to __all__ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_29__all_conditional____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_29__all_conditional____init__.py.snap new file mode 100644 index 0000000000000..0100397db388f --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_29__all_conditional____init__.py.snap @@ -0,0 +1,24 @@ +--- +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 + | + 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 + +__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 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 46850ca6619e6..f7db8b02e72f1 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 an explicit re-export: `bees as bees` + = help: Use an explicit re-export: `renamed as renamed` diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 240ab7a12fd8f..7cb2a5c5083fc 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -660,7 +660,12 @@ impl LintConfiguration { .collect(); #[allow(deprecated)] - let ignore_init_module_imports = options.common.ignore_init_module_imports; + let ignore_init_module_imports = { + if options.common.ignore_init_module_imports.is_some() { + warn_user_once!("The `ignore-init-module-imports` option is deprecated and will be removed in a future release. Ruff's handling of imports in `__init__.py` files has been improved (in preview) and unused imports will always be flagged."); + } + options.common.ignore_init_module_imports + }; Ok(LintConfiguration { exclude: options.exclude.map(|paths| { diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index fbd76b7e84e8f..71cb0ee84ee17 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -703,6 +703,10 @@ pub struct LintCommonOptions { ignore-init-module-imports = false "# )] + #[deprecated( + since = "0.4.4", + note = "`ignore-init-module-imports` will be removed in a future version because F401 now recommends appropriate fixes for unused imports in `__init__.py` (currently in preview mode). See documentation for more information and please update your configuration." + )] pub ignore_init_module_imports: Option, /// A list of objects that should be treated equivalently to a diff --git a/ruff.schema.json b/ruff.schema.json index ec514be5fc8a4..9827c7d241014 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2119,6 +2119,7 @@ }, "ignore-init-module-imports": { "description": "Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be flagged, but with a dedicated message suggesting that the import is either added to the module's `__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).\n\nThis option is enabled by default, but you can opt-in to removal of imports via an unsafe fix.", + "deprecated": true, "type": [ "boolean", "null"