diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index bcc2f1d9a9fe0c..be25a75ca7dae3 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -777,6 +777,35 @@ mod tests { Ok(()) } + #[test_case(Path::new("comment.py"))] + #[test_case(Path::new("docstring.py"))] + #[test_case(Path::new("docstring.pyi"))] + #[test_case(Path::new("docstring_only.py"))] + #[test_case(Path::new("docstring_with_continuation.py"))] + #[test_case(Path::new("docstring_with_semicolon.py"))] + #[test_case(Path::new("empty.py"))] + #[test_case(Path::new("existing_import.py"))] + #[test_case(Path::new("multiline_docstring.py"))] + #[test_case(Path::new("off.py"))] + fn required_import_with_alias(path: &Path) -> Result<()> { + let snapshot = format!("required_import_with_alias_{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("isort/required_imports").join(path).as_path(), + &Settings { + src: vec![test_resource_path("fixtures/isort")], + isort: super::settings::Settings { + required_imports: BTreeSet::from([ + "from __future__ import annotations as _annotations".to_string(), + ]), + ..super::settings::Settings::default() + }, + ..Settings::for_rule(Rule::MissingRequiredImport) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test_case(Path::new("docstring.py"))] #[test_case(Path::new("docstring.pyi"))] #[test_case(Path::new("docstring_only.py"))] diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_comment.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_comment.py.snap new file mode 100644 index 00000000000000..98c8abf237c064 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_comment.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +comment.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | #!/usr/bin/env python3 + | I002 +2 | +3 | x = 1 + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 1 | #!/usr/bin/env python3 + 2 |+from __future__ import annotations as _annotations +2 3 | +3 4 | x = 1 + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.py.snap new file mode 100644 index 00000000000000..3937d80bedb9a2 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.py.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +docstring.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | """Hello, world!""" + | I002 +2 | +3 | x = 1 + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 1 | """Hello, world!""" + 2 |+from __future__ import annotations as _annotations +2 3 | +3 4 | x = 1 + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.pyi.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.pyi.snap new file mode 100644 index 00000000000000..94aa1559b8bb63 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring.pyi.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_only.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_only.py.snap new file mode 100644 index 00000000000000..94aa1559b8bb63 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_only.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_continuation.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_continuation.py.snap new file mode 100644 index 00000000000000..2d0057caa702b2 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_continuation.py.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +docstring_with_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | """Hello, world!"""; x = \ + | I002 +2 | 1; y = 2 + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 |-"""Hello, world!"""; x = \ + 1 |+"""Hello, world!"""; from __future__ import annotations as _annotations; x = \ +2 2 | 1; y = 2 + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_semicolon.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_semicolon.py.snap new file mode 100644 index 00000000000000..610a7d7c629ee0 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_docstring_with_semicolon.py.snap @@ -0,0 +1,15 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +docstring_with_semicolon.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | """Hello, world!"""; x = 1 + | I002 + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 |-"""Hello, world!"""; x = 1 + 1 |+"""Hello, world!"""; from __future__ import annotations as _annotations; x = 1 + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_empty.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_empty.py.snap new file mode 100644 index 00000000000000..94aa1559b8bb63 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_empty.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_existing_import.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_existing_import.py.snap new file mode 100644 index 00000000000000..345ff07159d3b3 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_existing_import.py.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +existing_import.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | from __future__ import generator_stop + | I002 +2 | import os + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix + 1 |+from __future__ import annotations as _annotations +1 2 | from __future__ import generator_stop +2 3 | import os + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_multiline_docstring.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_multiline_docstring.py.snap new file mode 100644 index 00000000000000..5289840be47450 --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_multiline_docstring.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +multiline_docstring.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | """a + | I002 +2 | b""" +3 | # b + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 1 | """a +2 2 | b""" +3 3 | # b + 4 |+from __future__ import annotations as _annotations +4 5 | import os + + diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_off.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_off.py.snap new file mode 100644 index 00000000000000..617305a4bee57a --- /dev/null +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__required_import_with_alias_off.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff/src/rules/isort/mod.rs +--- +off.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations` + | +1 | # isort: off + | I002 +2 | +3 | x = 1 + | + = help: Insert required import: `from future import annotations as _annotations` + +ℹ Fix +1 1 | # isort: off + 2 |+from __future__ import annotations as _annotations +2 3 | +3 4 | x = 1 +4 5 | # isort: on + + diff --git a/crates/ruff_python_ast/src/imports.rs b/crates/ruff_python_ast/src/imports.rs index 6adfb9fce93773..1f9769b307f7f1 100644 --- a/crates/ruff_python_ast/src/imports.rs +++ b/crates/ruff_python_ast/src/imports.rs @@ -85,6 +85,9 @@ impl std::fmt::Display for ImportFrom<'_> { write!(f, "{module}")?; } write!(f, " import {}", self.name.name)?; + if let Some(as_name) = self.name.as_name { + write!(f, " as {as_name}")?; + } Ok(()) } }