From 8f676f330968276819786a1e511d661591ba43a9 Mon Sep 17 00:00:00 2001
From: Steve C <diceroll123@gmail.com>
Date: Sun, 14 Jan 2024 21:44:13 -0500
Subject: [PATCH 1/5] [`pygrep_hooks`] - add autofix for `deprecated_log_warn`
 (`PGH002`)

---
 .../ruff_linter/src/rules/pygrep_hooks/mod.rs | 20 ++++++++++
 .../pygrep_hooks/rules/deprecated_log_warn.rs | 26 +++++++++++--
 ...grep_hooks__tests__PGH002_PGH002_1.py.snap |  3 ++
 ...s__tests__preview__PGH002_PGH002_1.py.snap | 39 +++++++++++++++++++
 4 files changed, 84 insertions(+), 4 deletions(-)
 create mode 100644 crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap

diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs b/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
index 5a64e2c53513f..ef1e12b2b8f56 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
@@ -9,6 +9,8 @@ mod tests {
     use test_case::test_case;
 
     use crate::registry::Rule;
+    use crate::settings::types::PreviewMode;
+    use crate::settings::LinterSettings;
     use crate::test::test_path;
     use crate::{assert_messages, settings};
 
@@ -29,4 +31,22 @@ mod tests {
         assert_messages!(snapshot, diagnostics);
         Ok(())
     }
+
+    #[test_case(Rule::DeprecatedLogWarn, Path::new("PGH002_1.py"))]
+    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("pygrep_hooks").join(path).as_path(),
+            &LinterSettings {
+                preview: PreviewMode::Enabled,
+                ..LinterSettings::for_rule(rule_code)
+            },
+        )?;
+        assert_messages!(snapshot, diagnostics);
+        Ok(())
+    }
 }
diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
index 547f1aa9f79c5..94d0ae2e3abda 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
@@ -1,7 +1,8 @@
+use ast::ExprAttribute;
 use ruff_python_ast::{self as ast, Expr, ExprCall};
 use ruff_python_semantic::analyze::logging;
 
-use ruff_diagnostics::{Diagnostic, Violation};
+use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
 use ruff_macros::{derive_message_formats, violation};
 use ruff_python_stdlib::logging::LoggingLevel;
 use ruff_text_size::Ranged;
@@ -38,10 +39,16 @@ use crate::checkers::ast::Checker;
 pub struct DeprecatedLogWarn;
 
 impl Violation for DeprecatedLogWarn {
+    const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
+
     #[derive_message_formats]
     fn message(&self) -> String {
         format!("`warn` is deprecated in favor of `warning`")
     }
+
+    fn fix_title(&self) -> Option<String> {
+        Some(format!("Replace with `warning`"))
+    }
 }
 
 /// PGH002
@@ -74,7 +81,18 @@ pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ExprCall) {
         _ => return,
     }
 
-    checker
-        .diagnostics
-        .push(Diagnostic::new(DeprecatedLogWarn, call.func.range()));
+    let mut diagnostic = Diagnostic::new(DeprecatedLogWarn, call.func.range());
+
+    if checker.settings.preview.is_enabled() {
+        let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() else {
+            return;
+        };
+
+        diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
+            "warning".to_string(),
+            attr.range(),
+        )));
+    }
+
+    checker.diagnostics.push(diagnostic);
 }
diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH002_PGH002_1.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH002_PGH002_1.py.snap
index df73d2858d097..203a3fa5ee715 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH002_PGH002_1.py.snap
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH002_PGH002_1.py.snap
@@ -9,6 +9,7 @@ PGH002_1.py:4:1: PGH002 `warn` is deprecated in favor of `warning`
   | ^^^^^^^^^^^^ PGH002
 5 | warn("not ok")
   |
+  = help: Replace with `warning`
 
 PGH002_1.py:5:1: PGH002 `warn` is deprecated in favor of `warning`
   |
@@ -18,6 +19,7 @@ PGH002_1.py:5:1: PGH002 `warn` is deprecated in favor of `warning`
 6 | 
 7 | logger = logging.getLogger(__name__)
   |
+  = help: Replace with `warning`
 
 PGH002_1.py:8:1: PGH002 `warn` is deprecated in favor of `warning`
   |
@@ -25,5 +27,6 @@ PGH002_1.py:8:1: PGH002 `warn` is deprecated in favor of `warning`
 8 | logger.warn("this is not ok")
   | ^^^^^^^^^^^ PGH002
   |
