Skip to content

Commit

Permalink
[pycodestyle] - add autofix for multiple_imports_on_one_line (`E4…
Browse files Browse the repository at this point in the history
…01`)
  • Loading branch information
diceroll123 committed Jan 15, 2024
1 parent 6183b8e commit 7b3b35e
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 21 deletions.
7 changes: 7 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pycodestyle/E40.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#: E401
import os, sys
import re as regex, string # also with a comment!

def blah():
import datetime as dt, copy
def nested_and_tested():
import builtins, textwrap as tw

#: Okay
import os
import sys
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/pycodestyle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mod tests {
}

#[test_case(Rule::IsLiteral, Path::new("constant_literals.py"))]
#[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))]
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))]
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
Expand Down
46 changes: 41 additions & 5 deletions crates/ruff_linter/src/rules/pycodestyle/rules/imports.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Alias, PySourceType, Stmt};
use ruff_python_trivia::{indentation_at_offset, PythonWhitespace};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand All @@ -26,11 +27,15 @@ use crate::checkers::ast::Checker;
#[violation]
pub struct MultipleImportsOnOneLine;

impl Violation for MultipleImportsOnOneLine {
impl AlwaysFixableViolation for MultipleImportsOnOneLine {
#[derive_message_formats]
fn message(&self) -> String {
format!("Multiple imports on one line")
}

fn fix_title(&self) -> String {
format!("Split imports onto multiple lines")
}
}

/// ## What it does
Expand Down Expand Up @@ -85,9 +90,40 @@ impl Violation for ModuleImportNotAtTopOfFile {
/// E401
pub(crate) fn multiple_imports_on_one_line(checker: &mut Checker, stmt: &Stmt, names: &[Alias]) {
if names.len() > 1 {
checker
.diagnostics
.push(Diagnostic::new(MultipleImportsOnOneLine, stmt.range()));
let mut diagnostic = Diagnostic::new(MultipleImportsOnOneLine, stmt.range());

if checker.settings.preview.is_enabled() {
let indentation = indentation_at_offset(stmt.start(), checker.locator()).unwrap_or("");

let mut replacement = String::new();

for item in names {
let Alias {
range: _,
name,
asname,
} = item;

if let Some(asname) = asname {
replacement = format!("{replacement}{indentation}import {name} as {asname}\n",);
} else {
replacement = format!("{replacement}{indentation}import {name}\n",);
}
}

// remove leading whitespace because we start at the import keyword
replacement = replacement.trim_whitespace_start().to_string();

// remove trailing newline
replacement = replacement.trim_end_matches('\n').to_string();

diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
replacement,
stmt.range(),
)));
}

checker.diagnostics.push(diagnostic);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,156 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E40.py:55:1: E402 Module level import not at top of file
E40.py:11:1: E402 Module level import not at top of file
|
53 | VERSION = '1.2.3'
54 |
55 | import foo
10 | #: Okay
11 | import os
| ^^^^^^^^^ E402
12 | import sys
|

E40.py:12:1: E402 Module level import not at top of file
|
10 | #: Okay
11 | import os
12 | import sys
| ^^^^^^^^^^ E402
13 |
14 | from subprocess import Popen, PIPE
|

E40.py:14:1: E402 Module level import not at top of file
|
12 | import sys
13 |
14 | from subprocess import Popen, PIPE
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E402
15 |
16 | from myclass import MyClass
|

E40.py:16:1: E402 Module level import not at top of file
|
14 | from subprocess import Popen, PIPE
15 |
16 | from myclass import MyClass
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ E402
17 | from foo.bar.yourclass import YourClass
|

E40.py:17:1: E402 Module level import not at top of file
|
16 | from myclass import MyClass
17 | from foo.bar.yourclass import YourClass
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E402
18 |
19 | import myclass
|

E40.py:19:1: E402 Module level import not at top of file
|
17 | from foo.bar.yourclass import YourClass
18 |
19 | import myclass
| ^^^^^^^^^^^^^^ E402
20 | import foo.bar.yourclass
21 | #: Okay
|

E40.py:20:1: E402 Module level import not at top of file
|
19 | import myclass
20 | import foo.bar.yourclass
| ^^^^^^^^^^^^^^^^^^^^^^^^ E402
21 | #: Okay
22 | __all__ = ['abc']
|

E40.py:24:1: E402 Module level import not at top of file
|
22 | __all__ = ['abc']
23 |
24 | import foo
| ^^^^^^^^^^ E402
25 | #: Okay
26 | __version__ = "42"
|

E40.py:28:1: E402 Module level import not at top of file
|
26 | __version__ = "42"
27 |
28 | import foo
| ^^^^^^^^^^ E402
29 | #: Okay
30 | __author__ = "Simon Gomizelj"
|

E40.py:32:1: E402 Module level import not at top of file
|
30 | __author__ = "Simon Gomizelj"
31 |
32 | import foo
| ^^^^^^^^^^ E402
33 | #: Okay
34 | try:
|

E40.py:43:1: E402 Module level import not at top of file
|
41 | print('made attempt to import foo')
42 |
43 | import bar
| ^^^^^^^^^^ E402
44 | #: Okay
45 | with warnings.catch_warnings():
|

E40.py:49:1: E402 Module level import not at top of file
|
47 | import foo
48 |
49 | import bar
| ^^^^^^^^^^ E402
50 | #: Okay
51 | if False:
|

E40.py:58:1: E402 Module level import not at top of file
|
56 | import mwahaha
57 |
58 | import bar
| ^^^^^^^^^^ E402
59 | #: E402
60 | VERSION = '1.2.3'
|

E40.py:62:1: E402 Module level import not at top of file
|
60 | VERSION = '1.2.3'
61 |
62 | import foo
| ^^^^^^^^^^ E402
56 | #: E402
57 | import foo
63 | #: E402
64 | import foo
|

E40.py:57:1: E402 Module level import not at top of file
E40.py:64:1: E402 Module level import not at top of file
|
55 | import foo
56 | #: E402
57 | import foo
62 | import foo
63 | #: E402
64 | import foo
| ^^^^^^^^^^ E402
58 |
59 | a = 1
65 |
66 | a = 1
|

E40.py:61:1: E402 Module level import not at top of file
E40.py:68:1: E402 Module level import not at top of file
|
59 | a = 1
60 |
61 | import bar
66 | a = 1
67 |
68 | import bar
| ^^^^^^^^^^ E402
|

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E40.py:2:1: E401 [*] Multiple imports on one line
|
1 | #: E401
2 | import os, sys
| ^^^^^^^^^^^^^^ E401
3 | import re as regex, string # also with a comment!
|
= help: Split imports onto multiple lines

Safe fix
1 1 | #: E401
2 |-import os, sys
2 |+import os
3 |+import sys
3 4 | import re as regex, string # also with a comment!
4 5 |
5 6 | def blah():

E40.py:3:1: E401 [*] Multiple imports on one line
|
1 | #: E401
2 | import os, sys
3 | import re as regex, string # also with a comment!
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E401
4 |
5 | def blah():
|
= help: Split imports onto multiple lines

Safe fix
1 1 | #: E401
2 2 | import os, sys
3 |-import re as regex, string # also with a comment!
3 |+import re as regex
4 |+import string # also with a comment!
4 5 |
5 6 | def blah():
6 7 | import datetime as dt, copy

E40.py:6:5: E401 [*] Multiple imports on one line
|
5 | def blah():
6 | import datetime as dt, copy
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ E401
7 | def nested_and_tested():
8 | import builtins, textwrap as tw
|
= help: Split imports onto multiple lines

Safe fix
3 3 | import re as regex, string # also with a comment!
4 4 |
5 5 | def blah():
6 |- import datetime as dt, copy
6 |+ import datetime as dt
7 |+ import copy
7 8 | def nested_and_tested():
8 9 | import builtins, textwrap as tw
9 10 |

E40.py:8:9: E401 [*] Multiple imports on one line
|
6 | import datetime as dt, copy
7 | def nested_and_tested():
8 | import builtins, textwrap as tw
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E401
9 |
10 | #: Okay
|
= help: Split imports onto multiple lines

Safe fix
5 5 | def blah():
6 6 | import datetime as dt, copy
7 7 | def nested_and_tested():
8 |- import builtins, textwrap as tw
8 |+ import builtins
9 |+ import textwrap as tw
9 10 |
10 11 | #: Okay
11 12 | import os


0 comments on commit 7b3b35e

Please sign in to comment.