diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 81374e8fe544a..711e100bea3e3 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -180,8 +180,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::RedundantNumericUnion) { flake8_pyi::rules::redundant_numeric_union(checker, parameters); } - if checker.enabled(Rule::PrePep570PositionalArgument) { - flake8_pyi::rules::pre_pep570_positional_argument(checker, function_def); + if checker.enabled(Rule::Pep484StylePositionalOnlyParameter) { + flake8_pyi::rules::pep_484_positional_parameter(checker, function_def); } if checker.enabled(Rule::DunderFunctionName) { if let Some(diagnostic) = pep8_naming::rules::dunder_function_name( diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index cc6ea649153f5..47610e2f29d2f 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -791,7 +791,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass), (Flake8Pyi, "061") => (RuleGroup::Preview, rules::flake8_pyi::rules::RedundantNoneLiteral), (Flake8Pyi, "062") => (RuleGroup::Stable, rules::flake8_pyi::rules::DuplicateLiteralMember), - (Flake8Pyi, "063") => (RuleGroup::Stable, rules::flake8_pyi::rules::PrePep570PositionalArgument), + (Flake8Pyi, "063") => (RuleGroup::Stable, rules::flake8_pyi::rules::Pep484StylePositionalOnlyParameter), (Flake8Pyi, "064") => (RuleGroup::Stable, rules::flake8_pyi::rules::RedundantFinalLiteral), (Flake8Pyi, "066") => (RuleGroup::Stable, rules::flake8_pyi::rules::BadVersionInfoOrder), diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs index 09468b574e5b0..07d4d8b8597b1 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs @@ -11,14 +11,14 @@ use crate::rules::fastapi::rules::is_fastapi_route; use crate::settings::types::PythonVersion; /// ## What it does -/// Identifies FastAPI routes with deprecated uses of `Depends`. +/// Identifies FastAPI routes with deprecated uses of `Depends` or similar. /// /// ## Why is this bad? -/// The FastAPI documentation recommends the use of `Annotated` for defining -/// route dependencies and parameters, rather than using `Depends` directly -/// with a default value. -/// -/// This approach is also suggested for various route parameters, including Body and Cookie, as it helps ensure consistency and clarity in defining dependencies and parameters. +/// The [FastAPI documentation] recommends the use of [`typing.Annotated`] for +/// defining route dependencies and parameters, rather than using `Depends`, +/// `Query` or similar as a default value for a parameter. Using this approach +/// everywhere helps ensure consistency and clarity in defining dependencies +/// and parameters. /// /// ## Example /// @@ -55,7 +55,9 @@ use crate::settings::types::PythonVersion; /// async def read_items(commons: Annotated[dict, Depends(common_parameters)]): /// return commons /// ``` - +/// +/// [fastAPI documentation]: https://fastapi.tiangolo.com/tutorial/query-params-str-validations/?h=annotated#advantages-of-annotated +/// [typing.Annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated #[violation] pub struct FastApiNonAnnotatedDependency; @@ -72,7 +74,7 @@ impl Violation for FastApiNonAnnotatedDependency { } } -/// RUF103 +/// FAST002 pub(crate) fn fastapi_non_annotated_dependency( checker: &mut Checker, function_def: &ast::StmtFunctionDef, diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs index 36f7fc1129a8f..a18749d05b5dd 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs @@ -73,7 +73,7 @@ impl AlwaysFixableViolation for FastApiRedundantResponseModel { } } -/// RUF102 +/// FAST001 pub(crate) fn fastapi_redundant_response_model( checker: &mut Checker, function_def: &StmtFunctionDef, diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs index 184125011b313..b6ce0fc2b92ee 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs @@ -19,9 +19,9 @@ use crate::checkers::ast::Checker; /// the `ContextVar`. If the object is modified, those modifications will persist /// across calls, which can lead to unexpected behavior. /// -/// Instead, prefer to use immutable data structures; or, take `None` as a -/// default, and initialize a new mutable object inside for each call using the -/// `.set()` method. +/// Instead, prefer to use immutable data structures. Alternatively, take +/// `None` as a default, and initialize a new mutable object inside for each +/// call using the `.set()` method. /// /// Types outside the standard library can be marked as immutable with the /// [`lint.flake8-bugbear.extend-immutable-calls`] configuration option. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 4d383a1b54c4b..13c2fa2745e16 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -66,8 +66,8 @@ mod tests { #[test_case(Rule::PatchVersionComparison, Path::new("PYI004.pyi"))] #[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.py"))] #[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))] - #[test_case(Rule::PrePep570PositionalArgument, Path::new("PYI063.py"))] - #[test_case(Rule::PrePep570PositionalArgument, Path::new("PYI063.pyi"))] + #[test_case(Rule::Pep484StylePositionalOnlyParameter, Path::new("PYI063.py"))] + #[test_case(Rule::Pep484StylePositionalOnlyParameter, Path::new("PYI063.pyi"))] #[test_case(Rule::RedundantFinalLiteral, Path::new("PYI064.py"))] #[test_case(Rule::RedundantFinalLiteral, Path::new("PYI064.pyi"))] #[test_case(Rule::RedundantLiteralUnion, Path::new("PYI051.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs index ed89ed4ad60b1..5f09722aeb3cd 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs @@ -16,8 +16,9 @@ use crate::registry::Rule; /// Comparing `sys.version_info` with `==` or `<=` has unexpected behavior /// and can lead to bugs. /// -/// For example, `sys.version_info > (3, 8)` will also match `3.8.10`, -/// while `sys.version_info <= (3, 8)` will _not_ match `3.8.10`: +/// For example, `sys.version_info > (3, 8, 1)` will resolve to `True` if your +/// Python version is 3.8.1; meanwhile, `sys.version_info <= (3, 8)` will _not_ +/// resolve to `True` if your Python version is 3.8.10: /// /// ```python /// >>> import sys @@ -61,16 +62,20 @@ impl Violation for BadVersionInfoComparison { } /// ## What it does -/// Checks for if-else statements with `sys.version_info` comparisons that use -/// `<` comparators. +/// Checks for code that branches on `sys.version_info` comparisons where +/// branches corresponding to older Python versions come before branches +/// corresponding to newer Python versions. /// /// ## Why is this bad? /// As a convention, branches that correspond to newer Python versions should -/// come first when using `sys.version_info` comparisons. This makes it easier -/// to understand the desired behavior, which typically corresponds to the -/// latest Python versions. +/// come first. This makes it easier to understand the desired behavior, which +/// typically corresponds to the latest Python versions. /// -/// In [preview], this rule will also flag non-stub files. +/// This rule enforces the convention by checking for `if` tests that compare +/// `sys.version_info` with `<` rather than `>=`. +/// +/// By default, this rule only applies to stub files. +/// In [preview], it will also flag this anti-pattern in non-stub files. /// /// ## Example /// @@ -101,7 +106,7 @@ pub struct BadVersionInfoOrder; impl Violation for BadVersionInfoOrder { #[derive_message_formats] fn message(&self) -> String { - "Use `>=` when using `if`-`else` with `sys.version_info` comparisons".to_string() + "Put branches for newer Python versions first when branching on `sys.version_info` comparisons".to_string() } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs index e1eee26fa6e6d..4757f09d52539 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs @@ -8,12 +8,19 @@ use crate::checkers::ast::Checker; use crate::settings::types::PythonVersion; /// ## What it does -/// Checks for the presence of [PEP 484]-style positional-only arguments. +/// Checks for the presence of [PEP 484]-style positional-only parameters. /// /// ## Why is this bad? -/// Historically, [PEP 484] recommended prefixing positional-only arguments -/// with a double underscore (`__`). However, [PEP 570] introduced a dedicated -/// syntax for positional-only arguments, which should be preferred. +/// Historically, [PEP 484] recommended prefixing parameter names with double +/// underscores (`__`) to indicate to a type checker that they were +/// positional-only. However, [PEP 570] (introduced in Python 3.8) introduced +/// dedicated syntax for positional-only arguments. If a forward slash (`/`) is +/// present in a function signature on Python 3.8+, all parameters prior to the +/// slash are interpreted as positional-only. +/// +/// The new syntax should be preferred as it is more widely used, more concise +/// and more readable. It is also respected by Python at runtime, whereas the +/// old-style syntax was only understood by type checkers. /// /// ## Example /// @@ -33,12 +40,12 @@ use crate::settings::types::PythonVersion; /// [PEP 484]: https://peps.python.org/pep-0484/#positional-only-arguments /// [PEP 570]: https://peps.python.org/pep-0570 #[violation] -pub struct PrePep570PositionalArgument; +pub struct Pep484StylePositionalOnlyParameter; -impl Violation for PrePep570PositionalArgument { +impl Violation for Pep484StylePositionalOnlyParameter { #[derive_message_formats] fn message(&self) -> String { - "Use PEP 570 syntax for positional-only arguments".to_string() + "Use PEP 570 syntax for positional-only parameters".to_string() } fn fix_title(&self) -> Option { @@ -47,7 +54,7 @@ impl Violation for PrePep570PositionalArgument { } /// PYI063 -pub(crate) fn pre_pep570_positional_argument( +pub(crate) fn pep_484_positional_parameter( checker: &mut Checker, function_def: &ast::StmtFunctionDef, ) { @@ -82,18 +89,18 @@ pub(crate) fn pre_pep570_positional_argument( )); if let Some(arg) = function_def.parameters.args.get(skip) { - if is_pre_pep570_positional_only(arg) { + if is_old_style_positional_only(arg) { checker.diagnostics.push(Diagnostic::new( - PrePep570PositionalArgument, + Pep484StylePositionalOnlyParameter, arg.identifier(), )); } } } -/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only argument (i.e., +/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e., /// its name starts with `__` and does not end with `__`). -fn is_pre_pep570_positional_only(arg: &ParameterWithDefault) -> bool { +fn is_old_style_positional_only(arg: &ParameterWithDefault) -> bool { let arg_name = &arg.parameter.name; arg_name.starts_with("__") && !arg_name.ends_with("__") } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs index be77a3a948efe..65291db74210a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs @@ -11,18 +11,25 @@ use crate::Locator; /// Checks for redundant `Final[Literal[...]]` annotations. /// /// ## Why is this bad? -/// A `Final[Literal[...]]` annotation can be replaced with `Final`; the literal -/// use is unnecessary. +/// All constant variables annotated as `Final` are understood as implicitly +/// having `Literal` types by a type checker. As such, a `Final[Literal[...]]` +/// annotation can often be replaced with a bare `Final`, annotation, which +/// will have the same meaning to the type checker while being more concise and +/// more readable. /// /// ## Example /// /// ```pyi +/// from typing import Final, Literal +/// /// x: Final[Literal[42]] /// y: Final[Literal[42]] = 42 /// ``` /// /// Use instead: /// ```pyi +/// from typing import Final, Literal +/// /// x: Final = 42 /// y: Final = 42 /// ``` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.py.snap index 385ba903f424f..04c88e35cf9b4 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.py.snap @@ -1,8 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- -PYI063.py:6:9: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:6:9: PYI063 Use PEP 570 syntax for positional-only parameters | 4 | from typing import Self 5 | @@ -13,7 +12,7 @@ PYI063.py:6:9: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:7:14: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:7:14: PYI063 Use PEP 570 syntax for positional-only parameters | 6 | def bad(__x: int) -> None: ... # PYI063 7 | def also_bad(__x: int, __y: str) -> None: ... # PYI063 @@ -22,7 +21,7 @@ PYI063.py:7:14: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:8:15: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:8:15: PYI063 Use PEP 570 syntax for positional-only parameters | 6 | def bad(__x: int) -> None: ... # PYI063 7 | def also_bad(__x: int, __y: str) -> None: ... # PYI063 @@ -33,7 +32,7 @@ PYI063.py:8:15: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:24:14: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:24:14: PYI063 Use PEP 570 syntax for positional-only parameters | 22 | def bad(__self) -> None: ... # PYI063 23 | @staticmethod @@ -44,7 +43,7 @@ PYI063.py:24:14: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:25:22: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:25:22: PYI063 Use PEP 570 syntax for positional-only parameters | 23 | @staticmethod 24 | def bad2(__self) -> None: ... # PYI063 @@ -55,7 +54,7 @@ PYI063.py:25:22: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:26:25: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:26:25: PYI063 Use PEP 570 syntax for positional-only parameters | 24 | def bad2(__self) -> None: ... # PYI063 25 | def bad3(__self, __x: int) -> None: ... # PYI063 @@ -66,7 +65,7 @@ PYI063.py:26:25: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:28:25: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:28:25: PYI063 Use PEP 570 syntax for positional-only parameters | 26 | def still_bad(self, __x_: int) -> None: ... # PYI063 27 | @staticmethod @@ -77,7 +76,7 @@ PYI063.py:28:25: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:30:23: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:30:23: PYI063 Use PEP 570 syntax for positional-only parameters | 28 | def this_is_bad_too(__x: int) -> None: ... # PYI063 29 | @classmethod @@ -88,7 +87,7 @@ PYI063.py:30:23: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:52:23: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:52:23: PYI063 Use PEP 570 syntax for positional-only parameters | 50 | class Metaclass(type): 51 | @classmethod @@ -99,7 +98,7 @@ PYI063.py:52:23: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.py:56:26: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.py:56:26: PYI063 Use PEP 570 syntax for positional-only parameters | 54 | class Metaclass2(type): 55 | @classmethod diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.pyi.snap index 8cef657f052cd..f7d4f4de5448a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI063_PYI063.pyi.snap @@ -1,8 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- -PYI063.pyi:6:9: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:6:9: PYI063 Use PEP 570 syntax for positional-only parameters | 4 | from typing import Self 5 | @@ -13,7 +12,7 @@ PYI063.pyi:6:9: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:7:14: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:7:14: PYI063 Use PEP 570 syntax for positional-only parameters | 6 | def bad(__x: int) -> None: ... # PYI063 7 | def also_bad(__x: int, __y: str) -> None: ... # PYI063 @@ -22,7 +21,7 @@ PYI063.pyi:7:14: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:8:15: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:8:15: PYI063 Use PEP 570 syntax for positional-only parameters | 6 | def bad(__x: int) -> None: ... # PYI063 7 | def also_bad(__x: int, __y: str) -> None: ... # PYI063 @@ -33,7 +32,7 @@ PYI063.pyi:8:15: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:24:14: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:24:14: PYI063 Use PEP 570 syntax for positional-only parameters | 22 | def bad(__self) -> None: ... # PYI063 23 | @staticmethod @@ -44,7 +43,7 @@ PYI063.pyi:24:14: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:25:22: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:25:22: PYI063 Use PEP 570 syntax for positional-only parameters | 23 | @staticmethod 24 | def bad2(__self) -> None: ... # PYI063 @@ -55,7 +54,7 @@ PYI063.pyi:25:22: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:26:25: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:26:25: PYI063 Use PEP 570 syntax for positional-only parameters | 24 | def bad2(__self) -> None: ... # PYI063 25 | def bad3(__self, __x: int) -> None: ... # PYI063 @@ -66,7 +65,7 @@ PYI063.pyi:26:25: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:28:25: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:28:25: PYI063 Use PEP 570 syntax for positional-only parameters | 26 | def still_bad(self, __x_: int) -> None: ... # PYI063 # TODO: doesn't get raised here 27 | @staticmethod @@ -77,7 +76,7 @@ PYI063.pyi:28:25: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:30:23: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:30:23: PYI063 Use PEP 570 syntax for positional-only parameters | 28 | def this_is_bad_too(__x: int) -> None: ... # PYI063 29 | @classmethod @@ -88,7 +87,7 @@ PYI063.pyi:30:23: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:52:23: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:52:23: PYI063 Use PEP 570 syntax for positional-only parameters | 50 | class Metaclass(type): 51 | @classmethod @@ -99,7 +98,7 @@ PYI063.pyi:52:23: PYI063 Use PEP 570 syntax for positional-only arguments | = help: Add `/` to function signature -PYI063.pyi:56:26: PYI063 Use PEP 570 syntax for positional-only arguments +PYI063.pyi:56:26: PYI063 Use PEP 570 syntax for positional-only parameters | 54 | class Metaclass2(type): 55 | @classmethod diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI066_PYI066.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI066_PYI066.pyi.snap index 64cb01f72c354..dd98502eebd16 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI066_PYI066.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI066_PYI066.pyi.snap @@ -1,8 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- -PYI066.pyi:3:4: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` comparisons +PYI066.pyi:3:4: PYI066 Put branches for newer Python versions first when branching on `sys.version_info` comparisons | 1 | import sys 2 | @@ -12,7 +11,7 @@ PYI066.pyi:3:4: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` c 5 | else: | -PYI066.pyi:8:4: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` comparisons +PYI066.pyi:8:4: PYI066 Put branches for newer Python versions first when branching on `sys.version_info` comparisons | 6 | def foo(x, *, bar=True): ... 7 | @@ -22,7 +21,7 @@ PYI066.pyi:8:4: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` c 10 | elif sys.version_info < (3, 9): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 9)" | -PYI066.pyi:10:6: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` comparisons +PYI066.pyi:10:6: PYI066 Put branches for newer Python versions first when branching on `sys.version_info` comparisons | 8 | if sys.version_info < (3, 8): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 8)" 9 | def bar(x): ... @@ -32,7 +31,7 @@ PYI066.pyi:10:6: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` 12 | elif sys.version_info < (3, 11): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 10)" | -PYI066.pyi:12:6: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` comparisons +PYI066.pyi:12:6: PYI066 Put branches for newer Python versions first when branching on `sys.version_info` comparisons | 10 | elif sys.version_info < (3, 9): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 9)" 11 | def bar(x, *, bar=True): ... @@ -42,7 +41,7 @@ PYI066.pyi:12:6: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` 14 | else: | -PYI066.pyi:20:6: PYI066 Use `>=` when using `if`-`else` with `sys.version_info` comparisons +PYI066.pyi:20:6: PYI066 Put branches for newer Python versions first when branching on `sys.version_info` comparisons | 18 | if sys.version_info >= (3, 5): 19 | ... diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs index 5c0330f7ec7b2..3cf8d65efd5a2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs @@ -6,14 +6,18 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for unnecessary default type arguments. +/// Checks for unnecessary default type arguments for `Generator` and +/// `AsyncGenerator` on Python 3.13+. /// /// ## Why is this bad? /// Python 3.13 introduced the ability for type parameters to specify default -/// values. As such, the default type arguments for some types in the standard -/// library (e.g., Generator, AsyncGenerator) are now optional. +/// values. Following this change, several standard-library classes were +/// updated to add default values for some of their type parameters. For +/// example, `Generator[int]` is now equivalent to +/// `Generator[int, None, None]`, as the second and third type parameters of +/// `Generator` now default to `None`. /// -/// Omitting type parameters that match the default values can make the code +/// Omitting type arguments that match the default values can make the code /// more concise and easier to read. /// /// ## Examples diff --git a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs index 35a9dac318219..4d2756e1771a3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs @@ -10,13 +10,15 @@ use crate::checkers::ast::Checker; /// Checks for uses of `assert expression, print(message)`. /// /// ## Why is this bad? -/// The return value of the second expression is used as the contents of the -/// `AssertionError` raised by the `assert` statement. Using a `print` expression -/// in this context will output the message to `stdout`, before raising an -/// empty `AssertionError(None)`. +/// If an `assert x, y` assertion fails, the Python interpreter raises an +/// `AssertionError`, and the evaluated value of `y` is used as the contents of +/// that assertion error. The `print` function always returns `None`, however, +/// so the evaluated value of a call to `print` will always be `None`. /// -/// Instead, remove the `print` and pass the message directly as the second -/// expression, allowing `stderr` to capture the message in a well-formatted context. +/// Using a `print` call in this context will therefore output the message to +/// `stdout`, before raising an empty `AssertionError(None)`. Instead, remove +/// the `print` and pass the message directly as the second expression, +/// allowing `stderr` to capture the message in a well-formatted context. /// /// ## Example /// ```python @@ -41,7 +43,7 @@ pub struct AssertWithPrintMessage; impl AlwaysFixableViolation for AssertWithPrintMessage { #[derive_message_formats] fn message(&self) -> String { - "`print()` expression in `assert` statement is likely unintentional".to_string() + "`print()` call in `assert` statement is likely unintentional".to_string() } fn fix_title(&self) -> String { diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 1b4725f95500a..1b789e7b0f4a0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -19,7 +19,7 @@ pub(crate) use mutable_dataclass_default::*; pub(crate) use mutable_fromkeys_value::*; pub(crate) use never_union::*; pub(crate) use none_not_at_end_of_union::*; -pub(crate) use parenthesize_logical_operators::*; +pub(crate) use parenthesize_chained_operators::*; pub(crate) use post_init_default::*; pub(crate) use quadratic_list_summation::*; pub(crate) use redirected_noqa::*; @@ -61,7 +61,7 @@ mod mutable_dataclass_default; mod mutable_fromkeys_value; mod never_union; mod none_not_at_end_of_union; -mod parenthesize_logical_operators; +mod parenthesize_chained_operators; mod post_init_default; mod quadratic_list_summation; mod redirected_noqa; diff --git a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs similarity index 100% rename from crates/ruff_linter/src/rules/ruff/rules/parenthesize_logical_operators.rs rename to crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF030_RUF030.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF030_RUF030.py.snap index f1db8a36e304c..04049505ec025 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF030_RUF030.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF030_RUF030.py.snap @@ -1,8 +1,7 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- -RUF030.py:6:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:6:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 4 | # Expects: 5 | # - single StringLiteral @@ -23,7 +22,7 @@ RUF030.py:6:14: RUF030 [*] `print()` expression in `assert` statement is likely 8 8 | # Concatenated string literals 9 9 | # Expects: -RUF030.py:11:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:11:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 9 | # Expects: 10 | # - single StringLiteral @@ -44,7 +43,7 @@ RUF030.py:11:14: RUF030 [*] `print()` expression in `assert` statement is likely 13 13 | # Positional arguments, string literals 14 14 | # Expects: -RUF030.py:16:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:16:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 14 | # Expects: 15 | # - single StringLiteral concatenated with " " @@ -65,7 +64,7 @@ RUF030.py:16:14: RUF030 [*] `print()` expression in `assert` statement is likely 18 18 | # Concatenated string literals combined with Positional arguments 19 19 | # Expects: -RUF030.py:21:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:21:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 19 | # Expects: 20 | # - single stringliteral concatenated with " " only between `print` and `is` @@ -86,7 +85,7 @@ RUF030.py:21:14: RUF030 [*] `print()` expression in `assert` statement is likely 23 23 | # Positional arguments, string literals with a variable 24 24 | # Expects: -RUF030.py:26:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:26:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 24 | # Expects: 25 | # - single FString concatenated with " " @@ -107,7 +106,7 @@ RUF030.py:26:14: RUF030 [*] `print()` expression in `assert` statement is likely 28 28 | # Mixed brackets string literals 29 29 | # Expects: -RUF030.py:31:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:31:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 29 | # Expects: 30 | # - single StringLiteral concatenated with " " @@ -128,7 +127,7 @@ RUF030.py:31:14: RUF030 [*] `print()` expression in `assert` statement is likely 33 33 | # Mixed brackets with other brackets inside 34 34 | # Expects: -RUF030.py:36:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:36:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 34 | # Expects: 35 | # - single StringLiteral concatenated with " " and escaped brackets @@ -149,7 +148,7 @@ RUF030.py:36:14: RUF030 [*] `print()` expression in `assert` statement is likely 38 38 | # Positional arguments, string literals with a separator 39 39 | # Expects: -RUF030.py:41:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:41:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 39 | # Expects: 40 | # - single StringLiteral concatenated with "|" @@ -170,7 +169,7 @@ RUF030.py:41:14: RUF030 [*] `print()` expression in `assert` statement is likely 43 43 | # Positional arguments, string literals with None as separator 44 44 | # Expects: -RUF030.py:46:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:46:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 44 | # Expects: 45 | # - single StringLiteral concatenated with " " @@ -191,7 +190,7 @@ RUF030.py:46:14: RUF030 [*] `print()` expression in `assert` statement is likely 48 48 | # Positional arguments, string literals with variable as separator, needs f-string 49 49 | # Expects: -RUF030.py:51:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:51:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 49 | # Expects: 50 | # - single FString concatenated with "{U00A0}" @@ -212,7 +211,7 @@ RUF030.py:51:14: RUF030 [*] `print()` expression in `assert` statement is likely 53 53 | # Unnecessary f-string 54 54 | # Expects: -RUF030.py:56:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:56:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 54 | # Expects: 55 | # - single StringLiteral @@ -233,7 +232,7 @@ RUF030.py:56:14: RUF030 [*] `print()` expression in `assert` statement is likely 58 58 | # Positional arguments, string literals and f-strings 59 59 | # Expects: -RUF030.py:61:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:61:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 59 | # Expects: 60 | # - single FString concatenated with " " @@ -254,7 +253,7 @@ RUF030.py:61:14: RUF030 [*] `print()` expression in `assert` statement is likely 63 63 | # Positional arguments, string literals and f-strings with a separator 64 64 | # Expects: -RUF030.py:66:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:66:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 64 | # Expects: 65 | # - single FString concatenated with "|" @@ -275,7 +274,7 @@ RUF030.py:66:14: RUF030 [*] `print()` expression in `assert` statement is likely 68 68 | # A single f-string 69 69 | # Expects: -RUF030.py:71:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:71:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 69 | # Expects: 70 | # - single FString @@ -296,7 +295,7 @@ RUF030.py:71:14: RUF030 [*] `print()` expression in `assert` statement is likely 73 73 | # A single f-string with a redundant separator 74 74 | # Expects: -RUF030.py:76:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:76:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 74 | # Expects: 75 | # - single FString @@ -317,7 +316,7 @@ RUF030.py:76:14: RUF030 [*] `print()` expression in `assert` statement is likely 78 78 | # Complex f-string with variable as separator 79 79 | # Expects: -RUF030.py:83:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:83:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 81 | condition = "True is True" 82 | maintainer = "John Doe" @@ -338,7 +337,7 @@ RUF030.py:83:14: RUF030 [*] `print()` expression in `assert` statement is likely 85 85 | # Empty print 86 86 | # Expects: -RUF030.py:88:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:88:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 86 | # Expects: 87 | # - `msg` entirely removed from assertion @@ -359,7 +358,7 @@ RUF030.py:88:14: RUF030 [*] `print()` expression in `assert` statement is likely 90 90 | # Empty print with separator 91 91 | # Expects: -RUF030.py:93:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:93:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 91 | # Expects: 92 | # - `msg` entirely removed from assertion @@ -380,7 +379,7 @@ RUF030.py:93:14: RUF030 [*] `print()` expression in `assert` statement is likely 95 95 | # Custom print function that actually returns a string 96 96 | # Expects: -RUF030.py:108:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional +RUF030.py:108:14: RUF030 [*] `print()` call in `assert` statement is likely unintentional | 106 | # Expects: 107 | # - single StringLiteral