From f2134a207a6b5e60c5887d3615015f332d3d80f3 Mon Sep 17 00:00:00 2001 From: Steve C Date: Mon, 5 Aug 2024 00:20:48 -0400 Subject: [PATCH 1/3] [`flake8-pyi`] - add autofix for `future-annotations-in-stub` (`PYI044`) --- .../test/fixtures/flake8_pyi/PYI044.pyi | 1 + .../rules/future_annotations_in_stub.rs | 31 ++++++++++++++++--- ..._flake8_pyi__tests__PYI044_PYI044.pyi.snap | 31 +++++++++++++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi index 18018deee63ad..d624ac8eae89d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi @@ -1,5 +1,6 @@ # Bad import. from __future__ import annotations # PYI044. +from __future__ import annotations, OtherThing # PYI044. # Good imports. from __future__ import Something diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 9d02dedb3d79d..85be8053d83f6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -1,9 +1,9 @@ use ruff_python_ast::StmtImportFrom; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; -use crate::checkers::ast::Checker; +use crate::{checkers::ast::Checker, fix}; /// ## What it does /// Checks for the presence of the `from __future__ import annotations` import @@ -21,10 +21,16 @@ use crate::checkers::ast::Checker; pub struct FutureAnnotationsInStub; impl Violation for FutureAnnotationsInStub { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { format!("`from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics") } + + fn fix_title(&self) -> Option { + Some("Remove `annotations` import from `__future__`".to_string()) + } } /// PYI044 @@ -37,9 +43,24 @@ pub(crate) fn from_future_import(checker: &mut Checker, target: &StmtImportFrom) } = target { if name == "__future__" && names.iter().any(|alias| &*alias.name == "annotations") { - checker - .diagnostics - .push(Diagnostic::new(FutureAnnotationsInStub, *range)); + let mut diagnostic = Diagnostic::new(FutureAnnotationsInStub, *range); + + let stmt = checker.semantic().current_statement(); + + diagnostic.try_set_fix(|| { + let edit = fix::edits::remove_unused_imports( + std::iter::once("annotations"), + stmt, + None, + checker.locator(), + checker.stylist(), + checker.indexer(), + )?; + + Ok(Fix::unsafe_edit(edit)) + }); + + checker.diagnostics.push(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap index b69b3048f7a0e..267ce9f0b30dc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap @@ -1,13 +1,38 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI044.pyi:2:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics +PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 -3 | -4 | # Good imports. +3 | from __future__ import annotations, OtherThing # PYI044. | + = help: Remove `annotations` import from `__future__` +ℹ Unsafe fix +1 1 | # Bad import. +2 |-from __future__ import annotations # PYI044. +3 2 | from __future__ import annotations, OtherThing # PYI044. +4 3 | +5 4 | # Good imports. +PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics + | +1 | # Bad import. +2 | from __future__ import annotations # PYI044. +3 | from __future__ import annotations, OtherThing # PYI044. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 +4 | +5 | # Good imports. + | + = help: Remove `annotations` import from `__future__` + +ℹ Unsafe fix +1 1 | # Bad import. +2 2 | from __future__ import annotations # PYI044. +3 |-from __future__ import annotations, OtherThing # PYI044. + 3 |+from __future__ import OtherThing # PYI044. +4 4 | +5 5 | # Good imports. +6 6 | from __future__ import Something From a4f19a33ded9576d85e6eb7ecc119a3e05787c7f Mon Sep 17 00:00:00 2001 From: Steve C Date: Mon, 5 Aug 2024 00:38:30 -0400 Subject: [PATCH 2/3] add fix to preview --- .../ruff_linter/src/rules/flake8_pyi/mod.rs | 20 +++++++++- .../rules/future_annotations_in_stub.rs | 26 +++++++------ ..._flake8_pyi__tests__PYI044_PYI044.pyi.snap | 20 +--------- ...yi__tests__preview__PYI044_PYI044.pyi.snap | 38 +++++++++++++++++++ 4 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 9f82720ef9bcc..3c700f684031d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -9,7 +9,7 @@ mod tests { use test_case::test_case; use crate::registry::Rule; - use crate::settings::types::PythonVersion; + use crate::settings::types::{PreviewMode, PythonVersion}; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -149,4 +149,22 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } + + #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "preview__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("flake8_pyi").join(path).as_path(), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 85be8053d83f6..afe58dfbf2a04 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -45,20 +45,22 @@ pub(crate) fn from_future_import(checker: &mut Checker, target: &StmtImportFrom) if name == "__future__" && names.iter().any(|alias| &*alias.name == "annotations") { let mut diagnostic = Diagnostic::new(FutureAnnotationsInStub, *range); - let stmt = checker.semantic().current_statement(); + if checker.settings.preview.is_enabled() { + let stmt = checker.semantic().current_statement(); - diagnostic.try_set_fix(|| { - let edit = fix::edits::remove_unused_imports( - std::iter::once("annotations"), - stmt, - None, - checker.locator(), - checker.stylist(), - checker.indexer(), - )?; + diagnostic.try_set_fix(|| { + let edit = fix::edits::remove_unused_imports( + std::iter::once("annotations"), + stmt, + None, + checker.locator(), + checker.stylist(), + checker.indexer(), + )?; - Ok(Fix::unsafe_edit(edit)) - }); + Ok(Fix::unsafe_edit(edit)) + }); + } checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap index 267ce9f0b30dc..4fc509140c670 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics +PYI044.pyi:2:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. @@ -10,14 +10,7 @@ PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in | = help: Remove `annotations` import from `__future__` -ℹ Unsafe fix -1 1 | # Bad import. -2 |-from __future__ import annotations # PYI044. -3 2 | from __future__ import annotations, OtherThing # PYI044. -4 3 | -5 4 | # Good imports. - -PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics +PYI044.pyi:3:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. @@ -27,12 +20,3 @@ PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in 5 | # Good imports. | = help: Remove `annotations` import from `__future__` - -ℹ Unsafe fix -1 1 | # Bad import. -2 2 | from __future__ import annotations # PYI044. -3 |-from __future__ import annotations, OtherThing # PYI044. - 3 |+from __future__ import OtherThing # PYI044. -4 4 | -5 5 | # Good imports. -6 6 | from __future__ import Something diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap new file mode 100644 index 0000000000000..267ce9f0b30dc --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap @@ -0,0 +1,38 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics + | +1 | # Bad import. +2 | from __future__ import annotations # PYI044. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 +3 | from __future__ import annotations, OtherThing # PYI044. + | + = help: Remove `annotations` import from `__future__` + +ℹ Unsafe fix +1 1 | # Bad import. +2 |-from __future__ import annotations # PYI044. +3 2 | from __future__ import annotations, OtherThing # PYI044. +4 3 | +5 4 | # Good imports. + +PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics + | +1 | # Bad import. +2 | from __future__ import annotations # PYI044. +3 | from __future__ import annotations, OtherThing # PYI044. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 +4 | +5 | # Good imports. + | + = help: Remove `annotations` import from `__future__` + +ℹ Unsafe fix +1 1 | # Bad import. +2 2 | from __future__ import annotations # PYI044. +3 |-from __future__ import annotations, OtherThing # PYI044. + 3 |+from __future__ import OtherThing # PYI044. +4 4 | +5 5 | # Good imports. +6 6 | from __future__ import Something From cb3495cc8a7c1592d7aff9076fd497684115c631 Mon Sep 17 00:00:00 2001 From: Steve C Date: Mon, 5 Aug 2024 19:25:43 -0400 Subject: [PATCH 3/3] convert to safe fix, change fix title, and use real imports --- .../test/fixtures/flake8_pyi/PYI044.pyi | 4 ++-- .../rules/future_annotations_in_stub.rs | 4 ++-- ..._flake8_pyi__tests__PYI044_PYI044.pyi.snap | 10 ++++----- ...yi__tests__preview__PYI044_PYI044.pyi.snap | 22 +++++++++---------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi index d624ac8eae89d..e03804ff4c3a6 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI044.pyi @@ -1,8 +1,8 @@ # Bad import. from __future__ import annotations # PYI044. -from __future__ import annotations, OtherThing # PYI044. +from __future__ import annotations, with_statement # PYI044. # Good imports. -from __future__ import Something +from __future__ import with_statement import sys from socket import AF_INET diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index afe58dfbf2a04..c3d50ceaea391 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -29,7 +29,7 @@ impl Violation for FutureAnnotationsInStub { } fn fix_title(&self) -> Option { - Some("Remove `annotations` import from `__future__`".to_string()) + Some("Remove `from __future__ import annotations`".to_string()) } } @@ -58,7 +58,7 @@ pub(crate) fn from_future_import(checker: &mut Checker, target: &StmtImportFrom) checker.indexer(), )?; - Ok(Fix::unsafe_edit(edit)) + Ok(Fix::safe_edit(edit)) }); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap index 4fc509140c670..94fa6d7a63b8a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap @@ -6,17 +6,17 @@ PYI044.pyi:2:1: PYI044 `from __future__ import annotations` has no effect in stu 1 | # Bad import. 2 | from __future__ import annotations # PYI044. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 -3 | from __future__ import annotations, OtherThing # PYI044. +3 | from __future__ import annotations, with_statement # PYI044. | - = help: Remove `annotations` import from `__future__` + = help: Remove `from __future__ import annotations` PYI044.pyi:3:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. -3 | from __future__ import annotations, OtherThing # PYI044. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 +3 | from __future__ import annotations, with_statement # PYI044. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 4 | 5 | # Good imports. | - = help: Remove `annotations` import from `__future__` + = help: Remove `from __future__ import annotations` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap index 267ce9f0b30dc..5ddf8be331d38 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap @@ -6,14 +6,14 @@ PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in 1 | # Bad import. 2 | from __future__ import annotations # PYI044. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 -3 | from __future__ import annotations, OtherThing # PYI044. +3 | from __future__ import annotations, with_statement # PYI044. | - = help: Remove `annotations` import from `__future__` + = help: Remove `from __future__ import annotations` -ℹ Unsafe fix +ℹ Safe fix 1 1 | # Bad import. 2 |-from __future__ import annotations # PYI044. -3 2 | from __future__ import annotations, OtherThing # PYI044. +3 2 | from __future__ import annotations, with_statement # PYI044. 4 3 | 5 4 | # Good imports. @@ -21,18 +21,18 @@ PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. -3 | from __future__ import annotations, OtherThing # PYI044. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 +3 | from __future__ import annotations, with_statement # PYI044. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 4 | 5 | # Good imports. | - = help: Remove `annotations` import from `__future__` + = help: Remove `from __future__ import annotations` -ℹ Unsafe fix +ℹ Safe fix 1 1 | # Bad import. 2 2 | from __future__ import annotations # PYI044. -3 |-from __future__ import annotations, OtherThing # PYI044. - 3 |+from __future__ import OtherThing # PYI044. +3 |-from __future__ import annotations, with_statement # PYI044. + 3 |+from __future__ import with_statement # PYI044. 4 4 | 5 5 | # Good imports. -6 6 | from __future__ import Something +6 6 | from __future__ import with_statement