[isort] Add no-lines-before Option (#1955)
Closes https://github.com/charliermarsh/ruff/issues/1916.
This commit is contained in:
18
README.md
18
README.md
@@ -3135,6 +3135,24 @@ known-third-party = ["src"]
|
||||
|
||||
---
|
||||
|
||||
#### [`no-lines-before`](#no-lines-before)
|
||||
|
||||
A list of sections that should _not_ be delineated from the previous
|
||||
section via empty lines.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Option<Vec<ImportType>>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
no-lines-before = ["future", "standard-library"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`order-by-type`](#order-by-type)
|
||||
|
||||
Order imports by type, which is determined by case, in addition to
|
||||
|
||||
9
resources/test/fixtures/isort/no_lines_before.py
vendored
Normal file
9
resources/test/fixtures/isort/no_lines_before.py
vendored
Normal file
@@ -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
|
||||
@@ -762,6 +762,16 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ImportType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"future",
|
||||
"standard-library",
|
||||
"third-party",
|
||||
"first-party",
|
||||
"local-folder"
|
||||
]
|
||||
},
|
||||
"IsortOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -843,6 +853,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"no-lines-before": {
|
||||
"description": "A list of sections that should _not_ be delineated from the previous section via empty lines.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/ImportType"
|
||||
}
|
||||
},
|
||||
"order-by-type": {
|
||||
"description": "Order imports by type, which is determined by case, in addition to alphabetically.",
|
||||
"type": [
|
||||
|
||||
@@ -3,10 +3,15 @@ use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::debug;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::python::sys::KNOWN_STANDARD_LIBRARY;
|
||||
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
|
||||
#[derive(
|
||||
Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema, Hash,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum ImportType {
|
||||
Future,
|
||||
StandardLibrary,
|
||||
|
||||
@@ -575,6 +575,7 @@ pub fn format_imports(
|
||||
classes: &BTreeSet<String>,
|
||||
constants: &BTreeSet<String>,
|
||||
variables: &BTreeSet<String>,
|
||||
no_lines_before: &BTreeSet<ImportType>,
|
||||
) -> String {
|
||||
let trailer = &block.trailer;
|
||||
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
|
||||
@@ -596,7 +597,7 @@ pub fn format_imports(
|
||||
|
||||
// Generate replacement source code.
|
||||
let mut is_first_block = true;
|
||||
for import_block in block_by_type.into_values() {
|
||||
for (import_type, import_block) in block_by_type {
|
||||
let mut imports = order_imports(
|
||||
import_block,
|
||||
order_by_type,
|
||||
@@ -628,7 +629,7 @@ pub fn format_imports(
|
||||
// Add a blank line between every section.
|
||||
if is_first_block {
|
||||
is_first_block = false;
|
||||
} else {
|
||||
} else if !no_lines_before.contains(&import_type) {
|
||||
output.append(stylist.line_ending());
|
||||
}
|
||||
|
||||
@@ -680,6 +681,7 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::categorize::ImportType;
|
||||
use super::settings::RelatveImportsOrder;
|
||||
use crate::linter::test_path;
|
||||
use crate::registry::RuleCode;
|
||||
@@ -726,6 +728,7 @@ mod tests {
|
||||
#[test_case(Path::new("order_by_type_with_custom_classes.py"))]
|
||||
#[test_case(Path::new("order_by_type_with_custom_constants.py"))]
|
||||
#[test_case(Path::new("order_by_type_with_custom_variables.py"))]
|
||||
#[test_case(Path::new("no_lines_before.py"))]
|
||||
fn default(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -1073,4 +1076,31 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("no_lines_before.py"))]
|
||||
fn no_lines_before(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("no_lines_before.py_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
Path::new("./resources/test/fixtures/isort")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&Settings {
|
||||
isort: super::settings::Settings {
|
||||
no_lines_before: BTreeSet::from([
|
||||
ImportType::Future,
|
||||
ImportType::StandardLibrary,
|
||||
ImportType::ThirdParty,
|
||||
ImportType::FirstParty,
|
||||
ImportType::LocalFolder,
|
||||
]),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
|
||||
..Settings::for_rule(RuleCode::I001)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn organize_imports(
|
||||
&settings.isort.classes,
|
||||
&settings.isort.constants,
|
||||
&settings.isort.variables,
|
||||
&settings.isort.no_lines_before,
|
||||
);
|
||||
|
||||
// Expand the span the entire range, including leading and trailing space.
|
||||
|
||||
@@ -6,6 +6,8 @@ use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::categorize::ImportType;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub enum RelatveImportsOrder {
|
||||
@@ -201,6 +203,16 @@ pub struct Options {
|
||||
/// An override list of tokens to always recognize as a var
|
||||
/// for `order-by-type` regardless of casing.
|
||||
pub variables: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "Option<Vec<ImportType>>",
|
||||
example = r#"
|
||||
no-lines-before = ["future", "standard-library"]
|
||||
"#
|
||||
)]
|
||||
/// A list of sections that should _not_ be delineated from the previous
|
||||
/// section via empty lines.
|
||||
pub no_lines_before: Option<Vec<ImportType>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
@@ -221,6 +233,7 @@ pub struct Settings {
|
||||
pub classes: BTreeSet<String>,
|
||||
pub constants: BTreeSet<String>,
|
||||
pub variables: BTreeSet<String>,
|
||||
pub no_lines_before: BTreeSet<ImportType>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -241,6 +254,7 @@ impl Default for Settings {
|
||||
classes: BTreeSet::new(),
|
||||
constants: BTreeSet::new(),
|
||||
variables: BTreeSet::new(),
|
||||
no_lines_before: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,6 +281,7 @@ impl From<Options> for Settings {
|
||||
classes: BTreeSet::from_iter(options.classes.unwrap_or_default()),
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,6 +304,7 @@ impl From<Settings> for Options {
|
||||
classes: Some(settings.classes.into_iter().collect()),
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
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\n\nfrom typing import Any\n\nfrom my_first_party import my_first_party_object\nfrom requests import Session\n\nfrom . import my_local_folder_object\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
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\nfrom typing import Any\nfrom my_first_party import my_first_party_object\nfrom requests import Session\nfrom . import my_local_folder_object\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
Reference in New Issue
Block a user