From ba61bb6a6ccf94b46d9a79667fb009744a93d137 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 22 Feb 2023 21:35:53 +0200 Subject: [PATCH] Fix isort `no-lines-before` preceded by an empty section (#3139) Fix isort no-lines-before preceded by an empty section Fix #3138. --- .../no_lines_before_with_empty_sections.py | 3 ++ crates/ruff/src/rules/isort/categorize.rs | 3 +- crates/ruff/src/rules/isort/mod.rs | 44 +++++++++++++++++-- ...o_lines_before_with_empty_sections.py.snap | 22 ++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/isort/no_lines_before_with_empty_sections.py create mode 100644 crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__no_lines_before_with_empty_sections.py_no_lines_before_with_empty_sections.py.snap diff --git a/crates/ruff/resources/test/fixtures/isort/no_lines_before_with_empty_sections.py b/crates/ruff/resources/test/fixtures/isort/no_lines_before_with_empty_sections.py new file mode 100644 index 0000000000000..f26196fa30a43 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/isort/no_lines_before_with_empty_sections.py @@ -0,0 +1,3 @@ +from __future__ import annotations +from typing import Any +from . import my_local_folder_object diff --git a/crates/ruff/src/rules/isort/categorize.rs b/crates/ruff/src/rules/isort/categorize.rs index b2fd90cf1b763..114f0439e0f3b 100644 --- a/crates/ruff/src/rules/isort/categorize.rs +++ b/crates/ruff/src/rules/isort/categorize.rs @@ -6,12 +6,13 @@ use log::debug; use ruff_python::sys::KNOWN_STANDARD_LIBRARY; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use strum_macros::EnumIter; use super::types::{ImportBlock, Importable}; use crate::settings::types::PythonVersion; #[derive( - Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema, Hash, + Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema, Hash, EnumIter, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub enum ImportType { diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index 1089f6a6ae006..93ed32ccb6217 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -12,6 +12,7 @@ use normalize::normalize_imports; use order::order_imports; use settings::RelativeImportsOrder; use sorting::cmp_either_import; +use strum::IntoEnumIterator; use track::{Block, Trailer}; use types::EitherImport::{Import, ImportFrom}; use types::{AliasData, CommentSet, EitherImport, OrderedImportBlock, TrailingComma}; @@ -232,7 +233,7 @@ fn format_import_block( target_version: PythonVersion, ) -> String { // Categorize by type (e.g., first-party vs. third-party). - let block_by_type = categorize_imports( + let mut block_by_type = categorize_imports( block, src, package, @@ -247,7 +248,17 @@ fn format_import_block( // Generate replacement source code. let mut is_first_block = true; - for (import_type, import_block) in block_by_type { + let mut pending_lines_before = false; + for import_type in ImportType::iter() { + let import_block = block_by_type.remove(&import_type); + + if !no_lines_before.contains(&import_type) { + pending_lines_before = true; + } + let Some(import_block) = import_block else { + continue; + }; + let mut imports = order_imports( import_block, order_by_type, @@ -280,8 +291,10 @@ fn format_import_block( // Add a blank line between every section. if is_first_block { is_first_block = false; - } else if !no_lines_before.contains(&import_type) { + pending_lines_before = false; + } else if pending_lines_before { output.push_str(stylist.line_ending()); + pending_lines_before = false; } let mut lines_inserted = false; @@ -791,6 +804,31 @@ mod tests { Ok(()) } + #[test_case(Path::new("no_lines_before_with_empty_sections.py"))] + fn no_lines_before_with_empty_sections(path: &Path) -> Result<()> { + let snapshot = format!( + "no_lines_before_with_empty_sections.py_{}", + path.to_string_lossy() + ); + let mut diagnostics = test_path( + Path::new("isort").join(path).as_path(), + &Settings { + isort: super::settings::Settings { + no_lines_before: BTreeSet::from([ + ImportType::StandardLibrary, + ImportType::LocalFolder, + ]), + ..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(()) + } + #[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"))] diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__no_lines_before_with_empty_sections.py_no_lines_before_with_empty_sections.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__no_lines_before_with_empty_sections.py_no_lines_before_with_empty_sections.py.snap new file mode 100644 index 0000000000000..40fddb14bc44d --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__no_lines_before_with_empty_sections.py_no_lines_before_with_empty_sections.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +expression: diagnostics +--- +- kind: + UnsortedImports: ~ + location: + row: 1 + column: 0 + end_location: + row: 4 + column: 0 + fix: + content: "from __future__ import annotations\nfrom typing import Any\n\nfrom . import my_local_folder_object\n" + location: + row: 1 + column: 0 + end_location: + row: 4 + column: 0 + parent: ~ +