diff --git a/crates/ruff/src/rules/flake8_2020/rules/compare.rs b/crates/ruff/src/rules/flake8_2020/rules/compare.rs index 1e083b48a1..55197df851 100644 --- a/crates/ruff/src/rules/flake8_2020/rules/compare.rs +++ b/crates/ruff/src/rules/flake8_2020/rules/compare.rs @@ -9,6 +9,38 @@ use crate::registry::Rule; use super::super::helpers::is_sys; +/// ## What it does +/// Checks for comparisons that test `sys.version` against string literals, +/// such that the comparison will evaluate to `False` on Python 3.10 or later. +/// +/// ## Why is this bad? +/// Comparing `sys.version` to a string is error-prone and may cause subtle +/// bugs, as the comparison will be performed lexicographically, not +/// semantically. For example, `sys.version > "3.9"` will evaluate to `False` +/// when using Python 3.10, as `"3.10"` is lexicographically "less" than +/// `"3.9"`. +/// +/// Instead, use `sys.version_info` to access the current major and minor +/// version numbers as a tuple, which can be compared to other tuples +/// without issue. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version > "3.9" # `False` on Python 3.10. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// sys.version_info > (3, 9) # `True` on Python 3.10. +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionCmpStr3; @@ -19,6 +51,43 @@ 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`). +/// +/// ## 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. +/// +/// Instead, use `>=` to check if the major version number is 3 or greater, +/// to future-proof the code. +/// +/// ## Example +/// ```python +/// import sys +/// +/// if sys.version_info[0] == 3: +/// ... +/// else: +/// print("Python 2") # This will be printed on Python 4. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// if sys.version_info >= (3,): +/// ... +/// else: +/// print("Python 2") # This will not be printed on Python 4. +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionInfo0Eq3; @@ -29,6 +98,36 @@ impl Violation for SysVersionInfo0Eq3 { } } +/// ## What it does +/// Checks for comparisons that test `sys.version_info[1]` against an integer. +/// +/// ## Why is this bad? +/// Comparisons based on the current minor version number alone can cause +/// subtle bugs and would likely lead to unintended effects if the Python +/// major version number were ever incremented (e.g., to Python 4). +/// +/// Instead, compare `sys.version_info` to a tuple, including the major and +/// minor version numbers, to future-proof the code. +/// +/// ## Example +/// ```python +/// import sys +/// +/// if sys.version_info[1] < 7: +/// print("Python 3.6 or earlier.") # This will be printed on Python 4.0. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// if sys.version_info < (3, 7): +/// print("Python 3.6 or earlier.") +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionInfo1CmpInt; @@ -42,6 +141,36 @@ impl Violation for SysVersionInfo1CmpInt { } } +/// ## What it does +/// Checks for comparisons that test `sys.version_info.minor` against an integer. +/// +/// ## Why is this bad? +/// Comparisons based on the current minor version number alone can cause +/// subtle bugs and would likely lead to unintended effects if the Python +/// major version number were ever incremented (e.g., to Python 4). +/// +/// Instead, compare `sys.version_info` to a tuple, including the major and +/// minor version numbers, to future-proof the code. +/// +/// ## Example +/// ```python +/// import sys +/// +/// if sys.version_info.minor < 7: +/// print("Python 3.6 or earlier.") # This will be printed on Python 4.0. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// if sys.version_info < (3, 7): +/// print("Python 3.6 or earlier.") +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionInfoMinorCmpInt; @@ -55,6 +184,37 @@ impl Violation for SysVersionInfoMinorCmpInt { } } +/// ## What it does +/// Checks for comparisons that test `sys.version` against string literals, +/// such that the comparison would fail if the major version number were +/// ever incremented to Python 10 or higher. +/// +/// ## Why is this bad? +/// Comparing `sys.version` to a string is error-prone and may cause subtle +/// bugs, as the comparison will be performed lexicographically, not +/// semantically. +/// +/// Instead, use `sys.version_info` to access the current major and minor +/// version numbers as a tuple, which can be compared to other tuples +/// without issue. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version >= "3" # `False` on Python 10. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// sys.version_info >= (3,) # `True` on Python 10. +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionCmpStr10; diff --git a/crates/ruff/src/rules/flake8_2020/rules/name_or_attribute.rs b/crates/ruff/src/rules/flake8_2020/rules/name_or_attribute.rs index 3ca122af52..47bdd31421 100644 --- a/crates/ruff/src/rules/flake8_2020/rules/name_or_attribute.rs +++ b/crates/ruff/src/rules/flake8_2020/rules/name_or_attribute.rs @@ -5,6 +5,35 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; +/// ## What it does +/// Checks for uses of `six.PY3`. +/// +/// ## Why is this bad? +/// `six.PY3` will evaluate to `False` on Python 4 and greater. This is likely +/// unintended, and may cause code intended to run on Python 2 to run on Python 4 +/// too. +/// +/// Instead, use `not six.PY2` to validate that the current Python major version is +/// _not_ equal to 2, to future-proof the code. +/// +/// ## Example +/// ```python +/// import six +/// +/// six.PY3 # `False` on Python 4. +/// ``` +/// +/// Use instead: +/// ```python +/// import six +/// +/// not six.PY2 # `True` on Python 4. +/// ``` +/// +/// ## References +/// - [PyPI: `six`](https://pypi.org/project/six/) +/// - [Six documentation: `six.PY2`](https://six.readthedocs.io/#six.PY2) +/// - [Six documentation: `six.PY3`](https://six.readthedocs.io/#six.PY3) #[violation] pub struct SixPY3; diff --git a/crates/ruff/src/rules/flake8_2020/rules/subscript.rs b/crates/ruff/src/rules/flake8_2020/rules/subscript.rs index 71d6768c86..0f18cfae68 100644 --- a/crates/ruff/src/rules/flake8_2020/rules/subscript.rs +++ b/crates/ruff/src/rules/flake8_2020/rules/subscript.rs @@ -8,6 +8,36 @@ use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::flake8_2020::helpers::is_sys; +/// ## What it does +/// Checks for uses of `sys.version[:3]`. +/// +/// ## Why is this bad? +/// If the current major or minor version consists of multiple digits, +/// `sys.version[:3]` will truncate the version number (e.g., `"3.10"` would +/// become `"3.1"`). This is likely unintended, and can lead to subtle bugs if +/// the version string is used to test against a specific Python version. +/// +/// Instead, use `sys.version_info` to access the current major and minor +/// version numbers as a tuple, which can be compared to other tuples +/// without issue. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version[:3] # Evaluates to "3.1" on Python 3.10. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// sys.version_info[:2] # Evaluates to (3, 10) on Python 3.10. +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionSlice3; @@ -18,6 +48,36 @@ impl Violation for SysVersionSlice3 { } } +/// ## What it does +/// Checks for uses of `sys.version[2]`. +/// +/// ## Why is this bad? +/// If the current major or minor version consists of multiple digits, +/// `sys.version[2]` will select the first digit of the minor number only +/// (e.g., `"3.10"` would evaluate to `"1"`). This is likely unintended, and +/// can lead to subtle bugs if the version is used to test against a minor +/// version number. +/// +/// Instead, use `sys.version_info.minor` to access the current minor version +/// number. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version[2] # Evaluates to "1" on Python 3.10. +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// f"{sys.version_info.minor}" # Evaluates to "10" on Python 3.10. +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersion2; @@ -28,6 +88,36 @@ impl Violation for SysVersion2 { } } +/// ## What it does +/// Checks for uses of `sys.version[0]`. +/// +/// ## Why is this bad? +/// If the current major or minor version consists of multiple digits, +/// `sys.version[0]` will select the first digit of the major version number +/// only (e.g., `"3.10"` would evaluate to `"1"`). This is likely unintended, +/// and can lead to subtle bugs if the version string is used to test against a +/// major version number. +/// +/// Instead, use `sys.version_info.major` to access the current major version +/// number. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version[0] # If using Python 10, this evaluates to "1". +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// f"{sys.version_info.major}" # If using Python 10, this evaluates to "10". +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersion0; @@ -38,6 +128,36 @@ impl Violation for SysVersion0 { } } +/// ## What it does +/// Checks for uses of `sys.version[:1]`. +/// +/// ## Why is this bad? +/// If the major version number consists of more than one digit, this will +/// select the first digit of the major version number only (e.g., `"10.0"` +/// would evaluate to `"1"`). This is likely unintended, and can lead to subtle +/// bugs in future versions of Python if the version string is used to test +/// against a specific major version number. +/// +/// Instead, use `sys.version_info.major` to access the current major version +/// number. +/// +/// ## Example +/// ```python +/// import sys +/// +/// sys.version[:1] # If using Python 10, this evaluates to "1". +/// ``` +/// +/// Use instead: +/// ```python +/// import sys +/// +/// f"{sys.version_info.major}" # If using Python 10, this evaluates to "10". +/// ``` +/// +/// ## References +/// - [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) #[violation] pub struct SysVersionSlice1;