+  = help: Replace with `warning`
 
 
diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
new file mode 100644
index 0000000000000..e8d9cacbbe70e
--- /dev/null
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
@@ -0,0 +1,39 @@
+---
+source: crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
+---
+PGH002_1.py:4:1: PGH002 [*] `warn` is deprecated in favor of `warning`
+  |
+2 | from logging import warn
+3 | 
+4 | logging.warn("this is not ok")
+  | ^^^^^^^^^^^^ PGH002
+5 | warn("not ok")
+  |
+  = help: Replace with `warning`
+
+ℹ Safe fix
+1 1 | import logging
+2 2 | from logging import warn
+3 3 | 
+4   |-logging.warn("this is not ok")
+  4 |+logging.warning("this is not ok")
+5 5 | warn("not ok")
+6 6 | 
+7 7 | logger = logging.getLogger(__name__)
+
+PGH002_1.py:8:1: PGH002 [*] `warn` is deprecated in favor of `warning`
+  |
+7 | logger = logging.getLogger(__name__)
+8 | logger.warn("this is not ok")
+  | ^^^^^^^^^^^ PGH002
+  |
+  = help: Replace with `warning`
+
+ℹ Safe fix
+5 5 | warn("not ok")
+6 6 | 
+7 7 | logger = logging.getLogger(__name__)
+8   |-logger.warn("this is not ok")
+  8 |+logger.warning("this is not ok")
+
+

From 848d56fd76622ee0161a890e3af8cfad1efdb7b4 Mon Sep 17 00:00:00 2001
From: Charlie Marsh <charlie.r.marsh@gmail.com>
Date: Mon, 15 Jan 2024 21:29:11 -0500
Subject: [PATCH 2/5] Avoid return

---
 .../pygrep_hooks/rules/deprecated_log_warn.rs | 20 ++++++++-----------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
index 94d0ae2e3abda..d7ad64bf080ab 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
@@ -1,5 +1,5 @@
 use ast::ExprAttribute;
-use ruff_python_ast::{self as ast, Expr, ExprCall};
+use ruff_python_ast::{self as ast, Expr};
 use ruff_python_semantic::analyze::logging;
 
 use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
@@ -52,7 +52,7 @@ impl Violation for DeprecatedLogWarn {
 }
 
 /// PGH002
-pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ExprCall) {
+pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ast::ExprCall) {
     match call.func.as_ref() {
         Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
             if !logging::is_logger_candidate(
@@ -82,17 +82,13 @@ pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ExprCall) {
     }
 
     let mut diagnostic = Diagnostic::new(DeprecatedLogWarn, call.func.range());
-
     if checker.settings.preview.is_enabled() {
-        let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() else {
-            return;
-        };
-
-        diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
-            "warning".to_string(),
-            attr.range(),
-        )));
+        if let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() {
+            diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
+                "warning".to_string(),
+                attr.range(),
+            )));
+        }
     }
-
     checker.diagnostics.push(diagnostic);
 }

From 0cb4f5536159149ed990ac42abfc38c4042375a6 Mon Sep 17 00:00:00 2001
From: Charlie Marsh <charlie.r.marsh@gmail.com>
Date: Mon, 15 Jan 2024 21:43:14 -0500
Subject: [PATCH 3/5] Regenerate fixtures

---
 ...grep_hooks__tests__preview__PGH002_PGH002_1.py.snap | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
index e8d9cacbbe70e..ac08da9a39668 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
@@ -21,6 +21,16 @@ PGH002_1.py:4:1: PGH002 [*] `warn` is deprecated in favor of `warning`
 6 6 | 
 7 7 | logger = logging.getLogger(__name__)
 
+PGH002_1.py:5:1: PGH002 `warn` is deprecated in favor of `warning`
+  |
+4 | logging.warn("this is not ok")
+5 | warn("not ok")
+  | ^^^^ PGH002
+6 | 
+7 | logger = logging.getLogger(__name__)
+  |
+  = help: Replace with `warning`
+
 PGH002_1.py:8:1: PGH002 [*] `warn` is deprecated in favor of `warning`
   |
 7 | logger = logging.getLogger(__name__)

