Skip to content

Commit 994514d

Browse files
committed
Redirect PHG001 to S307 and PGH002 to G010 (#9756)
Follow-up to #9754 and #9689. Alternative to #9714. Replaces #7506 and #7507 Same ideas as #9755 Part of #8931
1 parent a578414 commit 994514d

16 files changed

+56
-233
lines changed

crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH001_0.py

-9
This file was deleted.

crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH001_1.py

-11
This file was deleted.

crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH002_0.py

-10
This file was deleted.

crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH002_1.py

-8
This file was deleted.

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ use crate::rules::{
1616
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
1717
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
1818
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_type_checking, flake8_use_pathlib,
19-
flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade,
20-
refurb, ruff,
19+
flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff,
2120
};
2221
use crate::settings::types::PythonVersion;
2322

@@ -773,12 +772,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
773772
if checker.enabled(Rule::CallDateFromtimestamp) {
774773
flake8_datetimez::rules::call_date_fromtimestamp(checker, func, expr.range());
775774
}
776-
if checker.enabled(Rule::Eval) {
777-
pygrep_hooks::rules::no_eval(checker, func);
778-
}
779-
if checker.enabled(Rule::DeprecatedLogWarn) {
780-
pygrep_hooks::rules::deprecated_log_warn(checker, call);
781-
}
782775
if checker.enabled(Rule::UnnecessaryDirectLambdaCall) {
783776
pylint::rules::unnecessary_direct_lambda_call(checker, expr, func);
784777
}

crates/ruff_linter/src/codes.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
705705
(Flake8Datetimez, "012") => (RuleGroup::Stable, rules::flake8_datetimez::rules::CallDateFromtimestamp),
706706

707707
// pygrep-hooks
708-
(PygrepHooks, "001") => (RuleGroup::Stable, rules::pygrep_hooks::rules::Eval),
709-
(PygrepHooks, "002") => (RuleGroup::Stable, rules::pygrep_hooks::rules::DeprecatedLogWarn),
708+
(PygrepHooks, "001") => (RuleGroup::Removed, rules::pygrep_hooks::rules::Eval),
709+
(PygrepHooks, "002") => (RuleGroup::Removed, rules::pygrep_hooks::rules::DeprecatedLogWarn),
710710
(PygrepHooks, "003") => (RuleGroup::Stable, rules::pygrep_hooks::rules::BlanketTypeIgnore),
711711
(PygrepHooks, "004") => (RuleGroup::Stable, rules::pygrep_hooks::rules::BlanketNOQA),
712712
(PygrepHooks, "005") => (RuleGroup::Stable, rules::pygrep_hooks::rules::InvalidMockAccess),

crates/ruff_linter/src/rule_redirects.rs

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
101101
("RUF011", "B035"),
102102
("TCH006", "TCH010"),
103103
("TRY200", "B904"),
104+
("PGH001", "S307"),
105+
("PHG002", "G010"),
104106
// Test redirect by exact code
105107
#[cfg(feature = "test-rules")]
106108
("RUF940", "RUF950"),

crates/ruff_linter/src/rule_selector.rs

+37-4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl FromStr for RuleSelector {
4848
type Err = ParseError;
4949

5050
fn from_str(s: &str) -> Result<Self, Self::Err> {
51+
// **Changes should be reflected in `parse_no_redirect` as well**
5152
match s {
5253
"ALL" => Ok(Self::All),
5354
#[allow(deprecated)]
@@ -67,7 +68,6 @@ impl FromStr for RuleSelector {
6768
return Ok(Self::Linter(linter));
6869
}
6970

70-
// Does the selector select a single rule?
7171
let prefix = RuleCodePrefix::parse(&linter, code)
7272
.map_err(|_| ParseError::Unknown(s.to_string()))?;
7373

@@ -254,8 +254,6 @@ pub struct PreviewOptions {
254254

255255
#[cfg(feature = "schemars")]
256256
mod schema {
257-
use std::str::FromStr;
258-
259257
use itertools::Itertools;
260258
use schemars::JsonSchema;
261259
use schemars::_serde_json::Value;
@@ -302,7 +300,7 @@ mod schema {
302300
.filter(|p| {
303301
// Exclude any prefixes where all of the rules are removed
304302
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
305-
RuleSelector::from_str(p)
303+
RuleSelector::parse_no_redirect(p)
306304
{
307305
!prefix.rules().all(|rule| rule.is_removed())
308306
} else {
@@ -341,6 +339,41 @@ impl RuleSelector {
341339
}
342340
}
343341
}
342+
343+
/// Parse [`RuleSelector`] from a string; but do not follow redirects.
344+
pub fn parse_no_redirect(s: &str) -> Result<Self, ParseError> {
345+
// **Changes should be reflected in `from_str` as well**
346+
match s {
347+
"ALL" => Ok(Self::All),
348+
#[allow(deprecated)]
349+
"NURSERY" => Ok(Self::Nursery),
350+
"C" => Ok(Self::C),
351+
"T" => Ok(Self::T),
352+
_ => {
353+
let (linter, code) =
354+
Linter::parse_code(s).ok_or_else(|| ParseError::Unknown(s.to_string()))?;
355+
356+
if code.is_empty() {
357+
return Ok(Self::Linter(linter));
358+
}
359+
360+
let prefix = RuleCodePrefix::parse(&linter, code)
361+
.map_err(|_| ParseError::Unknown(s.to_string()))?;
362+
363+
if is_single_rule_selector(&prefix) {
364+
Ok(Self::Rule {
365+
prefix,
366+
redirected_from: None,
367+
})
368+
} else {
369+
Ok(Self::Prefix {
370+
prefix,
371+
redirected_from: None,
372+
})
373+
}
374+
}
375+
}
376+
}
344377
}
345378

346379
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]

crates/ruff_linter/src/rules/pygrep_hooks/mod.rs

-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ mod tests {
1313
use crate::test::test_path;
1414
use crate::{assert_messages, settings};
1515

16-
#[test_case(Rule::Eval, Path::new("PGH001_0.py"))]
17-
#[test_case(Rule::Eval, Path::new("PGH001_1.py"))]
18-
#[test_case(Rule::DeprecatedLogWarn, Path::new("PGH002_0.py"))]
19-
#[test_case(Rule::DeprecatedLogWarn, Path::new("PGH002_1.py"))]
2016
#[test_case(Rule::BlanketTypeIgnore, Path::new("PGH003_0.py"))]
2117
#[test_case(Rule::BlanketTypeIgnore, Path::new("PGH003_1.py"))]
2218
#[test_case(Rule::BlanketNOQA, Path::new("PGH004_0.py"))]
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
1+
use ruff_diagnostics::{FixAvailability, Violation};
22
use ruff_macros::{derive_message_formats, violation};
3-
use ruff_python_ast::{self as ast, Expr};
4-
use ruff_python_semantic::analyze::logging;
5-
use ruff_python_stdlib::logging::LoggingLevel;
6-
use ruff_text_size::Ranged;
7-
8-
use crate::checkers::ast::Checker;
9-
use crate::importer::ImportRequest;
103

4+
/// ## Removed
5+
/// This rule is identical to [G010] which should be used instead.
6+
///
117
/// ## What it does
128
/// Check for usages of the deprecated `warn` method from the `logging` module.
139
///
@@ -34,9 +30,12 @@ use crate::importer::ImportRequest;
3430
///
3531
/// ## References
3632
/// - [Python documentation: `logger.Logger.warning`](https://docs.python.org/3/library/logging.html#logging.Logger.warning)
33+
///
34+
/// [G010]: https://docs.astral.sh/ruff/rules/logging-warn/
3735
#[violation]
3836
pub struct DeprecatedLogWarn;
3937

38+
/// PGH002
4039
impl Violation for DeprecatedLogWarn {
4140
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
4241

@@ -49,59 +48,3 @@ impl Violation for DeprecatedLogWarn {
4948
Some(format!("Replace with `warning`"))
5049
}
5150
}
52-
53-
/// PGH002
54-
pub(crate) fn deprecated_log_warn(checker: &mut Checker, call: &ast::ExprCall) {
55-
match call.func.as_ref() {
56-
Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
57-
if !logging::is_logger_candidate(
58-
&call.func,
59-
checker.semantic(),
60-
&checker.settings.logger_objects,
61-
) {
62-
return;
63-
}
64-
if !matches!(
65-
LoggingLevel::from_attribute(attr.as_str()),
66-
Some(LoggingLevel::Warn)
67-
) {
68-
return;
69-
}
70-
}
71-
Expr::Name(_) => {
72-
if !checker
73-
.semantic()
74-
.resolve_call_path(call.func.as_ref())
75-
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "warn"]))
76-
{
77-
return;
78-
}
79-
}
80-
_ => return,
81-
}
82-
83-
let mut diagnostic = Diagnostic::new(DeprecatedLogWarn, call.func.range());
84-
85-
match call.func.as_ref() {
86-
Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
87-
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
88-
"warning".to_string(),
89-
attr.range(),
90-
)));
91-
}
92-
Expr::Name(_) => {
93-
diagnostic.try_set_fix(|| {
94-
let (import_edit, binding) = checker.importer().get_or_import_symbol(
95-
&ImportRequest::import("logging", "warning"),
96-
call.start(),
97-
checker.semantic(),
98-
)?;
99-
let name_edit = Edit::range_replacement(binding, call.func.range());
100-
Ok(Fix::safe_edits(import_edit, [name_edit]))
101-
});
102-
}
103-
_ => {}
104-
}
105-
106-
checker.diagnostics.push(diagnostic);
107-
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
use ruff_python_ast::{self as ast, Expr};
2-
3-
use ruff_diagnostics::{Diagnostic, Violation};
1+
use ruff_diagnostics::Violation;
42
use ruff_macros::{derive_message_formats, violation};
5-
use ruff_text_size::Ranged;
6-
7-
use crate::checkers::ast::Checker;
83

