From ccbc863960466435be0ea5bf1579d8e4db447e78 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Thu, 15 Jun 2023 00:43:12 +0100 Subject: [PATCH] Complete `pyupgrade` documentation (#5096) ## Summary Completes the documentation for the `pyupgrade` rules. Related to #2646. ## Test Plan `python scripts/check_docs_formatted.py` --- ...convert_named_tuple_functional_to_class.rs | 30 +++++++++++++ .../convert_typed_dict_functional_to_class.rs | 29 +++++++++++++ .../pyupgrade/rules/datetime_utc_alias.rs | 23 ++++++++++ .../rules/deprecated_c_element_tree.rs | 19 ++++++++ .../pyupgrade/rules/deprecated_import.rs | 17 ++++++++ .../pyupgrade/rules/deprecated_mock_import.rs | 22 ++++++++++ .../rules/deprecated_unittest_alias.rs | 30 +++++++++++++ .../pyupgrade/rules/extraneous_parentheses.rs | 16 +++++++ .../src/rules/pyupgrade/rules/f_strings.rs | 19 ++++++++ .../rules/pyupgrade/rules/format_literals.rs | 24 +++++++++++ .../rules/lru_cache_with_maxsize_none.rs | 30 +++++++++++++ .../rules/lru_cache_without_parameters.rs | 30 +++++++++++++ .../rules/pyupgrade/rules/native_literals.rs | 20 +++++++++ .../src/rules/pyupgrade/rules/open_alias.rs | 25 ++++++++++- .../rules/pyupgrade/rules/os_error_alias.rs | 25 +++++++++++ .../pyupgrade/rules/outdated_version_block.rs | 28 ++++++++++++ .../rules/printf_string_formatting.rs | 22 ++++++++++ .../pyupgrade/rules/quoted_annotation.rs | 30 +++++++++++++ .../pyupgrade/rules/redundant_open_modes.rs | 21 +++++++++ .../pyupgrade/rules/replace_stdout_stderr.rs | 29 ++++++++++++- .../rules/replace_universal_newlines.rs | 27 ++++++++++++ .../rules/super_call_with_parameters.rs | 38 ++++++++++++++++ .../pyupgrade/rules/type_of_primitive.rs | 21 +++++++++ .../pyupgrade/rules/typing_text_str_alias.rs | 43 +++++++++++++++---- .../pyupgrade/rules/unicode_kind_prefix.rs | 38 +++++++++++----- .../rules/unnecessary_builtin_import.rs | 21 +++++++++ .../rules/unnecessary_coding_comment.rs | 20 ++++++++- .../rules/unnecessary_encode_utf8.rs | 19 ++++++++ .../rules/unnecessary_future_import.rs | 24 +++++++++++ .../rules/unpacked_list_comprehension.rs | 21 +++++++++ .../pyupgrade/rules/use_pep585_annotation.rs | 27 ++++++++++++ .../pyupgrade/rules/use_pep604_annotation.rs | 21 +++++++++ .../pyupgrade/rules/use_pep604_isinstance.rs | 24 +++++++++++ .../pyupgrade/rules/useless_metaclass_type.rs | 20 +++++++++ .../rules/useless_object_inheritance.rs | 21 +++++++++ .../pyupgrade/rules/yield_in_for_loop.rs | 21 +++++++++ ...ff__rules__pyupgrade__tests__UP019.py.snap | 8 ++-- ...ff__rules__pyupgrade__tests__UP022.py.snap | 14 +++--- ...ff__rules__pyupgrade__tests__UP025.py.snap | 24 +++++------ scripts/check_docs_formatted.py | 1 + 40 files changed, 896 insertions(+), 46 deletions(-) diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index 88da8b51f1..9e83d864cc 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -13,6 +13,36 @@ use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for `NamedTuple` declarations that use functional syntax. +/// +/// ## Why is this bad? +/// `NamedTuple` subclasses can be defined either through a functional syntax +/// (`Foo = NamedTuple(...)`) or a class syntax (`class Foo(NamedTuple): ...`). +/// +/// The class syntax is more readable and generally preferred over the +/// functional syntax, which exists primarily for backwards compatibility +/// with `collections.namedtuple`. +/// +/// ## Example +/// ```python +/// from typing import NamedTuple +/// +/// Foo = NamedTuple("Foo", [("a", int), ("b", str)]) +/// ``` +/// +/// Use instead: +/// ```python +/// from typing import NamedTuple +/// +/// +/// class Foo(NamedTuple): +/// a: int +/// b: str +/// ``` +/// +/// ## References +/// - [Python documentation: `typing.NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple) #[violation] pub struct ConvertNamedTupleFunctionalToClass { name: String, diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index cd56786e62..c1d2f5c58d 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -13,6 +13,35 @@ use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for `TypedDict` declarations that use functional syntax. +/// +/// ## Why is this bad? +/// `TypedDict` subclasses can be defined either through a functional syntax +/// (`Foo = TypedDict(...)`) or a class syntax (`class Foo(TypedDict): ...`). +/// +/// The class syntax is more readable and generally preferred over the +/// functional syntax. +/// +/// ## Example +/// ```python +/// from typing import TypedDict +/// +/// Foo = TypedDict("Foo", {"a": int, "b": str}) +/// ``` +/// +/// Use instead: +/// ```python +/// from typing import TypedDict +/// +/// +/// class Foo(TypedDict): +/// a: int +/// b: str +/// ``` +/// +/// ## References +/// - [Python documentation: `typing.TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) #[violation] pub struct ConvertTypedDictFunctionalToClass { name: String, diff --git a/crates/ruff/src/rules/pyupgrade/rules/datetime_utc_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/datetime_utc_alias.rs index 5083269d1d..b9750fe7ac 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/datetime_utc_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/datetime_utc_alias.rs @@ -7,6 +7,29 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `datetime.timezone.utc`. +/// +/// ## Why is this bad? +/// As of Python 3.11, `datetime.UTC` is an alias for `datetime.timezone.utc`. +/// The alias is more readable and generally preferred over the full path. +/// +/// ## Example +/// ```python +/// import datetime +/// +/// datetime.timezone.utc +/// ``` +/// +/// Use instead: +/// ```python +/// import datetime +/// +/// datetime.UTC +/// ``` +/// +/// ## References +/// - [Python documentation: `datetime.UTC`](https://docs.python.org/3/library/datetime.html#datetime.UTC) #[violation] pub struct DatetimeTimezoneUTC; diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs index dddc5b328f..0b5a075100 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs @@ -6,6 +6,25 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of the `xml.etree.cElementTree` module. +/// +/// ## Why is this bad? +/// In Python 3.3, `xml.etree.cElementTree` was deprecated in favor of +/// `xml.etree.ElementTree`. +/// +/// ## Example +/// ```python +/// from xml.etree import cElementTree +/// ``` +/// +/// Use instead: +/// ```python +/// from xml.etree import ElementTree +/// ``` +/// +/// ## References +/// - [Python documentation: `xml.etree.ElementTree`](https://docs.python.org/3/library/xml.etree.elementtree.html) #[violation] pub struct DeprecatedCElementTree; diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs index b2c5c62394..9f7421be15 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_import.rs @@ -35,6 +35,23 @@ enum Deprecation { WithoutRename(WithoutRename), } +/// ## What it does +/// Checks for uses of deprecated imports based on the minimum supported +/// Python version. +/// +/// ## Why is this bad? +/// Deprecated imports may be removed in future versions of Python, and +/// should be replaced with their new equivalents. +/// +/// ## Example +/// ```python +/// from collections import Sequence +/// ``` +/// +/// Use instead: +/// ```python +/// from collections.abc import Sequence +/// ``` #[violation] pub struct DeprecatedImport { deprecation: Deprecation, diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_mock_import.rs index 0c85c8f47b..68c3a20ab1 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -23,6 +23,28 @@ pub(crate) enum MockReference { Attribute, } +/// ## What it does +/// Checks for imports of the `mock` module that should be replaced with +/// `unittest.mock`. +/// +/// ## Why is this bad? +/// Since Python 3.3, `mock` has been a part of the standard library as +/// `unittest.mock`. The `mock` package is deprecated; use `unittest.mock` +/// instead. +/// +/// ## Example +/// ```python +/// import mock +/// ``` +/// +/// Use instead: +/// ```python +/// from unittest import mock +/// ``` +/// +/// ## References +/// - [Python documentation: `unittest.mock`](https://docs.python.org/3/library/unittest.mock.html) +/// - [PyPI: `mock`](https://pypi.org/project/mock/) #[violation] pub struct DeprecatedMockImport { reference_type: MockReference, diff --git a/crates/ruff/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs index e73b6a1efc..19cc009f00 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs @@ -8,6 +8,36 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of deprecated methods from the `unittest` module. +/// +/// ## Why is this bad? +/// The `unittest` module has deprecated aliases for some of its methods. +/// The aliases may be removed in future versions of Python. Instead, +/// use their non-deprecated counterparts. +/// +/// ## Example +/// ```python +/// from unittest import TestCase +/// +/// +/// class SomeTest(TestCase): +/// def test_something(self): +/// self.assertEquals(1, 1) +/// ``` +/// +/// Use instead: +/// ```python +/// from unittest import TestCase +/// +/// +/// class SomeTest(TestCase): +/// def test_something(self): +/// self.assertEqual(1, 1) +/// ``` +/// +/// ## References +/// - [Python documentation: Deprecated aliases](https://docs.python.org/3/library/unittest.html#deprecated-aliases) #[violation] pub struct DeprecatedUnittestAlias { alias: String, diff --git a/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs index 25884db60b..4eb02acef1 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -9,6 +9,22 @@ use ruff_python_ast::source_code::Locator; use crate::registry::Rule; use crate::settings::Settings; +/// ## What it does +/// Checks for extraneous parentheses. +/// +/// ## Why is this bad? +/// Extraneous parentheses are redundant, and can be removed to improve +/// readability while retaining identical semantics. +/// +/// ## Example +/// ```python +/// print(("Hello, world")) +/// ``` +/// +/// Use instead: +/// ```python +/// print("Hello, world") +/// ``` #[violation] pub struct ExtraneousParentheses; diff --git a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs index 2c23330d2d..896556ed04 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs @@ -17,6 +17,25 @@ use crate::registry::AsRule; use crate::rules::pyflakes::format::FormatSummary; use crate::rules::pyupgrade::helpers::curly_escape; +/// ## What it does +/// Checks for `str#format` calls that can be replaced with f-strings. +/// +/// ## Why is this bad? +/// f-strings are more readable and generally preferred over `str#format` +/// calls. +/// +/// ## Example +/// ```python +/// "{}".format(foo) +/// ``` +/// +/// Use instead: +/// ```python +/// f"{foo}" +/// ``` +/// +/// ## References +/// - [Python documentation: f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) #[violation] pub struct FString; diff --git a/crates/ruff/src/rules/pyupgrade/rules/format_literals.rs b/crates/ruff/src/rules/pyupgrade/rules/format_literals.rs index dde4279aab..a15974eb94 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/format_literals.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/format_literals.rs @@ -14,6 +14,30 @@ use crate::cst::matchers::{match_attribute, match_call_mut, match_expression}; use crate::registry::AsRule; use crate::rules::pyflakes::format::FormatSummary; +/// ## What it does +/// Checks for unnecessary positional indices in format strings. +/// +/// ## Why is this bad? +/// In Python 3.1 and later, format strings can use implicit positional +/// references. For example, `"{0}, {1}".format("Hello", "World")` can be +/// rewritten as `"{}, {}".format("Hello", "World")`. +/// +/// If the positional indices appear exactly in-order, they can be omitted +/// in favor of automatic indices to improve readability. +/// +/// ## Example +/// ```python +/// "{0}, {1}".format("Hello", "World") # "Hello, World" +/// ``` +/// +/// Use instead: +/// ```python +/// "{}, {}".format("Hello", "World") # "Hello, World" +/// ``` +/// +/// ## References +/// - [Python documentation: Format String Syntax](https://docs.python.org/3/library/string.html#format-string-syntax) +/// - [Python documentation: `str.format`](https://docs.python.org/3/library/stdtypes.html#str.format) #[violation] pub struct FormatLiterals; diff --git a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs index 7be2761a0e..497e1bdf6d 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs @@ -8,6 +8,36 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `functools.lru_cache` that set `maxsize=None`. +/// +/// ## Why is this bad? +/// Since Python 3.9, `functools.cache` can be used as a drop-in replacement +/// for `functools.lru_cache(maxsize=None)`. When possible, prefer +/// `functools.cache` as it is more readable and idiomatic. +/// +/// ## Example +/// ```python +/// import functools +/// +/// +/// @functools.lru_cache(maxsize=None) +/// def foo(): +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// import functools +/// +/// +/// @functools.cache +/// def foo(): +/// ... +/// ``` +/// +/// ## References +/// - [Python documentation: `@functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache) #[violation] pub struct LRUCacheWithMaxsizeNone; diff --git a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index 5088b3398c..07cb2999b5 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -7,6 +7,36 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for unnecessary parentheses on `functools.lru_cache` decorators. +/// +/// ## Why is this bad? +/// Since Python 3.8, `functools.lru_cache` can be used as a decorator without +/// trailing parentheses, as long as no arguments are passed to it. +/// +/// ## Example +/// ```python +/// import functools +/// +/// +/// @functools.lru_cache() +/// def foo(): +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// import functools +/// +/// +/// @functools.lru_cache +/// def foo(): +/// ... +/// ``` +/// +/// ## References +/// - [Python documentation: `@functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache) +/// - [Let lru_cache be used as a decorator with no arguments](https://github.com/python/cpython/issues/80953) #[violation] pub struct LRUCacheWithoutParameters; diff --git a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs index aac0f59c0d..e62888b9b9 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs @@ -24,6 +24,26 @@ impl fmt::Display for LiteralType { } } +/// ## What it does +/// Checks for unnecessary calls to `str` and `bytes`. +/// +/// ## Why is this bad? +/// The `str` and `bytes` constructors can be replaced with string and bytes +/// literals, which are more readable and idiomatic. +/// +/// ## Example +/// ```python +/// str("foo") +/// ``` +/// +/// Use instead: +/// ```python +/// "foo" +/// ``` +/// +/// ## References +/// - [Python documentation: `str`](https://docs.python.org/3/library/stdtypes.html#str) +/// - [Python documentation: `bytes`](https://docs.python.org/3/library/stdtypes.html#bytes) #[violation] pub struct NativeLiterals { literal_type: LiteralType, diff --git a/crates/ruff/src/rules/pyupgrade/rules/open_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/open_alias.rs index 584602d996..259615755a 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/open_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/open_alias.rs @@ -6,6 +6,29 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `io.open`. +/// +/// ## Why is this bad? +/// In Python 3, `io.open` is an alias for `open`. Prefer using `open` directly, +/// as it is more idiomatic. +/// +/// ## Example +/// ```python +/// import io +/// +/// with io.open("file.txt") as f: +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// with open("file.txt") as f: +/// ... +/// ``` +/// +/// ## References +/// - [Python documentation: `io.open`](https://docs.python.org/3/library/io.html#io.open) #[violation] pub struct OpenAlias; @@ -33,7 +56,7 @@ pub(crate) fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) { { let mut diagnostic = Diagnostic::new(OpenAlias, expr.range()); if checker.patch(diagnostic.kind.rule()) { - if checker.semantic().is_available("open") { + if checker.semantic().is_builtin("open") { diagnostic.set_fix(Fix::suggested(Edit::range_replacement( "open".to_string(), func.range(), diff --git a/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs index 666030fada..5068d07348 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs @@ -9,6 +9,31 @@ use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of exceptions that alias `OSError`. +/// +/// ## Why is this bad? +/// `OSError` is the builtin error type used for exceptions that relate to the +/// operating system. +/// +/// In Python 3.3, a variety of other exceptions, like `WindowsError` were +/// aliased to `OSError`. These aliases remain in place for compatibility with +/// older versions of Python, but may be removed in future versions. +/// +/// Prefer using `OSError` directly, as it is more idiomatic and future-proof. +/// +/// ## Example +/// ```python +/// raise IOError +/// ``` +/// +/// Use instead: +/// ```python +/// raise OSError +/// ``` +/// +/// ## References +/// - [Python documentation: `OSError`](https://docs.python.org/3/library/exceptions.html#OSError) #[violation] pub struct OSErrorAlias { pub name: Option, diff --git a/crates/ruff/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff/src/rules/pyupgrade/rules/outdated_version_block.rs index b0bb74a37f..6f40f2786c 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -16,6 +16,34 @@ use crate::registry::AsRule; use crate::rules::pyupgrade::fixes::adjust_indentation; use crate::settings::types::PythonVersion; +/// ## What it does +/// Checks for conditional blocks gated on `sys.version_info` comparisons +/// that are outdated for the minimum supported Python version. +/// +/// ## Why is this bad? +/// In Python, code can be conditionally executed based on the active +/// Python version by comparing against the `sys.version_info` tuple. +/// +/// If a code block is only executed for Python versions older than the +/// minimum supported version, it should be removed. +/// +/// ## Example +/// ```python +/// import sys +/// +/// if sys.version_info < (3, 0): +/// print("py2") +/// else: +/// print("py3") +/// ``` +/// +/// Use instead: +/// ```python +/// print("py3") +/// ``` +/// +/// ## References +/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info) #[violation] pub struct OutdatedVersionBlock; diff --git a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs index 5bcf6c764d..56008adfc4 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -18,6 +18,28 @@ use crate::checkers::ast::Checker; use crate::registry::AsRule; use crate::rules::pyupgrade::helpers::curly_escape; +/// ## What it does +/// Checks for `printf`-style string formatting. +/// +/// ## Why is this bad? +/// `printf`-style string formatting has a number of quirks, and leads to less +/// readable code than using `str.format` calls or f-strings. In general, prefer +/// the newer `str.format` and f-strings constructs over `printf`-style string +/// formatting. +/// +/// ## Example +/// ```python +/// "%s, %s" % ("Hello", "World") # "Hello, World" +/// ``` +/// +/// Use instead: +/// ```python +/// "{}, {}".format("Hello", "World") # "Hello, World" +/// ``` +/// +/// ## References +/// - [Python documentation: `printf`-style String Formatting](https://docs.python.org/3/library/stdtypes.html#old-string-formatting) +/// - [Python documentation: `str.format`](https://docs.python.org/3/library/stdtypes.html#str.format) #[violation] pub struct PrintfStringFormatting; diff --git a/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs index 1a8a29e0a6..c14ad79904 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/quoted_annotation.rs @@ -6,6 +6,36 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::Rule; +/// ## What it does +/// Checks for the presence of unnecessary quotes in type annotations. +/// +/// ## Why is this bad? +/// In Python, type annotations can be quoted to avoid forward references. +/// However, if `from __future__ import annotations` is present, Python +/// will always evaluate type annotations in a deferred manner, making +/// the quotes unnecessary. +/// +/// ## Example +/// ```python +/// from __future__ import annotations +/// +/// +/// def foo(bar: "Bar") -> "Bar": +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// from __future__ import annotations +/// +/// +/// def foo(bar: Bar) -> Bar: +/// ... +/// ``` +/// +/// ## References +/// - [PEP 563](https://peps.python.org/pep-0563/) +/// - [Python documentation: `__future__` - Future statement definitions](https://docs.python.org/3/library/__future__.html#module-__future__) #[violation] pub struct QuotedAnnotation; diff --git a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs index b65019e999..3646b9ad86 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -13,6 +13,27 @@ use ruff_python_ast::source_code::Locator; use crate::checkers::ast::Checker; use crate::registry::Rule; +/// ## What it does +/// Checks for redundant `open` mode parameters. +/// +/// ## Why is this bad? +/// Redundant `open` mode parameters are unnecessary and should be removed to +/// avoid confusion. +/// +/// ## Example +/// ```python +/// with open("foo.txt", "r") as f: +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// with open("foo.txt") as f: +/// ... +/// ``` +/// +/// ## References +/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open) #[violation] pub struct RedundantOpenModes { pub replacement: Option, diff --git a/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index 83f18f75c5..4dab18b024 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -10,13 +10,40 @@ use crate::autofix::edits::remove_argument; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `subprocess.run` that send `stdout` and `stderr` to a +/// pipe. +/// +/// ## Why is this bad? +/// As of Python 3.7, `subprocess.run` has a `capture_output` keyword argument +/// that can be set to `True` to capture `stdout` and `stderr` outputs. This is +/// equivalent to setting `stdout` and `stderr` to `subprocess.PIPE`, but is +/// more explicit and readable. +/// +/// ## Example +/// ```python +/// import subprocess +/// +/// subprocess.run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +/// ``` +/// +/// Use instead: +/// ```python +/// import subprocess +/// +/// subprocess.run(["foo"], capture_output=True) +/// ``` +/// +/// ## References +/// - [Python 3.7 release notes](https://docs.python.org/3/whatsnew/3.7.html#subprocess) +/// - [Python documentation: `subprocess.run`](https://docs.python.org/3/library/subprocess.html#subprocess.run) #[violation] pub struct ReplaceStdoutStderr; impl AlwaysAutofixableViolation for ReplaceStdoutStderr { #[derive_message_formats] fn message(&self) -> String { - format!("Sending stdout and stderr to pipe is deprecated, use `capture_output`") + format!("Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output`") } fn autofix_title(&self) -> String { diff --git a/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs index e46f87cc04..2e444211d5 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -8,6 +8,33 @@ use ruff_python_ast::helpers::find_keyword; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `subprocess.run` that set the `universal_newlines` +/// keyword argument. +/// +/// ## Why is this bad? +/// As of Python 3.7, the `universal_newlines` keyword argument has been +/// renamed to `text`, and now exists for backwards compatibility. The +/// `universal_newlines` keyword argument may be removed in a future version of +/// Python. Prefer `text`, which is more explicit and readable. +/// +/// ## Example +/// ```python +/// import subprocess +/// +/// subprocess.run(["foo"], universal_newlines=True) +/// ``` +/// +/// Use instead: +/// ```python +/// import subprocess +/// +/// subprocess.run(["foo"], text=True) +/// ``` +/// +/// ## References +/// - [Python 3.7 release notes](https://docs.python.org/3/whatsnew/3.7.html#subprocess) +/// - [Python documentation: `subprocess.run`](https://docs.python.org/3/library/subprocess.html#subprocess.run) #[violation] pub struct ReplaceUniversalNewlines; diff --git a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs index a7098a0eab..898ef4f364 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -7,6 +7,44 @@ use crate::checkers::ast::Checker; use crate::registry::AsRule; use crate::rules::pyupgrade::fixes; +/// ## What it does +/// Checks for `super` calls that pass redundant arguments. +/// +/// ## Why is this bad? +/// In Python 3, `super` can be invoked without any arguments when: (1) the +/// first argument is `__class__`, and (2) the second argument is equivalent to +/// the first argument of the enclosing method. +/// +/// When possible, omit the arguments to `super` to make the code more concise +/// and maintainable. +/// +/// ## Example +/// ```python +/// class A: +/// def foo(self): +/// pass +/// +/// +/// class B(A): +/// def bar(self): +/// super(B, self).foo() +/// ``` +/// +/// Use instead: +/// ```python +/// class A: +/// def foo(self): +/// pass +/// +/// +/// class B(A): +/// def bar(self): +/// super().foo() +/// ``` +/// +/// ## References +/// - [Python documentation: `super`](https://docs.python.org/3/library/functions.html#super) +/// - [super/MRO, Python's most misunderstood feature.](https://www.youtube.com/watch?v=X1PQ7zzltz4) #[violation] pub struct SuperCallWithParameters; diff --git a/crates/ruff/src/rules/pyupgrade/rules/type_of_primitive.rs b/crates/ruff/src/rules/pyupgrade/rules/type_of_primitive.rs index 9ee8cecfb2..e432547cde 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/type_of_primitive.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/type_of_primitive.rs @@ -8,6 +8,27 @@ use crate::registry::AsRule; use super::super::types::Primitive; +/// ## What it does +/// Checks for uses of `type` that take a primitive as an argument. +/// +/// ## Why is this bad? +/// `type()` returns the type of a given object. A type of a primitive can +/// always be known in advance and accessed directly, which is more concise +/// and explicit than using `type()`. +/// +/// ## Example +/// ```python +/// type(1) +/// ``` +/// +/// Use instead: +/// ```python +/// int +/// ``` +/// +/// ## References +/// - [Python documentation: `type()`](https://docs.python.org/3/library/functions.html#type) +/// - [Python documentation: Built-in types](https://docs.python.org/3/library/stdtypes.html) #[violation] pub struct TypeOfPrimitive { primitive: Primitive, diff --git a/crates/ruff/src/rules/pyupgrade/rules/typing_text_str_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/typing_text_str_alias.rs index cfdd0bceb5..ea8d0955ce 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/typing_text_str_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/typing_text_str_alias.rs @@ -1,22 +1,46 @@ use rustpython_parser::ast::{Expr, Ranged}; -use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of `typing.Text`. +/// +/// ## Why is this bad? +/// `typing.Text` is an alias for `str`, and only exists for Python 2 +/// compatibility. As of Python 3.11, `typing.Text` is deprecated. Use `str` +/// instead. +/// +/// ## Example +/// ```python +/// from typing import Text +/// +/// foo: Text = "bar" +/// ``` +/// +/// Use instead: +/// ```python +/// foo: str = "bar" +/// ``` +/// +/// ## References +/// - [Python documentation: `typing.Text`](https://docs.python.org/3/library/typing.html#typing.Text) #[violation] pub struct TypingTextStrAlias; -impl AlwaysAutofixableViolation for TypingTextStrAlias { +impl Violation for TypingTextStrAlias { + const AUTOFIX: AutofixKind = AutofixKind::Sometimes; + #[derive_message_formats] fn message(&self) -> String { format!("`typing.Text` is deprecated, use `str`") } - fn autofix_title(&self) -> String { - "Replace with `str`".to_string() + fn autofix_title(&self) -> Option { + Some("Replace with `str`".to_string()) } } @@ -31,11 +55,12 @@ pub(crate) fn typing_text_str_alias(checker: &mut Checker, expr: &Expr) { { let mut diagnostic = Diagnostic::new(TypingTextStrAlias, expr.range()); if checker.patch(diagnostic.kind.rule()) { - #[allow(deprecated)] - diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - "str".to_string(), - expr.range(), - ))); + if checker.semantic().is_builtin("str") { + diagnostic.set_fix(Fix::automatic(Edit::range_replacement( + "str".to_string(), + expr.range(), + ))); + } } checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff/src/rules/pyupgrade/rules/unicode_kind_prefix.rs b/crates/ruff/src/rules/pyupgrade/rules/unicode_kind_prefix.rs index 45dbf962d5..4058be5de3 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unicode_kind_prefix.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unicode_kind_prefix.rs @@ -7,6 +7,25 @@ use ruff_macros::{derive_message_formats, violation}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for uses of the Unicode kind prefix (`u`) in strings. +/// +/// ## Why is this bad? +/// In Python 3, all strings are Unicode by default. The Unicode kind prefix is +/// unnecessary and should be removed to avoid confusion. +/// +/// ## Example +/// ```python +/// u"foo" +/// ``` +/// +/// Use instead: +/// ```python +/// "foo" +/// ``` +/// +/// ## References +/// - [Python documentation: Unicode HOWTO](https://docs.python.org/3/howto/unicode.html) #[violation] pub struct UnicodeKindPrefix; @@ -23,17 +42,14 @@ impl AlwaysAutofixableViolation for UnicodeKindPrefix { /// UP025 pub(crate) fn unicode_kind_prefix(checker: &mut Checker, expr: &Expr, kind: Option<&str>) { - if let Some(const_kind) = kind { - if const_kind.to_lowercase() == "u" { - let mut diagnostic = Diagnostic::new(UnicodeKindPrefix, expr.range()); - if checker.patch(diagnostic.kind.rule()) { - #[allow(deprecated)] - diagnostic.set_fix(Fix::unspecified(Edit::range_deletion(TextRange::at( - expr.start(), - TextSize::from(1), - )))); - } - checker.diagnostics.push(diagnostic); + if matches!(kind, Some("u" | "U")) { + let mut diagnostic = Diagnostic::new(UnicodeKindPrefix, expr.range()); + if checker.patch(diagnostic.kind.rule()) { + diagnostic.set_fix(Fix::automatic(Edit::range_deletion(TextRange::at( + expr.start(), + TextSize::from(1), + )))); } + checker.diagnostics.push(diagnostic); } } diff --git a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs index 9353ad8c32..070a1c4aaa 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs @@ -8,6 +8,27 @@ use crate::autofix; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for unnecessary imports of builtins. +/// +/// ## Why is this bad? +/// Builtins are always available. Importing them is unnecessary and should be +/// removed to avoid confusion. +/// +/// ## Example +/// ```python +/// from builtins import str +/// +/// str(1) +/// ``` +/// +/// Use instead: +/// ```python +/// str(1) +/// ``` +/// +/// ## References +/// - [Python documentation: The Python Standard Library](https://docs.python.org/3/library/index.html) #[violation] pub struct UnnecessaryBuiltinImport { pub names: Vec, diff --git a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs index 7b474ab5f0..195607e58e 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs @@ -5,7 +5,25 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_whitespace::Line; -// TODO: document referencing [PEP 3120]: https://peps.python.org/pep-3120/ +/// ## What it does +/// Checks for unnecessary UTF-8 encoding declarations. +/// +/// ## Why is this bad? +/// [PEP 3120] makes UTF-8 the default encoding, so a UTF-8 encoding +/// declaration is unnecessary. +/// +/// ## Example +/// ```python +/// # -*- coding: utf-8 -*- +/// print("Hello, world!") +/// ``` +/// +/// Use instead: +/// ```python +/// print("Hello, world!") +/// ``` +/// +/// [PEP 3120]: https://peps.python.org/pep-3120/ #[violation] pub struct UTF8EncodingDeclaration; diff --git a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs index 3e286b2d95..ecd0505c49 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs @@ -16,6 +16,25 @@ pub(crate) enum Reason { DefaultArgument, } +/// ## What it does +/// Checks for unnecessary calls to `encode` as UTF-8. +/// +/// ## Why is this bad? +/// UTF-8 is the default encoding in Python, so there is no need to call +/// `encode` when UTF-8 is the desired encoding. Instead, use a bytes literal. +/// +/// ## Example +/// ```python +/// "foo".encode("utf-8") +/// ``` +/// +/// Use instead: +/// ```python +/// b"foo" +/// ``` +/// +/// ## References +/// - [Python documentation: `str.encode`](https://docs.python.org/3/library/stdtypes.html#str.encode) #[violation] pub struct UnnecessaryEncodeUTF8 { reason: Reason, diff --git a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_future_import.rs b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_future_import.rs index f3f1bbd609..676a5ad928 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unnecessary_future_import.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unnecessary_future_import.rs @@ -8,6 +8,30 @@ use crate::autofix; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for unnecessary `__future__` imports. +/// +/// ## Why is this bad? +/// The `__future__` module is used to enable features that are not yet +/// available in the current Python version. If a feature is already +/// available in the minimum supported Python version, importing it +/// from `__future__` is unnecessary and should be removed to avoid +/// confusion. +/// +/// ## Example +/// ```python +/// from __future__ import print_function +/// +/// print("Hello, world!") +/// ``` +/// +/// Use instead: +/// ```python +/// print("Hello, world!") +/// ``` +/// +/// ## References +/// - [Python documentation: `__future__` — Future statement definitions](https://docs.python.org/3/library/__future__.html) #[violation] pub struct UnnecessaryFutureImport { pub names: Vec, diff --git a/crates/ruff/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs b/crates/ruff/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs index 9098faf7fb..939b5e98bd 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs @@ -7,6 +7,27 @@ use ruff_python_ast::helpers::any_over_expr; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for list comprehensions that are immediately unpacked. +/// +/// ## Why is this bad? +/// There is no reason to use a list comprehension if the result is immediately +/// unpacked. Instead, use a generator expression, which is more efficient as +/// it avoids allocating an intermediary list. +/// +/// ## Example +/// ```python +/// a, b, c = [foo(x) for x in items] +/// ``` +/// +/// Use instead: +/// ```python +/// a, b, c = (foo(x) for x in items) +/// ``` +/// +/// ## References +/// - [Python documentation: Generator expressions](https://docs.python.org/3/reference/expressions.html#generator-expressions) +/// - [Python documentation: List comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) #[violation] pub struct UnpackedListComprehension; diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep585_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep585_annotation.rs index 5c8305e185..42677e097b 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep585_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep585_annotation.rs @@ -9,6 +9,33 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::registry::AsRule; +/// ## What it does +/// Checks for the use of generics that can be replaced with standard library +/// variants based on [PEP 585]. +/// +/// ## Why is this bad? +/// [PEP 585] enabled collections in the Python standard library (like `list`) +/// to be used as generics directly, instead of importing analogous members +/// from the `typing` module (like `typing.List`). +/// +/// When available, the [PEP 585] syntax should be used instead of importing +/// members from the `typing` module, as it's more concise and readable. +/// Importing those members from `typing` is considered deprecated as of PEP +/// 585. +/// +/// ## Example +/// ```python +/// from typing import List +/// +/// foo: List[int] = [1, 2, 3] +/// ``` +/// +/// Use instead: +/// ```python +/// foo: list[int] = [1, 2, 3] +/// ``` +/// +/// [PEP 585]: https://peps.python.org/pep-0585/ #[violation] pub struct NonPEP585Annotation { from: String, diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 67589ca86e..f96ad905d7 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -8,6 +8,27 @@ use ruff_python_semantic::analyze::typing::Pep604Operator; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Check for type annotations that can be rewritten based on [PEP 604] syntax. +/// +/// ## Why is this bad? +/// [PEP 604] introduced a new syntax for union type annotations based on the +/// `|` operator. This syntax is more concise and readable than the previous +/// `typing.Union` and `typing.Optional` syntaxes. +/// +/// ## Example +/// ```python +/// from typing import Union +/// +/// foo: Union[int, str] = 1 +/// ``` +/// +/// Use instead: +/// ```python +/// foo: int | str = 1 +/// ``` +/// +/// [PEP 604]: https://peps.python.org/pep-0604/ #[violation] pub struct NonPEP604Annotation; diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index c0f8d6d28b..aa7ce7c1a8 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -34,6 +34,30 @@ impl CallKind { } } +/// ## What it does +/// Checks for uses of `isinstance` and `issubclass` that take a tuple +/// of types for comparison. +/// +/// ## Why is this bad? +/// Since Python 3.10, `isinstance` and `issubclass` can be passed a +/// `|`-separated union of types, which is more concise and consistent +/// with the union operator introduced in [PEP 604]. +/// +/// ## Example +/// ```python +/// isinstance(x, (int, float)) +/// ``` +/// +/// Use instead: +/// ```python +/// isinstance(x, int | float) +/// ``` +/// +/// ## References +/// - [Python documentation: `isinstance`](https://docs.python.org/3/library/functions.html#isinstance) +/// - [Python documentation: `issubclass`](https://docs.python.org/3/library/functions.html#issubclass) +/// +/// [PEP 604]: https://peps.python.org/pep-0604/ #[violation] pub struct NonPEP604Isinstance { kind: CallKind, diff --git a/crates/ruff/src/rules/pyupgrade/rules/useless_metaclass_type.rs b/crates/ruff/src/rules/pyupgrade/rules/useless_metaclass_type.rs index 104384c246..a739ccad3e 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/useless_metaclass_type.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/useless_metaclass_type.rs @@ -7,6 +7,26 @@ use crate::autofix; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for the use of `__metaclass__ = type` in class definitions. +/// +/// ## Why is this bad? +/// Since Python 3, `__metaclass__ = type` is implied and can thus be omitted. +/// +/// ## Example +/// ```python +/// class Foo: +/// __metaclass__ = type +/// ``` +/// +/// Use instead: +/// ```python +/// class Foo: +/// ... +/// ``` +/// +/// ## References +/// - [PEP 3115](https://www.python.org/dev/peps/pep-3115/) #[violation] pub struct UselessMetaclassType; diff --git a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs index 4c8788324b..d3a86d91fb 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -8,6 +8,27 @@ use crate::autofix::edits::remove_argument; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for classes that inherit from `object`. +/// +/// ## Why is this bad? +/// Since Python 3, all classes inherit from `object` by default, so `object` can +/// be omitted from the list of base classes. +/// +/// ## Example +/// ```python +/// class Foo(object): +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// class Foo: +/// ... +/// ``` +/// +/// ## References +/// - [PEP 3115](https://www.python.org/dev/peps/pep-3115/) #[violation] pub struct UselessObjectInheritance { name: String, diff --git a/crates/ruff/src/rules/pyupgrade/rules/yield_in_for_loop.rs b/crates/ruff/src/rules/pyupgrade/rules/yield_in_for_loop.rs index 999df0fd04..6533a26278 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/yield_in_for_loop.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/yield_in_for_loop.rs @@ -11,6 +11,27 @@ use ruff_python_ast::{statement_visitor, visitor}; use crate::checkers::ast::Checker; use crate::registry::AsRule; +/// ## What it does +/// Checks for `for` loops that can be replaced with `yield from` expressions. +/// +/// ## Why is this bad? +/// If a `for` loop only contains a `yield` statement, it can be replaced with a +/// `yield from` expression, which is more concise and idiomatic. +/// +/// ## Example +/// ```python +/// for x in foo: +/// yield x +/// ``` +/// +/// Use instead: +/// ```python +/// yield from foo +/// ``` +/// +/// ## References +/// - [Python documentation: The `yield` statement](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) +/// - [PEP 380](https://peps.python.org/pep-0380/) #[violation] pub struct YieldInForLoop; diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP019.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP019.py.snap index c4697f0e39..291ef30d33 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP019.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP019.py.snap @@ -9,7 +9,7 @@ UP019.py:7:22: UP019 [*] `typing.Text` is deprecated, use `str` | = help: Replace with `str` -ℹ Suggested fix +ℹ Fix 4 4 | from typing import Text as Goodbye 5 5 | 6 6 | @@ -27,7 +27,7 @@ UP019.py:11:29: UP019 [*] `typing.Text` is deprecated, use `str` | = help: Replace with `str` -ℹ Suggested fix +ℹ Fix 8 8 | print(word) 9 9 | 10 10 | @@ -45,7 +45,7 @@ UP019.py:15:28: UP019 [*] `typing.Text` is deprecated, use `str` | = help: Replace with `str` -ℹ Suggested fix +ℹ Fix 12 12 | print(word) 13 13 | 14 14 | @@ -63,7 +63,7 @@ UP019.py:19:29: UP019 [*] `typing.Text` is deprecated, use `str` | = help: Replace with `str` -ℹ Suggested fix +ℹ Fix 16 16 | print(word) 17 17 | 18 18 | diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP022.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP022.py.snap index 6aade82834..75df54757d 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP022.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP022.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/pyupgrade/mod.rs --- -UP022.py:4:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:4:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 2 | import subprocess 3 | @@ -22,7 +22,7 @@ UP022.py:4:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `c 6 6 | output = subprocess.run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 7 7 | -UP022.py:6:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:6:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 4 | output = run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 5 | @@ -43,7 +43,7 @@ UP022.py:6:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `c 8 8 | output = subprocess.run(stdout=subprocess.PIPE, args=["foo"], stderr=subprocess.PIPE) 9 9 | -UP022.py:8:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:8:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 6 | output = subprocess.run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 7 | @@ -64,7 +64,7 @@ UP022.py:8:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `c 10 10 | output = subprocess.run( 11 11 | ["foo"], stdout=subprocess.PIPE, check=True, stderr=subprocess.PIPE -UP022.py:10:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:10:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 8 | output = subprocess.run(stdout=subprocess.PIPE, args=["foo"], stderr=subprocess.PIPE) 9 | @@ -88,7 +88,7 @@ UP022.py:10:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use ` 13 13 | 14 14 | output = subprocess.run( -UP022.py:14:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:14:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 12 | ) 13 | @@ -112,7 +112,7 @@ UP022.py:14:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use ` 17 17 | 18 18 | output = subprocess.run( -UP022.py:18:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:18:10: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 16 | ) 17 | @@ -144,7 +144,7 @@ UP022.py:18:10: UP022 [*] Sending stdout and stderr to pipe is deprecated, use ` 24 23 | encoding="utf-8", 25 24 | close_fds=True, -UP022.py:29:14: UP022 [*] Sending stdout and stderr to pipe is deprecated, use `capture_output` +UP022.py:29:14: UP022 [*] Sending `stdout` and `stderr` to `PIPE` is deprecated, use `capture_output` | 28 | if output: 29 | output = subprocess.run( diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP025.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP025.py.snap index 0ef2bd7b18..380d022354 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP025.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP025.py.snap @@ -11,7 +11,7 @@ UP025.py:2:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 1 1 | # These should change 2 |-x = u"Hello" 2 |+x = "Hello" @@ -30,7 +30,7 @@ UP025.py:4:1: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 1 1 | # These should change 2 2 | x = u"Hello" 3 3 | @@ -51,7 +51,7 @@ UP025.py:6:7: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 3 3 | 4 4 | u'world' 5 5 | @@ -72,7 +72,7 @@ UP025.py:8:7: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 5 5 | 6 6 | print(u"Hello") 7 7 | @@ -93,7 +93,7 @@ UP025.py:12:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 9 9 | 10 10 | import foo 11 11 | @@ -114,7 +114,7 @@ UP025.py:12:15: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 9 9 | 10 10 | import foo 11 11 | @@ -135,7 +135,7 @@ UP025.py:12:27: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 9 9 | 10 10 | import foo 11 11 | @@ -156,7 +156,7 @@ UP025.py:12:39: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 9 9 | 10 10 | import foo 11 11 | @@ -177,7 +177,7 @@ UP025.py:16:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 13 13 | 14 14 | # These should stay quoted they way they are 15 15 | @@ -197,7 +197,7 @@ UP025.py:17:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 14 14 | # These should stay quoted they way they are 15 15 | 16 16 | x = u'hello' @@ -217,7 +217,7 @@ UP025.py:18:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 15 15 | 16 16 | x = u'hello' 17 17 | x = u"""hello""" @@ -238,7 +238,7 @@ UP025.py:19:5: UP025 [*] Remove unicode literals from strings | = help: Remove unicode prefix -ℹ Suggested fix +ℹ Fix 16 16 | x = u'hello' 17 17 | x = u"""hello""" 18 18 | x = u'''hello''' diff --git a/scripts/check_docs_formatted.py b/scripts/check_docs_formatted.py index ef5ec4fc08..336ce040eb 100755 --- a/scripts/check_docs_formatted.py +++ b/scripts/check_docs_formatted.py @@ -48,6 +48,7 @@ KNOWN_FORMATTING_VIOLATIONS = [ "too-few-spaces-before-inline-comment", "trailing-comma-on-bare-tuple", "unexpected-indentation-comment", + "unicode-kind-prefix", "unnecessary-class-parentheses", "useless-semicolon", "whitespace-after-open-bracket",