[isort] Add no-lines-before Option (#1955)

Closes https://github.com/charliermarsh/ruff/issues/1916.
This commit is contained in:
Maksudul Haque
2023-01-18 22:09:47 +06:00
committed by GitHub
parent b9c6cfc0ab
commit 9a3e525930
9 changed files with 146 additions and 3 deletions

View File

@@ -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

View 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

View File

@@ -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": [

View File

@@ -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,

View File

@@ -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(())
}
}

View File

@@ -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.

View File

@@ -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()),
}
}
}

View File

@@ -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: ~

View File

@@ -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: ~