mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 01:51:30 +00:00
[pyflakes
] Fix preview-mode bugs in F401
when attempting to autofix unused first-party submodule imports in an __init__.py
file (#12569)
This commit is contained in:
parent
83b1c48a93
commit
a3900d2b0b
13 changed files with 220 additions and 86 deletions
|
@ -1,7 +1,5 @@
|
||||||
//! Interface for generating fix edits from higher-level actions (e.g., "remove an argument").
|
//! Interface for generating fix edits from higher-level actions (e.g., "remove an argument").
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
|
@ -126,7 +124,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`.
|
/// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`.
|
||||||
pub(crate) fn make_redundant_alias<'a>(
|
pub(crate) fn make_redundant_alias<'a>(
|
||||||
member_names: impl Iterator<Item = Cow<'a, str>>,
|
member_names: impl Iterator<Item = &'a str>,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
) -> Vec<Edit> {
|
) -> Vec<Edit> {
|
||||||
let aliases = match stmt {
|
let aliases = match stmt {
|
||||||
|
@ -527,7 +525,6 @@ fn all_lines_fit(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::borrow::Cow;
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||||
|
@ -619,7 +616,7 @@ x = 1 \
|
||||||
let contents = "import x, y as y, z as bees";
|
let contents = "import x, y as y, z as bees";
|
||||||
let stmt = parse_first_stmt(contents)?;
|
let stmt = parse_first_stmt(contents)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
make_redundant_alias(["x"].into_iter().map(Cow::from), &stmt),
|
make_redundant_alias(["x"].into_iter(), &stmt),
|
||||||
vec![Edit::range_replacement(
|
vec![Edit::range_replacement(
|
||||||
String::from("x as x"),
|
String::from("x as x"),
|
||||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||||
|
@ -627,7 +624,7 @@ x = 1 \
|
||||||
"make just one item redundant"
|
"make just one item redundant"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
make_redundant_alias(vec!["x", "y"].into_iter().map(Cow::from), &stmt),
|
make_redundant_alias(vec!["x", "y"].into_iter(), &stmt),
|
||||||
vec![Edit::range_replacement(
|
vec![Edit::range_replacement(
|
||||||
String::from("x as x"),
|
String::from("x as x"),
|
||||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||||
|
@ -635,7 +632,7 @@ x = 1 \
|
||||||
"the second item is already a redundant alias"
|
"the second item is already a redundant alias"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
make_redundant_alias(vec!["x", "z"].into_iter().map(Cow::from), &stmt),
|
make_redundant_alias(vec!["x", "z"].into_iter(), &stmt),
|
||||||
vec![Edit::range_replacement(
|
vec![Edit::range_replacement(
|
||||||
String::from("x as x"),
|
String::from("x as x"),
|
||||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||||
|
|
|
@ -11,6 +11,7 @@ mod tests {
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
|
@ -24,11 +25,12 @@ mod tests {
|
||||||
|
|
||||||
use crate::linter::check_path;
|
use crate::linter::check_path;
|
||||||
use crate::registry::{AsRule, Linter, Rule};
|
use crate::registry::{AsRule, Linter, Rule};
|
||||||
|
use crate::rules::isort;
|
||||||
use crate::rules::pyflakes;
|
use crate::rules::pyflakes;
|
||||||
use crate::settings::types::PreviewMode;
|
use crate::settings::types::PreviewMode;
|
||||||
use crate::settings::{flags, LinterSettings};
|
use crate::settings::{flags, LinterSettings};
|
||||||
use crate::source_kind::SourceKind;
|
use crate::source_kind::SourceKind;
|
||||||
use crate::test::{test_path, test_snippet};
|
use crate::test::{test_contents, test_path, test_snippet};
|
||||||
use crate::{assert_messages, directives};
|
use crate::{assert_messages, directives};
|
||||||
|
|
||||||
#[test_case(Rule::UnusedImport, Path::new("F401_0.py"))]
|
#[test_case(Rule::UnusedImport, Path::new("F401_0.py"))]
|
||||||
|
@ -232,6 +234,44 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(
|
||||||
|
r"import submodule.a",
|
||||||
|
"f401_preview_first_party_submodule_no_dunder_all"
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
r"
|
||||||
|
import submodule.a
|
||||||
|
__all__ = ['FOO']
|
||||||
|
FOO = 42",
|
||||||
|
"f401_preview_first_party_submodule_dunder_all"
|
||||||
|
)]
|
||||||
|
fn f401_preview_first_party_submodule(contents: &str, snapshot: &str) {
|
||||||
|
let diagnostics = test_contents(
|
||||||
|
&SourceKind::Python(dedent(contents).to_string()),
|
||||||
|
Path::new("f401_preview_first_party_submodule/__init__.py"),
|
||||||
|
&LinterSettings {
|
||||||
|
preview: PreviewMode::Enabled,
|
||||||
|
isort: isort::settings::Settings {
|
||||||
|
// This case specifically tests the scenario where
|
||||||
|
// the unused import is a first-party submodule import;
|
||||||
|
// use the isort settings to ensure that the `submodule.a` import
|
||||||
|
// is recognised as first-party in the test:
|
||||||
|
known_modules: isort::categorize::KnownModules::new(
|
||||||
|
vec!["submodule".parse().unwrap()],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
FxHashMap::default(),
|
||||||
|
),
|
||||||
|
..isort::settings::Settings::default()
|
||||||
|
},
|
||||||
|
..LinterSettings::for_rule(Rule::UnusedImport)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.0;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(Rule::UnusedImport, Path::new("F401_24/__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_25__all_nonempty/__init__.py"))]
|
||||||
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]
|
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::{Stmt, StmtImportFrom};
|
use ruff_python_ast::{Stmt, StmtImportFrom};
|
||||||
use ruff_python_semantic::{
|
use ruff_python_semantic::{
|
||||||
AnyImport, BindingKind, Exceptions, Imported, NodeId, Scope, SemanticModel,
|
AnyImport, BindingKind, Exceptions, Imported, NodeId, Scope, SemanticModel, SubmoduleImport,
|
||||||
};
|
};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
@ -18,16 +18,6 @@ use crate::fix;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rules::{isort, isort::ImportSection, isort::ImportType};
|
use crate::rules::{isort, isort::ImportSection, isort::ImportType};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
enum UnusedImportContext {
|
|
||||||
ExceptHandler,
|
|
||||||
Init {
|
|
||||||
first_party: bool,
|
|
||||||
dunder_all_count: usize,
|
|
||||||
ignore_init_module_imports: bool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for unused imports.
|
/// Checks for unused imports.
|
||||||
///
|
///
|
||||||
|
@ -111,8 +101,9 @@ pub struct UnusedImport {
|
||||||
module: String,
|
module: String,
|
||||||
/// Name of the import binding
|
/// Name of the import binding
|
||||||
binding: String,
|
binding: String,
|
||||||
context: Option<UnusedImportContext>,
|
context: UnusedImportContext,
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
|
ignore_init_module_imports: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for UnusedImport {
|
impl Violation for UnusedImport {
|
||||||
|
@ -122,17 +113,17 @@ impl Violation for UnusedImport {
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let UnusedImport { name, context, .. } = self;
|
let UnusedImport { name, context, .. } = self;
|
||||||
match context {
|
match context {
|
||||||
Some(UnusedImportContext::ExceptHandler) => {
|
UnusedImportContext::ExceptHandler => {
|
||||||
format!(
|
format!(
|
||||||
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Some(UnusedImportContext::Init { .. }) => {
|
UnusedImportContext::DunderInitFirstParty { .. } => {
|
||||||
format!(
|
format!(
|
||||||
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias"
|
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => format!("`{name}` imported but unused"),
|
UnusedImportContext::Other => format!("`{name}` imported but unused"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,30 +133,91 @@ impl Violation for UnusedImport {
|
||||||
module,
|
module,
|
||||||
binding,
|
binding,
|
||||||
multiple,
|
multiple,
|
||||||
..
|
ignore_init_module_imports,
|
||||||
|
context,
|
||||||
} = self;
|
} = self;
|
||||||
match self.context {
|
if *ignore_init_module_imports {
|
||||||
Some(UnusedImportContext::Init {
|
match context {
|
||||||
first_party: true,
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
dunder_all_count: 1,
|
dunder_all_count: DunderAllCount::Zero,
|
||||||
ignore_init_module_imports: true,
|
submodule_import: false,
|
||||||
}) => Some(format!("Add unused import `{binding}` to __all__")),
|
} => return Some(format!("Use an explicit re-export: `{module} as {module}`")),
|
||||||
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
Some(UnusedImportContext::Init {
|
dunder_all_count: DunderAllCount::Zero,
|
||||||
first_party: true,
|
submodule_import: true,
|
||||||
dunder_all_count: 0,
|
} => {
|
||||||
ignore_init_module_imports: true,
|
return Some(format!(
|
||||||
}) => Some(format!("Use an explicit re-export: `{module} as {module}`")),
|
"Use an explicit re-export: `import {parent} as {parent}; import {binding}`",
|
||||||
|
parent = binding
|
||||||
_ => Some(if *multiple {
|
.split('.')
|
||||||
|
.next()
|
||||||
|
.expect("Expected all submodule imports to contain a '.'")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
|
dunder_all_count: DunderAllCount::One,
|
||||||
|
submodule_import: false,
|
||||||
|
} => return Some(format!("Add unused import `{binding}` to __all__")),
|
||||||
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
|
dunder_all_count: DunderAllCount::One,
|
||||||
|
submodule_import: true,
|
||||||
|
} => {
|
||||||
|
return Some(format!(
|
||||||
|
"Add `{}` to __all__",
|
||||||
|
binding
|
||||||
|
.split('.')
|
||||||
|
.next()
|
||||||
|
.expect("Expected all submodule imports to contain a '.'")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
|
dunder_all_count: DunderAllCount::Many,
|
||||||
|
submodule_import: _,
|
||||||
|
}
|
||||||
|
| UnusedImportContext::ExceptHandler
|
||||||
|
| UnusedImportContext::Other => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(if *multiple {
|
||||||
"Remove unused import".to_string()
|
"Remove unused import".to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("Remove unused import: `{name}`")
|
format!("Remove unused import: `{name}`")
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumeration providing three possible answers to the question:
|
||||||
|
/// "How many `__all__` definitions are there in this file?"
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum DunderAllCount {
|
||||||
|
Zero,
|
||||||
|
One,
|
||||||
|
Many,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for DunderAllCount {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => Self::Zero,
|
||||||
|
1 => Self::One,
|
||||||
|
_ => Self::Many,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, is_macro::Is)]
|
||||||
|
enum UnusedImportContext {
|
||||||
|
/// The unused import occurs inside an except handler
|
||||||
|
ExceptHandler,
|
||||||
|
/// The unused import is a first-party import in an `__init__.py` file
|
||||||
|
DunderInitFirstParty {
|
||||||
|
dunder_all_count: DunderAllCount,
|
||||||
|
submodule_import: bool,
|
||||||
|
},
|
||||||
|
/// The unused import is something else
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool {
|
fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool {
|
||||||
let category = isort::categorize(
|
let category = isort::categorize(
|
||||||
qualified_name,
|
qualified_name,
|
||||||
|
@ -304,31 +356,20 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|binding| {
|
.map(|binding| {
|
||||||
let context = if in_except_handler {
|
let context = if in_except_handler {
|
||||||
Some(UnusedImportContext::ExceptHandler)
|
UnusedImportContext::ExceptHandler
|
||||||
} else if in_init {
|
} else if in_init
|
||||||
Some(UnusedImportContext::Init {
|
&& is_first_party(&binding.import.qualified_name().to_string(), level, checker)
|
||||||
first_party: is_first_party(
|
{
|
||||||
&binding.import.qualified_name().to_string(),
|
UnusedImportContext::DunderInitFirstParty {
|
||||||
level,
|
dunder_all_count: DunderAllCount::from(dunder_all_exprs.len()),
|
||||||
checker,
|
submodule_import: binding.import.is_submodule_import(),
|
||||||
),
|
}
|
||||||
dunder_all_count: dunder_all_exprs.len(),
|
|
||||||
ignore_init_module_imports: !fix_init,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
UnusedImportContext::Other
|
||||||
};
|
};
|
||||||
(binding, context)
|
(binding, context)
|
||||||
})
|
})
|
||||||
.partition(|(_, context)| {
|
.partition(|(_, context)| context.is_dunder_init_first_party() && preview_mode);
|
||||||
matches!(
|
|
||||||
context,
|
|
||||||
Some(UnusedImportContext::Init {
|
|
||||||
first_party: true,
|
|
||||||
..
|
|
||||||
})
|
|
||||||
) && preview_mode
|
|
||||||
});
|
|
||||||
|
|
||||||
// generate fixes that are shared across bindings in the statement
|
// generate fixes that are shared across bindings in the statement
|
||||||
let (fix_remove, fix_reexport) =
|
let (fix_remove, fix_reexport) =
|
||||||
|
@ -344,7 +385,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
|
||||||
fix_by_reexporting(
|
fix_by_reexporting(
|
||||||
checker,
|
checker,
|
||||||
import_statement,
|
import_statement,
|
||||||
to_reexport.iter().map(|(b, _)| b).collect::<Vec<_>>(),
|
to_reexport.iter().map(|(b, _)| b),
|
||||||
&dunder_all_exprs,
|
&dunder_all_exprs,
|
||||||
)
|
)
|
||||||
.ok(),
|
.ok(),
|
||||||
|
@ -364,6 +405,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
|
||||||
binding: binding.name.to_string(),
|
binding: binding.name.to_string(),
|
||||||
context,
|
context,
|
||||||
multiple,
|
multiple,
|
||||||
|
ignore_init_module_imports: !fix_init,
|
||||||
},
|
},
|
||||||
binding.range,
|
binding.range,
|
||||||
);
|
);
|
||||||
|
@ -387,8 +429,9 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
|
||||||
name: binding.import.qualified_name().to_string(),
|
name: binding.import.qualified_name().to_string(),
|
||||||
module: binding.import.member_name().to_string(),
|
module: binding.import.member_name().to_string(),
|
||||||
binding: binding.name.to_string(),
|
binding: binding.name.to_string(),
|
||||||
context: None,
|
context: UnusedImportContext::Other,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
ignore_init_module_imports: !fix_init,
|
||||||
},
|
},
|
||||||
binding.range,
|
binding.range,
|
||||||
);
|
);
|
||||||
|
@ -412,6 +455,31 @@ struct ImportBinding<'a> {
|
||||||
parent_range: Option<TextRange>,
|
parent_range: Option<TextRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportBinding<'a> {
|
||||||
|
/// The symbol that is stored in the outer scope as a result of this import.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// - `import foo` => `foo` symbol stored in outer scope
|
||||||
|
/// - `import foo as bar` => `bar` symbol stored in outer scope
|
||||||
|
/// - `from foo import bar` => `bar` symbol stored in outer scope
|
||||||
|
/// - `from foo import bar as baz` => `baz` symbol stored in outer scope
|
||||||
|
/// - `import foo.bar` => `foo` symbol stored in outer scope
|
||||||
|
fn symbol_stored_in_outer_scope(&self) -> &str {
|
||||||
|
match &self.import {
|
||||||
|
AnyImport::FromImport(_) => self.name,
|
||||||
|
AnyImport::Import(_) => self.name,
|
||||||
|
AnyImport::SubmoduleImport(SubmoduleImport { qualified_name }) => {
|
||||||
|
qualified_name.segments().first().unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Expected an import binding to have a non-empty qualified name;
|
||||||
|
got {qualified_name}"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Ranged for ImportBinding<'_> {
|
impl Ranged for ImportBinding<'_> {
|
||||||
fn range(&self) -> TextRange {
|
fn range(&self) -> TextRange {
|
||||||
self.range
|
self.range
|
||||||
|
@ -461,29 +529,31 @@ fn fix_by_removing_imports<'a>(
|
||||||
|
|
||||||
/// Generate a [`Fix`] to make bindings in a statement explicit, either by adding them to `__all__`
|
/// 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`.
|
/// or changing them from `import a` to `import a as a`.
|
||||||
fn fix_by_reexporting(
|
fn fix_by_reexporting<'a>(
|
||||||
checker: &Checker,
|
checker: &Checker,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
mut imports: Vec<&ImportBinding>,
|
imports: impl IntoIterator<Item = &'a ImportBinding<'a>>,
|
||||||
dunder_all_exprs: &[&ast::Expr],
|
dunder_all_exprs: &[&ast::Expr],
|
||||||
) -> Result<Fix> {
|
) -> Result<Fix> {
|
||||||
let statement = checker.semantic().statement(node_id);
|
let statement = checker.semantic().statement(node_id);
|
||||||
|
|
||||||
|
let imports = {
|
||||||
|
let mut imports: Vec<&str> = imports
|
||||||
|
.into_iter()
|
||||||
|
.map(ImportBinding::symbol_stored_in_outer_scope)
|
||||||
|
.collect();
|
||||||
if imports.is_empty() {
|
if imports.is_empty() {
|
||||||
bail!("Expected import bindings");
|
bail!("Expected import bindings");
|
||||||
}
|
}
|
||||||
|
imports.sort_unstable();
|
||||||
imports.sort_by_key(|b| b.name);
|
imports
|
||||||
|
};
|
||||||
|
|
||||||
let edits = match dunder_all_exprs {
|
let edits = match dunder_all_exprs {
|
||||||
[] => fix::edits::make_redundant_alias(
|
[] => fix::edits::make_redundant_alias(imports.into_iter(), statement),
|
||||||
imports.iter().map(|b| b.import.member_name()),
|
[dunder_all] => {
|
||||||
statement,
|
fix::edits::add_to_dunder_all(imports.into_iter(), dunder_all, checker.stylist())
|
||||||
),
|
}
|
||||||
[dunder_all] => fix::edits::add_to_dunder_all(
|
|
||||||
imports.iter().map(|b| b.name),
|
|
||||||
dunder_all,
|
|
||||||
checker.stylist(),
|
|
||||||
),
|
|
||||||
_ => bail!("Cannot offer a fix when there are multiple __all__ definitions"),
|
_ => bail!("Cannot offer a fix when there are multiple __all__ definitions"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 [*] `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 [*] `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
|
---
|
||||||
|
__init__.py:2:8: F401 [*] `submodule.a` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||||
|
|
|
||||||
|
2 | import submodule.a
|
||||||
|
| ^^^^^^^^^^^ F401
|
||||||
|
3 | __all__ = ['FOO']
|
||||||
|
4 | FOO = 42
|
||||||
|
|
|
||||||
|
= help: Add `submodule` to __all__
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
1 1 |
|
||||||
|
2 2 | import submodule.a
|
||||||
|
3 |-__all__ = ['FOO']
|
||||||
|
3 |+__all__ = ['FOO', 'submodule']
|
||||||
|
4 4 | FOO = 42
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
|
---
|
||||||
|
__init__.py:1:8: F401 `submodule.a` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||||
|
|
|
||||||
|
1 | import submodule.a
|
||||||
|
| ^^^^^^^^^^^ F401
|
||||||
|
|
|
||||||
|
= help: Use an explicit re-export: `import submodule as submodule; import submodule.a`
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
---
|
---
|
||||||
__init__.py:1:8: F401 `os` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
__init__.py:1:8: F401 `os` imported but unused
|
||||||
|
|
|
|
||||||
1 | import os
|
1 | import os
|
||||||
| ^^ F401
|
| ^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 [*] `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
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
|
__init__.py:19:8: F401 [*] `sys` imported but unused
|
||||||
|
|
|
|
||||||
19 | import sys # F401: remove unused
|
19 | import sys # F401: remove unused
|
||||||
| ^^^ F401
|
| ^^^ F401
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
---
|
---
|
||||||
__init__.py:1:8: F401 [*] `os` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
__init__.py:1:8: F401 [*] `os` imported but unused
|
||||||
|
|
|
|
||||||
1 | import os
|
1 | import os
|
||||||
| ^^ F401
|
| ^^ F401
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue