Skip to content

Commit c98ef43

Browse files
committed
Check non-from imports
1 parent 44cc626 commit c98ef43

3 files changed

Lines changed: 63 additions & 51 deletions

File tree

crates/ruff_linter/resources/test/fixtures/pylint/import_private_name/submodule/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from _f.g import h
55
from i import _j
66
from k import _l as m
7+
import _aaa
8+
import bbb._ccc
79

810
# Non-errors.
911
import n
@@ -30,12 +32,15 @@
3032
from _rr import ss
3133
from tt._uu import vv
3234
from _ww.xx import yy as zz
35+
import _ddd as ddd
3336

3437
some_variable: _nn = None
3538

3639
def func(arg: qq) -> ss:
3740
pass
3841

3942
class Class:
43+
lst: list[ddd]
44+
4045
def __init__(self, arg: vv) -> "zz":
4146
pass

crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use itertools::join;
22
use ruff_diagnostics::{Diagnostic, Violation};
33
use ruff_macros::{derive_message_formats, violation};
4+
use std::borrow::Cow;
45

5-
use ruff_python_semantic::{Imported, ResolvedReference, Scope};
6+
use ruff_python_semantic::{FromImport, Import, Imported, ResolvedReference, Scope};
67
use ruff_text_size::Ranged;
78

89
use crate::checkers::ast::Checker;
@@ -76,25 +77,27 @@ pub(crate) fn import_private_name(
7677
let Some(import) = binding.as_any_import() else {
7778
continue;
7879
};
79-
let Some(import) = import.from_import() else {
80-
continue;
80+
81+
let import_info = match import {
82+
import if import.is_import() => ImportInfo::from(import.import().unwrap()),
83+
import if import.is_from_import() => ImportInfo::from(import.from_import().unwrap()),
84+
_ => return,
8185
};
8286

83-
let module = import.module_name();
84-
let Some(root_module) = module.first() else {
87+
let Some(root_module) = import_info.module_name.first() else {
8588
continue;
8689
};
8790

8891
// Relative imports are not a public API.
8992
// Ex) `from . import foo`
90-
if module.starts_with(&["."]) {
93+
if import_info.module_name.starts_with(&["."]) {
9194
continue;
9295
}
9396

9497
// We can also ignore dunder names.
9598
// Ex) `from __future__ import annotations`
9699
// Ex) `from foo import __version__`
97-
if root_module.starts_with("__") || import.member_name().starts_with("__") {
100+
if root_module.starts_with("__") || import_info.member_name.starts_with("__") {
98101
continue;
99102
}
100103

@@ -107,8 +110,11 @@ pub(crate) fn import_private_name(
107110
continue;
108111
}
109112

110-
let call_path = import.call_path();
111-
if call_path.iter().any(|name| name.starts_with('_')) {
113+
if import_info
114+
.call_path
115+
.iter()
116+
.any(|name| name.starts_with('_'))
117+
{
112118
// Ignore private imports used for typing.
113119
if binding.context.is_runtime()
114120
&& binding
@@ -119,9 +125,16 @@ pub(crate) fn import_private_name(
119125
continue;
120126
}
121127

122-
let private_name = call_path.iter().find(|name| name.starts_with('_')).unwrap();
128+
let private_name = import_info
129+
.call_path
130+
.iter()
131+
.find(|name| name.starts_with('_'))
132+
.unwrap();
123133
let external_module = Some(join(
124-
call_path.iter().take_while(|name| name != &private_name),
134+
import_info
135+
.call_path
136+
.iter()
137+
.take_while(|name| name != &private_name),
125138
".",
126139
))
127140
.filter(|module| !module.is_empty());
@@ -144,3 +157,35 @@ fn is_typing(reference: &ResolvedReference) -> bool {
144157
|| reference.in_simple_string_type_definition()
145158
|| reference.in_runtime_evaluated_annotation()
146159
}
160+
161+
struct ImportInfo<'a> {
162+
module_name: &'a [&'a str],
163+
member_name: Cow<'a, str>,
164+
call_path: &'a [&'a str],
165+
}
166+
167+
impl<'a> From<&'a FromImport<'_>> for ImportInfo<'a> {
168+
fn from(import: &'a FromImport) -> Self {
169+
let module_name = import.module_name();
170+
let member_name = import.member_name();
171+
let call_path = import.call_path();
172+
Self {
173+
module_name,
174+
member_name,
175+
call_path,
176+
}
177+
}
178+
}
179+
180+
impl<'a> From<&'a Import<'_>> for ImportInfo<'a> {
181+
fn from(import: &'a Import) -> Self {
182+
let module_name = import.module_name();
183+
let member_name = import.member_name();
184+
let call_path = import.call_path();
185+
Self {
186+
module_name,
187+
member_name,
188+
call_path,
189+
}
190+
}
191+
}
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,14 @@
11
---
22
source: crates/ruff_linter/src/rules/pylint/mod.rs
33
---
4-
__main__.py:2:16: PLC2701 Private name import `_a`
5-
|
6-
1 | # Errors.
7-
2 | from _a import b
8-
| ^ PLC2701
9-
3 | from c._d import e
10-
4 | from _f.g import h
11-
|
12-
13-
__main__.py:3:18: PLC2701 Private name import `_d` from external module `c`
14-
|
15-
1 | # Errors.
16-
2 | from _a import b
17-
3 | from c._d import e
18-
| ^ PLC2701
19-
4 | from _f.g import h
20-
5 | from i import _j
21-
|
22-
23-
__main__.py:4:18: PLC2701 Private name import `_f`
24-
|
25-
2 | from _a import b
26-
3 | from c._d import e
27-
4 | from _f.g import h
28-
| ^ PLC2701
29-
5 | from i import _j
30-
6 | from k import _l as m
31-
|
32-
33-
__main__.py:5:15: PLC2701 Private name import `_j` from external module `i`
34-
|
35-
3 | from c._d import e
36-
4 | from _f.g import h
37-
5 | from i import _j
38-
| ^^ PLC2701
39-
6 | from k import _l as m
40-
|
41-
424
__main__.py:6:21: PLC2701 Private name import `_l` from external module `k`
435
|
446
4 | from _f.g import h
457
5 | from i import _j
468
6 | from k import _l as m
479
| ^ PLC2701
48-
7 |
49-
8 | # Non-errors.
10+
7 | import _aaa
11+
8 | import bbb._ccc
5012
|
5113

5214

0 commit comments

Comments
 (0)