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__)