diff --git a/README.md b/README.md index 475daf9e219231..86c614ca04c3fd 100644 --- a/README.md +++ b/README.md @@ -1270,25 +1270,6 @@ Summary ### Options -#### [`dummy_variable_rgx`](#dummy_variable_rgx) - -A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating -(e.g.) unused-variable checks. - -**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`) - -**Type**: `Regex` - -**Example usage**: - -```toml -[tool.ruff] -# Only ignore variables named "_". -dummy_variable_rgx = "^_$" -``` - ---- - #### [`exclude`](#exclude) A list of file patterns to exclude from linting. @@ -1302,7 +1283,7 @@ Exclusions are based on globs, and can be either: (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`). -Note that you'll typically want to use [`extend_exclude`](#extend-exclude) to modify the excluded +Note that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded paths. **Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]` @@ -1509,6 +1490,44 @@ line-length = 120 --- +#### [`dummy-variable-rgx`](#dummy-variable-rgx) + +A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating +(e.g.) unused-variable checks. + +**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`) + +**Type**: `Regex` + +**Example usage**: + +```toml +[tool.ruff] +# Only ignore variables named "_". +dummy-variable-rgx = "^_$" +``` + +--- + +#### [`ignore-init-module-imports`](#ignore-init-module-imports) + +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`). + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff] +ignore-init-module-imports = true +``` + +--- + #### [`format`](#format) The style in which violation messages should be formatted: `"text"` (default), `"grouped"` diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index a87db4baa21b3d..04097ce2b92a6f 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -253,6 +253,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![ @@ -295,6 +296,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: Some(100), per_file_ignores: None, select: Some(vec![ @@ -337,6 +339,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: Some(100), per_file_ignores: None, select: Some(vec![ @@ -379,6 +382,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![ @@ -421,6 +425,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![ @@ -471,6 +476,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![ @@ -548,6 +554,7 @@ mod tests { fixable: None, format: None, ignore: Some(vec![]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![ diff --git a/src/check_ast.rs b/src/check_ast.rs index 6d90e19ce9edb3..82b94ef44fd2c2 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -3095,8 +3095,9 @@ impl<'a> Checker<'a> { let child = self.parents[defined_by]; let parent = defined_in.map(|defined_in| self.parents[defined_in]); - let in_init_py = self.path.ends_with("__init__.py"); - let fix = if !in_init_py && self.patch(&CheckCode::F401) { + let ignore_init = self.settings.ignore_init_module_imports + && self.path.ends_with("__init__.py"); + let fix = if !ignore_init && self.patch(&CheckCode::F401) { let deleted: Vec<&Stmt> = self .deletions .iter() @@ -3126,7 +3127,7 @@ impl<'a> Checker<'a> { for (full_name, range) in unused_imports { let mut check = Check::new( - CheckKind::UnusedImport(full_name.clone(), in_init_py), + CheckKind::UnusedImport(full_name.clone(), ignore_init), *range, ); if let Some(fix) = fix.as_ref() { diff --git a/src/checks.rs b/src/checks.rs index 3a2fda43602f78..fcf0a6905e1209 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -1803,9 +1803,12 @@ impl CheckKind { CheckKind::UndefinedName(name) => { format!("Undefined name `{name}`") } - CheckKind::UnusedImport(name, in_init_py) => { - if *in_init_py { - format!("`{name}` imported but unused and missing from `__all__`") + CheckKind::UnusedImport(name, ignore_init) => { + if *ignore_init { + format!( + "`{name}` imported but unused; consider adding to `__all__` or using a \ + redundant alias" + ) } else { format!("`{name}` imported but unused") } diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index b07966087dabef..2b2b7f5eba410d 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -29,6 +29,7 @@ pub struct Configuration { pub fixable: Vec, pub format: SerializationFormat, pub ignore: Vec, + pub ignore_init_module_imports: bool, pub line_length: usize, pub per_file_ignores: Vec, pub select: Vec, @@ -125,6 +126,7 @@ impl Configuration { unfixable: options.unfixable.unwrap_or_default(), format: options.format.unwrap_or_default(), ignore: options.ignore.unwrap_or_default(), + ignore_init_module_imports: options.ignore_init_module_imports.unwrap_or_default(), line_length: options.line_length.unwrap_or(88), per_file_ignores: options .per_file_ignores diff --git a/src/settings/mod.rs b/src/settings/mod.rs index b8bc4287a26f42..d99f0b2644a715 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -35,6 +35,7 @@ pub struct Settings { pub external: BTreeSet, pub fixable: FxHashSet, pub format: SerializationFormat, + pub ignore_init_module_imports: bool, pub line_length: usize, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet)>, pub show_source: bool, @@ -79,6 +80,7 @@ impl Settings { flake8_bugbear: config.flake8_bugbear, flake8_quotes: config.flake8_quotes, flake8_tidy_imports: config.flake8_tidy_imports, + ignore_init_module_imports: config.ignore_init_module_imports, isort: config.isort, mccabe: config.mccabe, line_length: config.line_length, @@ -100,6 +102,7 @@ impl Settings { external: BTreeSet::default(), fixable: FxHashSet::from_iter([check_code]), format: SerializationFormat::Text, + ignore_init_module_imports: false, line_length: 88, per_file_ignores: vec![], show_source: false, @@ -125,6 +128,7 @@ impl Settings { external: BTreeSet::default(), fixable: FxHashSet::from_iter(check_codes), format: SerializationFormat::Text, + ignore_init_module_imports: false, line_length: 88, per_file_ignores: vec![], show_source: false, diff --git a/src/settings/options.rs b/src/settings/options.rs index fb016227c513b6..d859fce458b5d0 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -23,6 +23,7 @@ pub struct Options { pub fixable: Option>, pub format: Option, pub ignore: Option>, + pub ignore_init_module_imports: Option, pub line_length: Option, pub select: Option>, pub show_source: Option, diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index 6f1ae08385b891..7d00ce278c4f77 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -142,6 +142,7 @@ mod tests { fix: None, fixable: None, ignore: None, + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: None, @@ -182,6 +183,7 @@ line-length = 79 fix: None, fixable: None, ignore: None, + ignore_init_module_imports: None, line_length: Some(79), per_file_ignores: None, select: None, @@ -221,6 +223,7 @@ exclude = ["foo.py"] extend_select: None, external: None, ignore: None, + ignore_init_module_imports: None, extend_ignore: None, fixable: None, format: None, @@ -262,6 +265,7 @@ select = ["E501"] fix: None, fixable: None, ignore: None, + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: Some(vec![CheckCodePrefix::E501]), @@ -303,6 +307,7 @@ ignore = ["E501"] fix: None, fixable: None, ignore: Some(vec![CheckCodePrefix::E501]), + ignore_init_module_imports: None, line_length: None, per_file_ignores: None, select: None, @@ -381,6 +386,7 @@ other-attribute = 1 extend_select: None, external: Some(vec!["V101".to_string()]), ignore: None, + ignore_init_module_imports: None, extend_ignore: None, fixable: None, format: None,