diff --git a/crates/ruff_linter/resources/test/fixtures/isort/required_imports/docstring_followed_by_continuation.py b/crates/ruff_linter/resources/test/fixtures/isort/required_imports/docstring_followed_by_continuation.py new file mode 100644 index 0000000000..4426d86c7b --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/required_imports/docstring_followed_by_continuation.py @@ -0,0 +1,3 @@ +"""Hello, world!"""\ + +x = 1; y = 2 diff --git a/crates/ruff_linter/src/importer/insertion.rs b/crates/ruff_linter/src/importer/insertion.rs index f76bd0a383..af87958e11 100644 --- a/crates/ruff_linter/src/importer/insertion.rs +++ b/crates/ruff_linter/src/importer/insertion.rs @@ -56,13 +56,19 @@ impl<'a> Insertion<'a> { stylist: &Stylist, ) -> Insertion<'static> { // Skip over any docstrings. - let mut location = if let Some(location) = match_docstring_end(body) { + let mut location = if let Some(mut location) = match_docstring_end(body) { // If the first token after the docstring is a semicolon, insert after the semicolon as // an inline statement. if let Some(offset) = match_semicolon(locator.after(location)) { return Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";"); } + // If the first token after the docstring is a continuation character (i.e. "\"), advance + // an additional row to prevent inserting in the same logical line. + if match_continuation(locator.after(location)).is_some() { + location = locator.full_line_end(location); + } + // Otherwise, advance to the next row. locator.full_line_end(location) } else { @@ -363,6 +369,16 @@ mod tests { Insertion::own_line("", TextSize::from(20), "\n") ); + let contents = r#" +"""Hello, world!"""\ + +"# + .trim_start(); + assert_eq!( + insert(contents)?, + Insertion::own_line("", TextSize::from(22), "\n") + ); + let contents = r" x = 1 " diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index 04bfc55c71..0ad92d763b 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -794,6 +794,7 @@ mod tests { #[test_case(Path::new("comments_and_newlines.py"))] #[test_case(Path::new("docstring.py"))] #[test_case(Path::new("docstring.pyi"))] + #[test_case(Path::new("docstring_followed_by_continuation.py"))] #[test_case(Path::new("docstring_only.py"))] #[test_case(Path::new("docstring_with_continuation.py"))] #[test_case(Path::new("docstring_with_semicolon.py"))] @@ -828,6 +829,7 @@ mod tests { #[test_case(Path::new("comments_and_newlines.py"))] #[test_case(Path::new("docstring.py"))] #[test_case(Path::new("docstring.pyi"))] + #[test_case(Path::new("docstring_followed_by_continuation.py"))] #[test_case(Path::new("docstring_only.py"))] #[test_case(Path::new("docstring_with_continuation.py"))] #[test_case(Path::new("docstring_with_semicolon.py"))] diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_docstring_followed_by_continuation.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_docstring_followed_by_continuation.py.snap new file mode 100644 index 0000000000..56afca4d3b --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_docstring_followed_by_continuation.py.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +docstring_followed_by_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations` +ℹ Safe fix +1 1 | """Hello, world!"""\ +2 2 | + 3 |+from __future__ import annotations +3 4 | x = 1; y = 2 diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_with_alias_docstring_followed_by_continuation.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_with_alias_docstring_followed_by_continuation.py.snap new file mode 100644 index 0000000000..bfce198bae --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__required_import_with_alias_docstring_followed_by_continuation.py.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +docstring_followed_by_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` +ℹ Safe fix +1 1 | """Hello, world!"""\ +2 2 | + 3 |+from __future__ import annotations as _annotations +3 4 | x = 1; y = 2