Skip to content

Commit c2921e9

Browse files
authored
[pylint] Implement import-self (W0406) (#4154)
1 parent 93cfce6 commit c2921e9

File tree

10 files changed

+127
-0
lines changed

10 files changed

+127
-0
lines changed

crates/ruff/resources/test/fixtures/pylint/import_self/__init__.py

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import import_self.module
2+
from import_self import module
3+
from . import module

crates/ruff/src/checkers/ast/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,13 @@ where
10001000
if self.settings.rules.enabled(Rule::ManualFromImport) {
10011001
pylint::rules::manual_from_import(self, stmt, alias, names);
10021002
}
1003+
if self.settings.rules.enabled(Rule::ImportSelf) {
1004+
if let Some(diagnostic) =
1005+
pylint::rules::import_self(alias, self.module_path.as_deref())
1006+
{
1007+
self.diagnostics.push(diagnostic);
1008+
}
1009+
}
10031010

10041011
if let Some(asname) = &alias.node.asname {
10051012
let name = alias.node.name.split('.').last().unwrap();
@@ -1477,6 +1484,17 @@ where
14771484
}
14781485
}
14791486

1487+
if self.settings.rules.enabled(Rule::ImportSelf) {
1488+
if let Some(diagnostic) = pylint::rules::import_from_self(
1489+
*level,
1490+
module.as_deref(),
1491+
names,
1492+
self.module_path.as_deref(),
1493+
) {
1494+
self.diagnostics.push(diagnostic);
1495+
}
1496+
}
1497+
14801498
if self.settings.rules.enabled(Rule::BannedImportFrom) {
14811499
if let Some(diagnostic) = flake8_import_conventions::rules::banned_import_from(
14821500
stmt,

crates/ruff/src/codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
204204
(Pylint, "R5501") => Rule::CollapsibleElseIf,
205205
(Pylint, "W0120") => Rule::UselessElseOnLoop,
206206
(Pylint, "W0129") => Rule::AssertOnStringLiteral,
207+
(Pylint, "W0406") => Rule::ImportSelf,
207208
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
208209
(Pylint, "W0603") => Rule::GlobalStatement,
209210
(Pylint, "W0711") => Rule::BinaryOpException,

crates/ruff/src/registry.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ ruff_macros::register_rules!(
156156
rules::pylint::rules::BadStringFormatType,
157157
rules::pylint::rules::BidirectionalUnicode,
158158
rules::pylint::rules::BinaryOpException,
159+
rules::pylint::rules::ImportSelf,
159160
rules::pylint::rules::InvalidCharacterBackspace,
160161
rules::pylint::rules::InvalidCharacterSub,
161162
rules::pylint::rules::InvalidCharacterEsc,

crates/ruff/src/rules/pylint/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ mod tests {
4040
#[test_case(Rule::ContinueInFinally, Path::new("continue_in_finally.py"); "PLE0116")]
4141
#[test_case(Rule::GlobalStatement, Path::new("global_statement.py"); "PLW0603")]
4242
#[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")]
43+
#[test_case(Rule::ImportSelf, Path::new("import_self/module.py"); "PLW0406")]
4344
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
4445
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
4546
#[test_case(Rule::InvalidCharacterBackspace, Path::new("invalid_characters.py"); "PLE2510")]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use rustpython_parser::ast::Alias;
2+
3+
use ruff_diagnostics::{Diagnostic, Violation};
4+
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_python_ast::helpers::resolve_imported_module_path;
6+
7+
#[violation]
8+
pub struct ImportSelf {
9+
pub name: String,
10+
}
11+
12+
impl Violation for ImportSelf {
13+
#[derive_message_formats]
14+
fn message(&self) -> String {
15+
let Self { name } = self;
16+
format!("Module `{name}` imports itself")
17+
}
18+
}
19+
20+
/// PLW0406
21+
pub fn import_self(alias: &Alias, module_path: Option<&[String]>) -> Option<Diagnostic> {
22+
let Some(module_path) = module_path else {
23+
return None;
24+
};
25+
26+
if alias.node.name.split('.').eq(module_path) {
27+
return Some(Diagnostic::new(
28+
ImportSelf {
29+
name: alias.node.name.clone(),
30+
},
31+
alias.range(),
32+
));
33+
}
34+
35+
None
36+
}
37+
38+
/// PLW0406
39+
pub fn import_from_self(
40+
level: Option<usize>,
41+
module: Option<&str>,
42+
names: &[Alias],
43+
module_path: Option<&[String]>,
44+
) -> Option<Diagnostic> {
45+
let Some(module_path) = module_path else {
46+
return None;
47+
};
48+
let Some(imported_module_path) = resolve_imported_module_path(level, module, Some(module_path)) else {
49+
return None;
50+
};
51+
52+
if imported_module_path
53+
.split('.')
54+
.eq(&module_path[..module_path.len() - 1])
55+
{
56+
if let Some(alias) = names
57+
.iter()
58+
.find(|alias| alias.node.name == module_path[module_path.len() - 1])
59+
{
60+
return Some(Diagnostic::new(
61+
ImportSelf {
62+
name: format!("{}.{}", imported_module_path, alias.node.name),
63+
},
64+
alias.range(),
65+
));
66+
}
67+
}
68+
69+
None
70+
}

crates/ruff/src/rules/pylint/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant};
1010
pub use continue_in_finally::{continue_in_finally, ContinueInFinally};
1111
pub use global_statement::{global_statement, GlobalStatement};
1212
pub use global_variable_not_assigned::GlobalVariableNotAssigned;
13+
pub use import_self::{import_from_self, import_self, ImportSelf};
1314
pub use invalid_all_format::{invalid_all_format, InvalidAllFormat};
1415
pub use invalid_all_object::{invalid_all_object, InvalidAllObject};
1516
pub use invalid_envvar_default::{invalid_envvar_default, InvalidEnvvarDefault};
@@ -57,6 +58,7 @@ mod comparison_of_constant;
5758
mod continue_in_finally;
5859
mod global_statement;
5960
mod global_variable_not_assigned;
61+
mod import_self;
6062
mod invalid_all_format;
6163
mod invalid_all_object;
6264
mod invalid_envvar_default;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: crates/ruff/src/rules/pylint/mod.rs
3+
---
4+
module.py:1:8: PLW0406 Module `import_self.module` imports itself
5+
|
6+
1 | import import_self.module
7+
| ^^^^^^^^^^^^^^^^^^ PLW0406
8+
2 | from import_self import module
9+
3 | from . import module
10+
|
11+
12+
module.py:2:25: PLW0406 Module `import_self.module` imports itself
13+
|
14+
2 | import import_self.module
15+
3 | from import_self import module
16+
| ^^^^^^ PLW0406
17+
4 | from . import module
18+
|
19+
20+
module.py:3:15: PLW0406 Module `import_self.module` imports itself
21+
|
22+
3 | import import_self.module
23+
4 | from import_self import module
24+
5 | from . import module
25+
| ^^^^^^ PLW0406
26+
|
27+
28+

ruff.schema.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)