mirror of https://github.com/astral-sh/ruff
feat: Add isort option lines-after-imports (#2440)
Fixes https://github.com/charliermarsh/ruff/issues/2243 Adds support for the isort option [lines_after_imports](https://pycqa.github.io/isort/docs/configuration/options.html#lines-after-imports) to insert blank lines between imports and the follow up code.
This commit is contained in:
parent
68422d4ff2
commit
ec7b25290b
19
README.md
19
README.md
|
|
@ -3500,6 +3500,25 @@ known-third-party = ["src"]
|
|||
|
||||
---
|
||||
|
||||
#### [`lines-after-imports`](#lines-after-imports)
|
||||
|
||||
The number of blank lines to place after imports.
|
||||
-1 for automatic determination.
|
||||
|
||||
**Default value**: `-1`
|
||||
|
||||
**Type**: `int`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
# Use a single line after each import block.
|
||||
lines-after-imports = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`no-lines-before`](#no-lines-before)
|
||||
|
||||
A list of sections that should _not_ be delineated from the previous
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
my_local_folder_object.get()
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
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
|
||||
|
|
@ -1,2 +1,5 @@
|
|||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.isort]
|
||||
lines-after-imports = 3
|
||||
|
|
|
|||
|
|
@ -957,6 +957,14 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"lines-after-imports": {
|
||||
"description": "The number of blank lines to place after imports. -1 for automatic determination.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int"
|
||||
},
|
||||
"no-lines-before": {
|
||||
"description": "A list of sections that should _not_ be delineated from the previous section via empty lines.",
|
||||
"type": [
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Either::{Left, Right};
|
||||
|
||||
use annotate::annotate_imports;
|
||||
use categorize::categorize_imports;
|
||||
pub use categorize::{categorize, ImportType};
|
||||
use comments::Comment;
|
||||
use itertools::Either::{Left, Right};
|
||||
use normalize::normalize_imports;
|
||||
use order::order_imports;
|
||||
use settings::RelativeImportsOrder;
|
||||
|
|
@ -127,6 +128,7 @@ pub fn format_imports(
|
|||
constants: &BTreeSet<String>,
|
||||
variables: &BTreeSet<String>,
|
||||
no_lines_before: &BTreeSet<ImportType>,
|
||||
lines_after_imports: isize,
|
||||
) -> String {
|
||||
let trailer = &block.trailer;
|
||||
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
|
||||
|
|
@ -214,11 +216,23 @@ pub fn format_imports(
|
|||
match trailer {
|
||||
None => {}
|
||||
Some(Trailer::Sibling) => {
|
||||
output.push_str(stylist.line_ending());
|
||||
if lines_after_imports >= 0 {
|
||||
for _ in 0..lines_after_imports {
|
||||
output.push_str(stylist.line_ending());
|
||||
}
|
||||
} else {
|
||||
output.push_str(stylist.line_ending());
|
||||
}
|
||||
}
|
||||
Some(Trailer::FunctionDef | Trailer::ClassDef) => {
|
||||
output.push_str(stylist.line_ending());
|
||||
output.push_str(stylist.line_ending());
|
||||
if lines_after_imports >= 0 {
|
||||
for _ in 0..lines_after_imports {
|
||||
output.push_str(stylist.line_ending());
|
||||
}
|
||||
} else {
|
||||
output.push_str(stylist.line_ending());
|
||||
output.push_str(stylist.line_ending());
|
||||
}
|
||||
}
|
||||
}
|
||||
output
|
||||
|
|
@ -232,13 +246,14 @@ mod tests {
|
|||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::categorize::ImportType;
|
||||
use super::settings::RelativeImportsOrder;
|
||||
use crate::assert_yaml_snapshot;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::Settings;
|
||||
use crate::test::{test_path, test_resource_path};
|
||||
|
||||
use super::categorize::ImportType;
|
||||
use super::settings::RelativeImportsOrder;
|
||||
|
||||
#[test_case(Path::new("add_newline_before_comments.py"))]
|
||||
#[test_case(Path::new("combine_as_imports.py"))]
|
||||
#[test_case(Path::new("combine_import_from.py"))]
|
||||
|
|
@ -641,4 +656,25 @@ mod tests {
|
|||
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("lines_after_imports_nothing_after.py"))]
|
||||
#[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<()> {
|
||||
let snapshot = format!("lines_after_imports_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&Settings {
|
||||
isort: super::settings::Settings {
|
||||
lines_after_imports: 3,
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
..Settings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.location);
|
||||
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ fn matches_ignoring_indentation(val1: &str, val2: &str) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
/// I001
|
||||
pub fn organize_imports(
|
||||
block: &Block,
|
||||
|
|
@ -117,6 +118,7 @@ pub fn organize_imports(
|
|||
&settings.isort.constants,
|
||||
&settings.isort.variables,
|
||||
&settings.isort.no_lines_before,
|
||||
settings.isort.lines_after_imports,
|
||||
);
|
||||
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
|
|
|
|||
|
|
@ -213,6 +213,17 @@ pub struct Options {
|
|||
/// A list of sections that should _not_ be delineated from the previous
|
||||
/// section via empty lines.
|
||||
pub no_lines_before: Option<Vec<ImportType>>,
|
||||
#[option(
|
||||
default = r#"-1"#,
|
||||
value_type = "int",
|
||||
example = r#"
|
||||
# Use a single line after each import block.
|
||||
lines-after-imports = 1
|
||||
"#
|
||||
)]
|
||||
/// The number of blank lines to place after imports.
|
||||
/// -1 for automatic determination.
|
||||
pub lines_after_imports: Option<isize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
|
|
@ -234,6 +245,7 @@ pub struct Settings {
|
|||
pub constants: BTreeSet<String>,
|
||||
pub variables: BTreeSet<String>,
|
||||
pub no_lines_before: BTreeSet<ImportType>,
|
||||
pub lines_after_imports: isize,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
|
@ -255,6 +267,7 @@ impl Default for Settings {
|
|||
constants: BTreeSet::new(),
|
||||
variables: BTreeSet::new(),
|
||||
no_lines_before: BTreeSet::new(),
|
||||
lines_after_imports: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -282,6 +295,7 @@ impl From<Options> for Settings {
|
|||
constants: BTreeSet::from_iter(options.constants.unwrap_or_default()),
|
||||
variables: BTreeSet::from_iter(options.variables.unwrap_or_default()),
|
||||
no_lines_before: BTreeSet::from_iter(options.no_lines_before.unwrap_or_default()),
|
||||
lines_after_imports: options.lines_after_imports.unwrap_or(-1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -305,6 +319,7 @@ impl From<Settings> for Options {
|
|||
constants: Some(settings.constants.into_iter().collect()),
|
||||
variables: Some(settings.variables.into_iter().collect()),
|
||||
no_lines_before: Some(settings.no_lines_before.into_iter().collect()),
|
||||
lines_after_imports: Some(settings.lines_after_imports),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
source: src/rules/isort/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnsortedImports: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
fix:
|
||||
content:
|
||||
- from __future__ import annotations
|
||||
- ""
|
||||
- from typing import Any
|
||||
- ""
|
||||
- from my_first_party import my_first_party_object
|
||||
- from requests import Session
|
||||
- ""
|
||||
- from . import my_local_folder_object
|
||||
- ""
|
||||
- ""
|
||||
- ""
|
||||
- ""
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
source: src/rules/isort/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnsortedImports: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 0
|
||||
fix:
|
||||
content:
|
||||
- from __future__ import annotations
|
||||
- ""
|
||||
- from typing import Any
|
||||
- ""
|
||||
- from my_first_party import my_first_party_object
|
||||
- from requests import Session
|
||||
- ""
|
||||
- from . import my_local_folder_object
|
||||
- ""
|
||||
- ""
|
||||
- ""
|
||||
- ""
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: src/rules/isort/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnsortedImports: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
fix:
|
||||
content:
|
||||
- from __future__ import annotations
|
||||
- ""
|
||||
- from typing import Any
|
||||
- ""
|
||||
- from my_first_party import my_first_party_object
|
||||
- from requests import Session
|
||||
- ""
|
||||
- from . import my_local_folder_object
|
||||
- ""
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
Loading…
Reference in New Issue