4+
/// ## Removed
5+
/// This rule is identical to [S307] which should be used instead.
6+
///
97
/// ## What it does
108
/// Checks for uses of the builtin `eval()` function.
119
///
@@ -29,28 +27,15 @@ use crate::checkers::ast::Checker;
2927
/// ## References
3028
/// - [Python documentation: `eval`](https://docs.python.org/3/library/functions.html#eval)
3129
/// - [_Eval really is dangerous_ by Ned Batchelder](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html)
30+
///
31+
/// [S307]: https://docs.astral.sh/ruff/rules/suspicious-eval-usage/
3232
#[violation]
3333
pub struct Eval;
3434

35+
/// PGH001
3536
impl Violation for Eval {
3637
#[derive_message_formats]
3738
fn message(&self) -> String {
3839
format!("No builtin `eval()` allowed")
3940
}
4041
}
41-
42-
/// PGH001
43-
pub(crate) fn no_eval(checker: &mut Checker, func: &Expr) {
44-
let Expr::Name(ast::ExprName { id, .. }) = func else {
45-
return;
46-
};
47-
if id != "eval" {
48-
return;
49-
}
50-
if !checker.semantic().is_builtin("eval") {
51-
return;
52-
}
53-
checker
54-
.diagnostics
55-
.push(Diagnostic::new(Eval, func.range()));
56-
}

crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH001_PGH001_0.py.snap

-21
This file was deleted.

crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH001_PGH001_1.py.snap

-4
This file was deleted.

crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH002_PGH002_0.py.snap

-4
This file was deleted.

0 commit comments

Comments
 (0)