From 92290047879cca199316dd014644882b9ea7b473 Mon Sep 17 00:00:00 2001
From: Charlie Marsh <charlie.r.marsh@gmail.com>
Date: Mon, 15 Jan 2024 21:43:46 -0500
Subject: [PATCH 4/5] Fix imports

---
 .../src/rules/pygrep_hooks/rules/deprecated_log_warn.rs   | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
index d7ad64bf080ab..83047d190ffc5 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
@@ -1,9 +1,7 @@
-use ast::ExprAttribute;
-use ruff_python_ast::{self as ast, Expr};
-use ruff_python_semantic::analyze::logging;
-
 use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
 use ruff_macros::{derive_message_formats, violation};
+use ruff_python_ast::{self as ast, Expr};
+use ruff_python_semantic::analyze::logging;
 use ruff_python_stdlib::logging::LoggingLevel;
 use ruff_text_size::Ranged;
 
@@ -83,7 +81,7 @@ pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ast::ExprCall) {
 
     let mut diagnostic = Diagnostic::new(DeprecatedLogWarn, call.func.range());
     if checker.settings.preview.is_enabled() {
-        if let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() {
+        if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = call.func.as_ref() {
             diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
                 "warning".to_string(),
                 attr.range(),

From 6f853082fde83b2ed20bdce4e09f0214f1f4b15a Mon Sep 17 00:00:00 2001
From: Charlie Marsh <charlie.r.marsh@gmail.com>
Date: Mon, 15 Jan 2024 21:46:47 -0500
Subject: [PATCH 5/5] Fix names

---
 .../pygrep_hooks/rules/deprecated_log_warn.rs | 25 +++++++++++++++----
 ...s__tests__preview__PGH002_PGH002_1.py.snap | 12 ++++++++-
 2 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
index 83047d190ffc5..19f6df52f9986 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs
@@ -6,6 +6,7 @@ use ruff_python_stdlib::logging::LoggingLevel;
 use ruff_text_size::Ranged;
 
 use crate::checkers::ast::Checker;
+use crate::importer::ImportRequest;
 
 /// ## What it does
 /// Check for usages of the deprecated `warn` method from the `logging` module.
@@ -81,11 +82,25 @@ pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ast::ExprCall) {
 
     let mut diagnostic = Diagnostic::new(DeprecatedLogWarn, call.func.range());
     if checker.settings.preview.is_enabled() {
-        if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = call.func.as_ref() {
-            diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
-                "warning".to_string(),
-                attr.range(),
-            )));
+        match call.func.as_ref() {
+            Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
+                diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
+                    "warning".to_string(),
+                    attr.range(),
+                )));
+            }
+            Expr::Name(_) => {
+                diagnostic.try_set_fix(|| {
+                    let (import_edit, binding) = checker.importer().get_or_import_symbol(
+                        &ImportRequest::import("logging", "warning"),
+                        call.start(),
+                        checker.semantic(),
+                    )?;
+                    let name_edit = Edit::range_replacement(binding, call.func.range());
+                    Ok(Fix::safe_edits(import_edit, [name_edit]))
+                });
+            }
+            _ => {}
         }
     }
     checker.diagnostics.push(diagnostic);
diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
index ac08da9a39668..6c1c5f1f712be 100644
--- a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
+++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__preview__PGH002_PGH002_1.py.snap
@@ -21,7 +21,7 @@ PGH002_1.py:4:1: PGH002 [*] `warn` is deprecated in favor of `warning`
 6 6 | 
 7 7 | logger = logging.getLogger(__name__)
 
-PGH002_1.py:5:1: PGH002 `warn` is deprecated in favor of `warning`
+PGH002_1.py:5:1: PGH002 [*] `warn` is deprecated in favor of `warning`
   |
 4 | logging.warn("this is not ok")
 5 | warn("not ok")
@@ -31,6 +31,16 @@ PGH002_1.py:5:1: PGH002 `warn` is deprecated in favor of `warning`
   |
   = help: Replace with `warning`
 
+ℹ Safe fix
+2 2 | from logging import warn
+3 3 | 
+4 4 | logging.warn("this is not ok")
+5   |-warn("not ok")
+  5 |+logging.warning("not ok")
+6 6 | 
+7 7 | logger = logging.getLogger(__name__)
+8 8 | logger.warn("this is not ok")
+
 PGH002_1.py:8:1: PGH002 [*] `warn` is deprecated in favor of `warning`
   |
 7 | logger = logging.getLogger(__name__)