Fix YTT201 for '!=' comparisons (#18293)

## Summary

Closes #18292.
This commit is contained in:
Charlie Marsh 2025-05-25 13:16:19 -04:00 committed by GitHub
parent 83a036960b
commit 14c3755445
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 20 additions and 9 deletions

View file

@ -52,17 +52,20 @@ impl Violation for SysVersionCmpStr3 {
/// ## What it does
/// Checks for equality comparisons against the major version returned by
/// `sys.version_info` (e.g., `sys.version_info[0] == 3`).
/// `sys.version_info` (e.g., `sys.version_info[0] == 3` or `sys.version_info[0] != 3`).
///
/// ## Why is this bad?
/// Using `sys.version_info[0] == 3` to verify that the major version is
/// Python 3 or greater will fail if the major version number is ever
/// incremented (e.g., to Python 4). This is likely unintended, as code
/// that uses this comparison is likely intended to be run on Python 2,
/// but would now run on Python 4 too.
/// but would now run on Python 4 too. Similarly, using `sys.version_info[0] != 3`
/// to check for Python 2 will also fail if the major version number is
/// incremented.
///
/// Instead, use `>=` to check if the major version number is 3 or greater,
/// to future-proof the code.
/// or `<` to check if the major version number is less than 3, to future-proof
/// the code.
///
/// ## Example
/// ```python
@ -88,12 +91,18 @@ impl Violation for SysVersionCmpStr3 {
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
#[derive(ViolationMetadata)]
pub(crate) struct SysVersionInfo0Eq3;
pub(crate) struct SysVersionInfo0Eq3 {
eq: bool,
}
impl Violation for SysVersionInfo0Eq3 {
#[derive_message_formats]
fn message(&self) -> String {
if self.eq {
"`sys.version_info[0] == 3` referenced (python4), use `>=`".to_string()
} else {
"`sys.version_info[0] != 3` referenced (python4), use `<`".to_string()
}
}
}
@ -235,7 +244,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
{
if *i == 0 {
if let (
[CmpOp::Eq | CmpOp::NotEq],
[operator @ (CmpOp::Eq | CmpOp::NotEq)],
[
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(n),
@ -246,7 +255,9 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators
{
if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) {
checker.report_diagnostic(Diagnostic::new(
SysVersionInfo0Eq3,
SysVersionInfo0Eq3 {
eq: matches!(*operator, CmpOp::Eq),
},
left.range(),
));
}

View file

@ -20,7 +20,7 @@ YTT201.py:8:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=`
10 | PY2 = version_info[0] != 3
|
YTT201.py:9:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=`
YTT201.py:9:7: YTT201 `sys.version_info[0] != 3` referenced (python4), use `<`
|
7 | PY3 = sys.version_info[0] == 3
8 | PY3 = version_info[0] == 3
@ -29,7 +29,7 @@ YTT201.py:9:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=`
10 | PY2 = version_info[0] != 3
|
YTT201.py:10:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=`
YTT201.py:10:7: YTT201 `sys.version_info[0] != 3` referenced (python4), use `<`
|
8 | PY3 = version_info[0] == 3
9 | PY2 = sys.version_info[0] != 3