From 51d485758783533ddb17adc3d82647b79a81999f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 13 Feb 2024 09:30:29 +0100 Subject: [PATCH] Limit `isort.lines-after-imports` to 1 for stub files --- .../fixtures/isort/lines_after_imports.pyi | 16 ++++++ crates/ruff_linter/src/rules/isort/mod.rs | 34 +++++++++++- ...isort__tests__lines_after_imports.pyi.snap | 41 ++++++++++++++ ...s__lines_after_imports_class_after.py.snap | 38 +++++++++++++ ...ts__lines_after_imports_func_after.py.snap | 55 +++++++++++++++++++ ...after_imports_lines_after_imports.pyi.snap | 41 ++++++++++++++ crates/ruff_workspace/src/options.rs | 3 + ruff.schema.json | 2 +- 8 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports.pyi.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_class_after.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_func_after.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_lines_after_imports.pyi.snap diff --git a/crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi b/crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi new file mode 100644 index 0000000000000..83d24dd8b11f2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/lines_after_imports.pyi @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Any + +from requests import Session + +from my_first_party import my_first_party_object + +from . import my_local_folder_object + + + +class Thing(object): + name: str + def __init__(self, name: str): + self.name = name diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index d77a818aa8fff..6541e2cb3bcd4 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -108,7 +108,17 @@ pub(crate) fn format_imports( output.push_str(block_output.as_str()); } - let lines_after_imports = settings.lines_after_imports; + let lines_after_imports = if source_type.is_stub() { + // Limit the number of lines after imports in stub files to at most 1 to be compatible with the formatter. + // `isort` does the same when using the profile `isort` + match settings.lines_after_imports { + 0 => 0, + _ => 1, + } + } else { + settings.lines_after_imports + }; + match trailer { None => {} Some(Trailer::Sibling) => { @@ -978,6 +988,7 @@ mod tests { } #[test_case(Path::new("lines_after_imports_nothing_after.py"))] + #[test_case(Path::new("lines_after_imports.pyi"))] #[test_case(Path::new("lines_after_imports_func_after.py"))] #[test_case(Path::new("lines_after_imports_class_after.py"))] fn lines_after_imports(path: &Path) -> Result<()> { @@ -998,6 +1009,27 @@ mod tests { Ok(()) } + #[test_case(Path::new("lines_after_imports.pyi"))] + #[test_case(Path::new("lines_after_imports_func_after.py"))] + #[test_case(Path::new("lines_after_imports_class_after.py"))] + fn lines_after_imports_default_settings(path: &Path) -> Result<()> { + let snapshot = path.to_string_lossy(); + let mut diagnostics = test_path( + Path::new("isort").join(path).as_path(), + &LinterSettings { + src: vec![test_resource_path("fixtures/isort")], + isort: super::settings::Settings { + lines_after_imports: -1, + ..super::settings::Settings::default() + }, + ..LinterSettings::for_rule(Rule::UnsortedImports) + }, + )?; + diagnostics.sort_by_key(Ranged::start); + assert_messages!(*snapshot, diagnostics); + Ok(()) + } + #[test_case(Path::new("lines_between_types.py"))] fn lines_between_types(path: &Path) -> Result<()> { let snapshot = format!("lines_between_types{}", path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports.pyi.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports.pyi.snap new file mode 100644 index 0000000000000..3cb4eb573c315 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports.pyi.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +lines_after_imports.pyi:1:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | / from __future__ import annotations + 2 | | + 3 | | from typing import Any + 4 | | + 5 | | from requests import Session + 6 | | + 7 | | from my_first_party import my_first_party_object + 8 | | + 9 | | from . import my_local_folder_object +10 | | +11 | | +12 | | +13 | | class Thing(object): + | |_^ I001 +14 | name: str +15 | def __init__(self, name: str): + | + = help: Organize imports + +ℹ Safe fix +2 2 | +3 3 | from typing import Any +4 4 | +5 |-from requests import Session +6 |- +7 5 | from my_first_party import my_first_party_object + 6 |+from requests import Session +8 7 | +9 8 | from . import my_local_folder_object +10 |- +11 |- +12 9 | +13 10 | class Thing(object): +14 11 | name: str + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_class_after.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_class_after.py.snap new file mode 100644 index 0000000000000..8f5efb9988504 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_class_after.py.snap @@ -0,0 +1,38 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +lines_after_imports_class_after.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | / from __future__ import annotations + 2 | | + 3 | | from typing import Any + 4 | | + 5 | | from requests import Session + 6 | | + 7 | | from my_first_party import my_first_party_object + 8 | | + 9 | | from . import my_local_folder_object +10 | | class Thing(object): + | |_^ I001 +11 | name: str +12 | def __init__(self, name: str): + | + = help: Organize imports + +ℹ Safe fix +2 2 | +3 3 | from typing import Any +4 4 | +5 |-from requests import Session +6 |- +7 5 | from my_first_party import my_first_party_object + 6 |+from requests import Session +8 7 | +9 8 | from . import my_local_folder_object + 9 |+ + 10 |+ +10 11 | class Thing(object): +11 12 | name: str +12 13 | def __init__(self, name: str): + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_func_after.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_func_after.py.snap new file mode 100644 index 0000000000000..0f71f16f36243 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_func_after.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +lines_after_imports_func_after.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | / from __future__ import annotations + 2 | | + 3 | | from typing import Any + 4 | | + 5 | | from requests import Session + 6 | | + 7 | | from my_first_party import my_first_party_object + 8 | | + 9 | | from . import my_local_folder_object +10 | | +11 | | +12 | | +13 | | +14 | | +15 | | +16 | | +17 | | +18 | | +19 | | +20 | | +21 | | def main(): + | |_^ I001 +22 | my_local_folder_object.get() + | + = help: Organize imports + +ℹ Safe fix +2 2 | +3 3 | from typing import Any +4 4 | +5 |-from requests import Session +6 |- +7 5 | from my_first_party import my_first_party_object + 6 |+from requests import Session +8 7 | +9 8 | from . import my_local_folder_object +10 |- +11 |- +12 |- +13 |- +14 |- +15 |- +16 |- +17 |- +18 |- +19 9 | +20 10 | +21 11 | def main(): + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_lines_after_imports.pyi.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_lines_after_imports.pyi.snap new file mode 100644 index 0000000000000..3cb4eb573c315 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__lines_after_imports_lines_after_imports.pyi.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +lines_after_imports.pyi:1:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | / from __future__ import annotations + 2 | | + 3 | | from typing import Any + 4 | | + 5 | | from requests import Session + 6 | | + 7 | | from my_first_party import my_first_party_object + 8 | | + 9 | | from . import my_local_folder_object +10 | | +11 | | +12 | | +13 | | class Thing(object): + | |_^ I001 +14 | name: str +15 | def __init__(self, name: str): + | + = help: Organize imports + +ℹ Safe fix +2 2 | +3 3 | from typing import Any +4 4 | +5 |-from requests import Session +6 |- +7 5 | from my_first_party import my_first_party_object + 6 |+from requests import Session +8 7 | +9 8 | from . import my_local_folder_object +10 |- +11 |- +12 9 | +13 10 | class Thing(object): +14 11 | name: str + + diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 89be64b5ebed1..e5cebf71816d8 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2049,6 +2049,9 @@ pub struct IsortOptions { /// The number of blank lines to place after imports. /// Use `-1` for automatic determination. /// + /// Ruff uses at most one blank line after imports in typing stub files (files with `.pyi` extension) in accordance to + /// the typing style recommendations ([source](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)). + /// /// When using the formatter, only the values `-1`, `1`, and `2` are compatible because /// it enforces at least one empty and at most two empty lines after imports. #[option( diff --git a/ruff.schema.json b/ruff.schema.json index 369c973256cc5..6d760a2c18b98 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1584,7 +1584,7 @@ ] }, "lines-after-imports": { - "description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.", + "description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nRuff uses at most one blank line after imports in typing stub files (files with `.pyi` extension) in accordance to the typing style recommendations ([source](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)).\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.", "type": [ "integer", "null"